2015/03/30

Standard and Advanced PowerShell functions

This post is part of the PowerShell Blogging Week (#PSBlogWeek), a series of coordinated posts designed to provide a comprehensive view of a particular topic.

This week topic will be focused on Windows PowerShell Advanced Functions.


In this post, we'll discuss Standard and Advanced Functions and why you should write advanced functions.


When you have been working with PowerShell for some time, creating reusable tools is an obvious evolution to avoid writing the same code over and over again. You will want to have modular pieces of code that only do one job and do it well - that’s the role of functions.

Let's suppose you have to accomplish a task that requires multiple lines of code, for example:

# Computer System
Get-WmiObject -Class Win32_ComputerSystem
# Operating System
Get-WmiObject -class win32_OperatingSystem
# BIOS
Get-WmiObject -class Win32_BIOS



Standard Function


A function is a list of statements wrapped into a scriptblock. A function has a name that you assign. You run those statements by simply typing the function name.

We can take the code above and wrap it into a function that we will call Get-ComputerInformation

Function Get-ComputerInformation
{
    # Computer System
    Get-WmiObject -Class Win32_ComputerSystem
    # Operating System
    Get-WmiObject -class win32_OperatingSystem
    # BIOS
    Get-WmiObject -class win32_BIOS
}

It can be used this way:


Now we can make our function more versatile by including a parameter that accepts different computer names. In the following example I'm adding the parameter $ComputerName and some extra code on the WMI queries to pass the machine name.

For the Output, I'm creating a new powershell object to only return some selected information.

Function Get-ComputerInformation
{
    PARAM ($ComputerName)
    # Computer System
    $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName
    # Operating System
    $OperatingSystem = Get-WmiObject -class win32_OperatingSystem -ComputerName $ComputerName
    # BIOS
    $Bios = Get-WmiObject -class win32_BIOS -ComputerName $ComputerName
    
    # Prepare Output
    $Properties = @{
        ComputerName = $ComputerName
        Manufacturer = $ComputerSystem.Manufacturer
        Model = $ComputerSystem.Model
        OperatingSystem = $OperatingSystem.Caption
        OperatingSystemVersion = $OperatingSystem.Version
        SerialNumber = $Bios.SerialNumber
    }
    
    # Output Information
    New-Object -TypeName PSobject -Property $Properties
    
}




We created a very simple and nice tool that can query different machines by editing the ComputerName parameter. What can we do to make this tool more efficient?


Advanced Function


Advanced functions allow you to write functions that can act like cmdlets. This means that you can make your functions more robust, handle errors, support Verbose, Debug, Dynamic Parameters, Validate input, … just to name a few.

Those features would be typically available with compiled cmdlet using a Microsoft .NET Framework language (for example with C#). However, Advanced Functions make it simple and are written in Windows PowerShell in the same way that other functions or script blocks are written.

How do I make a function advanced?
Pretty simple, all you need is the attribute CmdletBinding.
Note: You can also use the [Parameter()] attribute to make it advanced, but for this example I'll stick with CmdletBinding.


Let’s apply this to our function.

Function Get-ComputerInformation
{
    [CmdletBinding()]
    PARAM ($ComputerName)
    # Computer System
    $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName
    # Operating System
    $OperatingSystem = Get-WmiObject -Class win32_OperatingSystem -ComputerName $ComputerName
    # BIOS
    $Bios = Get-WmiObject -class win32_BIOS -ComputerName $ComputerName
    
    # Prepare Output
    $Properties = @{
        ComputerName = $ComputerName
        Manufacturer = $ComputerSystem.Manufacturer
        Model = $ComputerSystem.Model
        OperatingSystem = $OperatingSystem.Caption
        OperatingSystemVersion = $OperatingSystem.Version
        SerialNumber = $Bios.SerialNumber
    }
    
    # Output Information
    New-Object -TypeName PSobject -Property $Properties
    
}


That's it! This is all you need to make an Advanced Function.

If you take a look at the parameters available with and without the CmdletBinding attribute, you’ll be surprised by all the greatness this little word enables to our function.

Standard Function (Without CmdletBinding)


Advanced Function (With CmdletBinding)
The common parameters are available with any cmdlet and on advanced functions that use the CmdletBinding attribute or the Parameter attribute. They can, for example, help you handle different types of error, warnings or show some programmer-level details about the operation performed.

I won’t go into too much detail about those, you can check this article about_CommonParameters for more information.

Why should you use Advanced Function over the Standard?

Standard functions are great for simple tasks that will make you save lines of code or as “helpers” for another advanced function.

If you plan to create a tool that needs to work in many scenarios such as inside a pipeline, to validate the data passed to its parameters, to handles errors, to be compatible with –confirm and –whatif switches, to show verbose messages, … or if you simply plan to share and add this function into a module, then Advanced function is the way to go.

As we saw earlier, making your function “Advanced” is really simple and adds some really great features.

Using those useful features can help you create a really strong reusable tool.



Accept Pipeline Input and Verbose message

As a final example, here is how you can simply make your advanced function accept input from the pipeline and show some verbose messages to keep track of your function’s progress.

Adding support for pipeline can be done by adding the static parameter “ValueFromPipeline” inside the Parameter attribute: [Parameter(ValueFromPipeline)]. In my example I added this on the parameter we defined ComputerName.

Verbose messages are available using the Write-Verbose cmdlet. Remember that you will need to use the switch –verbose when you call your function to show those messages.


Function Get-ComputerInformation
{
    [CmdletBinding()]
    PARAM (
        [Parameter(ValueFromPipeline)]
        $ComputerName = $env:COMPUTERNAME
    )
    PROCESS
    {
        Write-Verbose -Message "$ComputerName"
        
        # Computer System
        $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName
        # Operating System
        $OperatingSystem = Get-WmiObject -class win32_OperatingSystem -ComputerName $ComputerName
        # BIOS
        $Bios = Get-WmiObject -class win32_BIOS -ComputerName $ComputerName
        
        # Prepare Output
        Write-Verbose -Message "$ComputerName - Preparing output"
        $Properties = @{
            ComputerName = $ComputerName
            Manufacturer = $ComputerSystem.Manufacturer
            Model = $ComputerSystem.Model
            OperatingSystem = $OperatingSystem.Caption
            OperatingSystemVersion = $OperatingSystem.Version
            SerialNumber = $Bios.SerialNumber
        } #Properties
        
        # Output Information
        Write-Verbose -Message "$ComputerName - Output Information"
        New-Object -TypeName PSobject -Property $Properties
    } #PROCESS
} #Function



In this example, I'm loading a list of machines inside the text file computers.txt. Those machines are passed to the parameter "ComputerName". I also used the verbose switch which lets me follow the sequence of my tool.




Resources on Advanced Functions

Here are some great resources if you want to learn more on PowerShell Functions:

PowerShell Blogging Week


Follow the hashtag #PSBlogWeek on twitter and make sure to follow the contributors below :-)

Name
Twitter
Blog
Adam Bertram @adbertam http://www.adamtheautomator.com/
Boe Prox @proxb http://learn-powershell.net/
Francois-Xavier Cat @lazywinadm http://www.lazywinadmin.com/
Jeffery Hicks @jeffhicks http://jdhitsolutions.com/blog/
June Blender @juneb_get_help http://www.sapien.com/blog/
Mike F. Robbins @mikefrobbins http://mikefrobbins.com/



Thanks for reading! If you have any questions, leave a comment or send me an email at fxcat@lazywinadmin.com. I invite you to follow me on Twitter @lazywinadm / Google+ / LinkedIn. You can also follow the LazyWinAdmin Blog on Facebook Page and Google+ Page.

4 comments:

  1. It is amazing! Thank you for sharing your knowledge!

    ReplyDelete
  2. Great post! My first time learning about [Parameter(ValueFromPipeline)] was from one of your functions Add-ADSubnet!

    Learning how to use [CMDletBinding()] definitely gives your script that PowerShell finishing touch! -Verbose and Write-Verbose saved many puppies!!!

    ReplyDelete
  3. Thanks Irwin, really appreciate your comment :-)
    Really happy my posts could help you learn something about PowerShell !!
    Let me know if you have questions.

    See also the final small ebook that we released with June, Boe, Adam and Jeff
    http://www.lazywinadmin.com/2015/04/psblogweek-on-advanced-functions-free.html

    ReplyDelete