This week topic will be focused on Windows PowerShell Advanced Functions.
In this series:
In this post, we'll discuss Standard and Advanced Functions and why you should write advanced functions.- Standard and Advanced PowerShell Functions by François-Xavier Cat (@LazyWinAdm) (March 30, 2015)
- PowerShell Advanced Functions: Can we build them better? With parameter validation, yes we can! by Mike F. Robbins (@mikefrobbins) (March 31, 2015)
- Dynamic Parameters and Parameter Validation by Adam Bertram (@adbertram) (April 1, 2015)
- Supporting WhatIf and Confirm in Advanced Functions by Jeffery Hicks (@JeffHicks) (April 2, 2015)
- Advanced Help for Advanced Functions by June Blender (@juneb_get_help) (April 3, 2015)
- A Look at Try/Catch in PowerShell by Boe Prox (@proxb) (April 4, 2015)
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:- about_Functions
- about_Functions_Advanced
- about_Functions_CmdletBindingAttribute
- about_Functions_Advanced_Methods
- about_Functions_Advanced_Parameters
- about_Functions_OutputTypeAttribute
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.
It is amazing! Thank you for sharing your knowledge!
ReplyDeleteThanks Alexandr! I appreciate your feedback :-)
ReplyDeleteGreat post! My first time learning about [Parameter(ValueFromPipeline)] was from one of your functions Add-ADSubnet!
ReplyDeleteLearning how to use [CMDletBinding()] definitely gives your script that PowerShell finishing touch! -Verbose and Write-Verbose saved many puppies!!!
Thanks Irwin, really appreciate your comment :-)
ReplyDeleteReally 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