Condividi tramite


Splatbuilder Wrapper

This week, I was working on a project that spawned a lot of ideas for posts.  The project involves creating a wrapper for a number of cmdlets and being able to pass any / all of the parameters to the actual cmdlet being performed.  I've done this type of work before, but the cmdlets didn't have a lot of parameters.  In the case of Set-Mailbox, however, there are a ton of parameters that can possibly be set, and I became exhausted just thinking about writing all of them down

But, as my history teacher used to say, "Use a test to take a test."

There are a couple of ways to get all of the parameters for a cmdlet, but I'm going to look at what I think is the fastest.

One of the tasks I decided to embark upon was a "splat builder," if you will ... since i had to do this type of wrapping for a lot of cmdlets, I thought it would be useful to have a tool to do the work for me.  For this portion, I decided that I would use the System.Text.Stringbuilder class to build a few blocks of text and then output them to a file that I could then open or use.  If you want to know a bunch of cool stuff you can do with Stringbuilder (or really, anything in PowerShell), I heartily recommend reading Kevin Marquette's blog.

The building blocks of my script would be:

  • Capturing the available parameters from Get-Command <command>
  • Iterating through those to build a param() block
  • Building a new hash table containing the soon-to-be splatted parameters that were passed to my script from the command line
  • Outputting that all to a text file that I could then use as the basis for a new script

Capture all parameters by querying the parameters of the cmdlet

The easiest way to see everything available is to use Get-Command:

 (Get-Command Set-Mailbox).Parameters.Keys

This gets you a pretty long list of the parameters.  Like, again, tired just looking at it. Step aside typing every parameter, this is definitely a job for looping.

As a footnote, Set-Mailbox has 133 parameters.  So, definitely need to automate this.

The first step turned out to be relatively painless:

 [array]$paramvalues = (Get-Command Set-Mailbox).Parameters.Keys

On to the next part!

Iterating through the $paramvalues to build a param() block

Since we want to capture a parameter and its corresponding value (to wrap and pass on down the line), we need to define a param() block.  Typically, it's something like param($param1,$param2) .  You can learn more about function structure at our whiz-bang documentation site docs.microsoft.com.

This is a relatively simple process, and armed with my now encyclopedic knowledge of the Stringbuilder class, this becomes pretty easy as well:

 $paramblock = New-Object System.Text.StringBuilder
[void]$paramblock.Append("param(") 
     foreach ($obj in $paramvalues)
     {
          [void]$paramblock.Append("`n`$$($obj),") 
     }
$paramblock = $paramblock.ToString().TrimEnd(",") + ")`n"

Deconstructing, we're:

  1. Creating a new $paramblock Stringbuilder object.  This is what's going to hold our string in memory while we build it.  Hence, the name Stringbuilder. I'm here all week, folks.
  2. Adding the value "param(" to the first line.
  3. Looping through $paramvalues (which is just a list of the parameter names we created in the previous section), adding `n`$$($obj) in each successive loop:
    1. `n is a newline character
    2. `$ is denoting that we're escaping the $ character so it will appear as an actual character in our final output
    3. $($obj) is used for expanding the value of $obj in the pipeline
    4. , is added at the end (since each parameter in a parameter declaration is comma-separated
    5. Finally, we're going to use the ToString() method on the $paramblock object, remove the last comma character and replace it with a closing parenthesis and a newline character.

woot! Almost done!

Building a new hash table containing the splattable parameters

Now that we've gotten the parameter block created as a string in memory, we're going to hit the next part.  We need to evaluate the parameters that were sent to our script.  We can perform any additional validation on them, formatting, etc later--I just want to get the list of parameters we're going to pass.  That's where the magic of PSBoundParameters comes in.

Sidebar, if you haven't looked at it before, $PSBoundParameters is an amazing automatic variable.  Basically, it lists every parameter that was passed in the command line.  I digress.  Consider this function:

 function charcuterie
     {
          param (
               $cheese,
               $jam,
               $meat
          )
          $PSBoundParameters
     }

As you can see, when I run foo -cheese brie -jam fig -meat pepperone, $PSBoundParameters executes as part of the function to display the hash table of values passed to function. How cool is that? And now how do I use it?

Oh, quite simple.  We can iterate through those passed parameters using a loop combined with the GetEnumerator() method.  Clear as mud?  Here we go:

 $params = @{}
     foreach ($parameter in $PSBoundParameters.GetEnumerator())
     {
          if ($parameter)
          {
               $params.Add($parameter.Key, $parameter.Value)
          }
     }

When we do this, we're looping through every key in $PSBoundParameters, and then, if it has a value (duh, it should, since we passed it), we're going to add it the $params hash table that we can later splat to the command.

And, to store this as a text block that we're eventually going to write out, we can do this:

 $foreachblock = New-Object System.Text.StringBuilder
[void]$foreachblock.AppendLine("`$params = @{ }") 
[void]$foreachblock.AppendLine("foreach (`$parameter in `$PSBoundParameters.GetEnumerator())") 
[void]$foreachblock.AppendLine("{") 
[void]$foreachblock.AppendLine("`tif (`$parameter)") 
[void]$foreachblock.AppendLine("`t{") 
[void]$foreachblock.AppendLine("`t`t`$params.Add(`$parameter.Key, `$parameter.Value)") 
[void]$foreachblock.AppendLine("`t}") 
[void]$foreachblock.AppendLine("}")

If you're quick, you'll notice I used the AppendLine method (instead of Append() that I'd used before).  Using AppendLine automatically inserts the newline character.  Since I needed to trim off the trailing "," in the previous block, I used the Append() method with a leading newline.

Based on what we just did above, I'll let you decipher it.  If you need help, let me know in the comments.

Output to file

We've reached the end of the road.

No, not that one.

The final step is to build the splat command (in the form of <command> @params) and write the final output to a text file.

One more Stringbuilder object, combine them all together, and Out-File that like it's my job:

 $splatblock = New-Object System.Text.StringBuilder
[void]$splatblock.AppendLine("$($Command) @params")

$finalblock = $paramblock + $foreachblock.ToString() + $splatblock.ToString()
$finalblock | Out-File $OutputFile -Force

Combine all those pieces, and then run it with a command of your choice.  I chose a one with a small parameter list (Remove-Item) to show you how the completed project works.

You can now edit that text file, copy and paste it into a new script, or print it out and show your mom.  What I do is some evaluation to further evaluation (for example, if a parameter is specified but not one of the values that I want to pass to the final splatted command, I may remove it or tweak its value).

You can pick up the final script over on the TechNet Gallery, if you so desire.