Automatically Create Custom PowerShell Functions

Automatically Create Custom PowerShell Functions

  • Comments 1
  • Likes

 

Summary: Learn how to automatically create custom Windows PowerShell functions by using a free tool that writes your code for you.

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I wish there was a good way to modify Windows PowerShell commands. I can modify the names of commands by creating an alias for the command. I wish I could create an alias that also modified the way the command runs. I guess I would have to create some sort of function, but that tends to be an awful lot of work. Is there an easier way to do this?

-- MB

 

Hey, Scripting Guy! Answer

Hello MB,

Microsoft Scripting Guy Ed Wilson here. James Brundage has been talking to me about his New-ScriptCmdlet function since he wrote it and sent me a copy back when I was working on all the Windows PowerShell scripts for the Windows 7 Resource Kit. It was so cool, he decided to include it in the PowerShellPack (the PowerShellPack was so cool, I decided to include that in the Windows 7 Resource Kit). MB, your question will provide James a perfect opportunity to talk about the New-ScriptCmdlet function.

James Brundage is the founder of Start-Automating (http://www.start-automating.com), a company dedicated to saving people time and money by helping them automate. Start-Automating offers Windows PowerShell training and custom Windows PowerShell and .NET Framework development. James formerly worked on the Windows PowerShell team at Microsoft, and he is the author of the PowerShellPack (http://code.msdn.microsoft.com/PowerShellPack ), a collection of Windows PowerShell scripts to enable building user interfaces, interact with RSS feeds, get system information, and more.


A Series on Splatting (and Stuff)

Part 2 – Wrapping a Command with Splatting

Welcome back to A Series on Splatting (and Stuff). In yesterday’s blog post, I covered the basics of what splatting was, and showed you how to use it. We created some handy shortcuts for common WMI queries in yesterday’s post. Here is one of those examples to refresh your memory:

$ComputerSystem = @{Query="Select * From Win32_ComputerSystem"}
Get-WmiObject @ComputerSystem

Now we’ll put that to practical use by building some wrapper functions, which call other functions. In this post, we’ll learn how to take the Get-WmiObject example and turn it into a Get-ComputerSystem command, while still keeping all of the Get-WmiObject command parameters like such as -AsJob, -ComputerName, and -Credential.

Building wrapper functions involves using splatting and using a built-in variable that’s there to help enable proxy commands. Proxy commands are the type of scripts you deal with if you ever use implicit remoting (see Export-PSSession). Implicit remoting gives you a command that you interact with as if it were running locally, but really runs against a remote machine. An example might be running Get-Process, but really returning the processes on machine X. In order for this to work, the local command has to be able to pass all of its parameters perfectly down to the remote command. In order to enable this, a variable was introduced in Windows PowerShell version 2.0 called $psBoundParameters.

The Microsoft Scripting Guys have written a number of Hey, Scripting Guy! Blog posts that discuss remoting. They have even had a couple of guest blogger posts on the subject. Needless to say, remoting is cool!

$psBoundParameters contains all of the parameters that were passed to the function. It does not contain default values (don’t forget this fact; we’ll keep coming back to it). If you use $psBoundParameters with splatting, you can basically make a function that simply calls another, but preserves all of the capabilities of the underlying function.

Let’s see how $psBoundParameters works in isolation, by making a small function:

function foo($a, $b, $c = "d") {
   
$psBoundParameters
}

When we call foo with no parameters, it returns nothing:

foo

When we call foo with values for $a, $b, or $c, it contains the data:

foo -a "a" -b "b" -c "c"

It doesn’t' matter if the value for $a, $b, or $c was supplied explicitly. In this simple function, a, b, and c can also be supplied by position, and $psBoundParameters will still work:

foo a b c

So how can we use this with splatting? Well, let's go back to the WMI example. In our example, we're only providing one parameter for Get-WmiObject. By only providing the query, I can still use any of the other parameters Get-WmiObject has:

Get-WmiObject @ComputerSystem -ComputerName "localhost"

If I want to build a function that just gets me the computer system information, but still allows me to use other parameters such as -ComputerName and -AsJob, I can write a function that wraps Get-WmiObject, but omits the query parameter (and anything else that could confuse the –Query parameter, such as –Class and –Namespace).

There’s a nifty function in the PowerShellPack called New-ScriptCmdlet that will make it easier to write this sort of simple wrapper.

To use the New-ScriptCmdlet function, you need to download the PowerShell pack, install the module, and import the module into your current Windows PowerShell session. To import the module, use the command Import-Module PowerShellPack.

The New-ScriptCmdlet function requires the name of the command you're going to create, the command it's based on, the parameters you want to remove from the original command, and the process block. It then produces a new advanced function (script cmdlet was the name for advanced function that was used in the beta of Windows PowerShell 2.0) that will wrap the original one. The use of the New-ScriptCmdlet function is seen here:

$newScriptCmdletParameters = @{
   
Name = "Get-ComputerSystem"
   
FromCommand = Get-Command Get-WmiObject
   
RemoveParameter = "Namespace", "Class", "Query"
   
ProcessBlock = {
       
$getWmiObjectParameters = $psBoundParameters + @{
           
Query="Select * From Win32_ComputerSystem"
       
}
       
Get-WmiObject @GetWmiObjectParameters
   
}
}

New-ScriptCmdlet @newScriptCmdletParameters

That's pretty nifty, isn't it? A really astoundingly small amount of code makes a function that's much bigger. Let's make a quick pipeline to figure out how much code is created.

The pipeline is a fun Windows PowerShell learning experience in and of itself. I could write the whole pipeline in several lines, but it’s always an interesting Windows PowerShell exercise to make a pure pipeline. To store the output of New-ScriptCmdlet into a variable, I pipe the result into Set-Variable. I add a –PassThru to return back the result of Set-Variable. Because Set-Variable returns a variable, and I want to measure the words and lines in that variable, I use Select-Object –ExpandProperty Value to get back to the value. Then I pipe this into Measure-Object –Word –Line –Character to see how much text was generated:

$newScriptCmdletParameters = @{
   
Name = "Get-ComputerSystem"
   
FromCommand = Get-Command Get-WmiObject
   
RemoveParameter = "Namespace", "Class", "Query"
   
ProcessBlock = {
       
$getWmiObjectParameters = $psBoundParameters + @{
           
Query="Select * From Win32_ComputerSystem"
       
}
       
Get-WmiObject @GetWmiObjectParameters
   
}
}

New-ScriptCmdlet @newScriptCmdletParameters |
   
Set-Variable -Name GetComputerSystem -PassThru |
   
Select-Object -ExpandProperty Value |
   
Measure-Object -Word -Line -Character

Wow! About 15 lines generated over 100 lines. That’s a time saver.

New-ScriptCmdlet produces text, so if I want to turn this into a function I'm going to use, I can do one of two things. I can either pipe it into Set-Content and then dot-source the file to import the function, or I can declare a script block that contains the data and dot-source that script block. Because more readers are probably familiar with the first approach and not with the second, I'll show both.

The previous example used the Set-Variable cmdlet to create a variable named $GetComputerSystem. Here’s an example of outputting the contents of that variable to a Windows PowerShell script file, dot-sourcing that file, and then using the newly imported Get-ComputerSystem function:

$GetComputerSystem | 
   
Set-Content .\Get-ComputerSystem.ps1

. .\Get-ComputerSystem.ps1

Get-ComputerSystem

 

Here’s an example of dynamically creating a function by using the create method from the scriptblock class is illustrated here. The scriptblock class is contained in the System.Management.Automation .NET Framework namespace. Luckily, the Windows PowerShell team created the [scriptblock] type accelerator to make it easy to use this class:

. ([ScriptBlock]::Create($GetComputerSystem))
Get-ComputerSystem

As you can see in the following image, the output looks just like Get-WmiObject Win32_ComputerSystem (because it really is), but you can now refer to it by a more convenient command.

Image of output

You can make simple wrappers like this for all sorts of cases. You can use either technique for creating the function you’d like. The dynamic technique is great to know when you just want to play around, but if you used it on a production machine, you’d need to have New-ScriptCmdlet loaded, so I tend to use the first technique when I’m working in a production environment.

Great! Now you know how to wrap commands. Let’s do one more for performance counters, just to recap:

# Create a set of parameters for New-ScriptCmdlet 
$newScriptCmdletParameters = @{
   
# The name of the command will be Watch-ProcessorPerformance
   
Name = "Watch-ProcessorPerformance"                   
   
# It will be based off of Get-Counter
   
FromCommand = Get-Command Get-Counter
   
# We'll remove the ListSet parameter and the Counter Parameter
    RemoveParameter = "ListSet", "Counter"
    # The process block of the commands will simply call Get-Counter with the parameters
    ProcessBlock = {
        $getCounterParameters= $psBoundParameters + @{
            Counter='
\Processor(_total)\% Processor Time'
        }
        Get-Counter @getCounterParameters
    }
}

# Run New-ScriptCmdlet and save the value into a variable
New-ScriptCmdlet @newScriptCmdletParameters |
    Set-Variable -Name GetCounter
    
# Create a script block with that variable and dot-source it (import it)
. ([ScriptBlock]::Create($GetCounter))

# Call the Watch-ProcessorPerformance command
Watch-ProcessorPerformance -MaxSamples 3

You can get a lot more complicated with the information you try to get from WMI or the performance counters, but this technique will help you anytime you want to go and build up some commands that wrap other commands.

Splatting (and the stuff related to splatting) opens up an incredibly large number of doors because it’s one of the best ways to approach taking myriad general purpose cmdlets and turning them into the specific tool you need. From there on it, it’s a lot easier to discover and use. I know that I have an easier time remembering something like Watch-ProcessorPerformance than something like Get-Counter ‘\Processor(_total)\% Processor Time’.

Now you know how you can build a convenient representation of a single cmdlet. In the next article, we’ll cover building a bridge between two commands.

MB, that is all there is to using splatting to wrap more complicated Windows PowerShell commands. Guest Blogger Week will continue tomorrow when James will talk about splatting and command metadata.

We invite you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • watch processor performance is very good and it is more benefit to use.