Summary: Guest blogger, Microsoft MVP, Adam Bertram, talks about advanced Windows PowerShell functions.
Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest post by Microsoft MVP, Adam Bertram. Take it away Adam…
I'm a Pluralsight author and I develop online training courses that are mostly about Windows PowerShell. I recently had the opportunity to develop a course about building advanced PowerShell functions and modules, and a big topic in that course was about advanced functions.
With that topic fresh on my mind, I thought how to build advanced functions would make a great post for the Hey, Scripting Guy! Blog. So without further ado, let's start taking your PowerShell skills to the next level by going from basic functions to advanced functions!
In the PowerShell world, there are two kinds of functions: basic and advanced. If you're starting with PowerShell, you're probably using basic functions. This kind of function is what you'd typically think of with a traditional programming language function. It's simply a group of code that can be executed autonomously with optional input parameters and an optional output. In essence, basic functions are a great way to prevent having to duplicate your code by building a small framework of code that you can simply point to and execute.
For example, a basic function in PowerShell might look something like this:
Function Get-Something {
Param($item)
Write-Host "You passed the parameter $item into the function"
}
This is an extremely simple example of a basic function with a single parameter called $item. This function will output a statement to the console replacing $item with the runtime value that $item represents. If I call this function by doing something like this:
PS> Get-Something –item 'abc123'
I would get an output that looks like this:
PS> You passed the parameter abc123 into the function
This is the basic premise of a function. It's simply a group of code that can be called. However, there's another kind of function in PowerShell that is called "advanced." Advanced functions inherit all the functionality of basic functions, but they give you so much more functionality.
Advanced is the concept of cmdlets. When learning PowerShell, you might see the word cmdlet generically tossed around to describe any of the various commands that you can execute in your console, for example: Get-Content, Get-Service, or Test-Connection. Sometimes you'll see people downloading PS1 scripts or modules from the Internet, running the functions, and referring to them as cmdlets—which isn't the correct terminology.
Cmdlets are not functions. They are separate entities. Cmdlets are created by software developers in languages other than PowerShell, such as C# or .NET. Cmdlets are then compiled into binary form, which allows us non-developers to use them. This is why there's no official Get-Content PS1 script or Test-Connection PS1 script. These cmdlets aren't simply plain-text files. In actuality, if you look into one, it would probably look something like this:
Aldjf;aliu0paouidjf klj*&(&*^&PDLJF:LKJ:LDKFMKM"J"JKDF
You get the point—plus I almost accidently lost my work randomly hitting keys.
Cmdlets consist of compiled, machine-readable code. Functions, on the other hand, are expressed in PowerShell and mere mortals can write and read them in a simple plain-text editor. Why am I talking this much about cmdlets when this is an article about advanced functions? It's because whenever you make an advanced function, it inherits all of the capabilities of those compiled cmdlets.
For example, with any of the compiled cmdlets, you have the ability to accept pipeline input, validate parameters, and use any of the default parameters, for example –Verbose, -ErrorAction, or -WarningVariable. Advanced functions, like cmdlets, have a lot of built-in functionality that you don't get with a simple basic function.
To make a function "advanced" is easier than you can probably imagine. We simply need to append a simple keyword reference called [CmdletBinding()] under the function declaration:
Function Get-Something {
[CmdletBinding()]
Param($item)
Write-Host "You passed the parameter $item into the function"
}
Voila! Our basic function has advanced, and it is now all grown up. I think I just shed a tear. They grow up so fast, don't they? Anyway, Get-Something is now an advanced function. So what? What does that give me that I didn't have before? The answer is, “A ton!”
Making your function advanced opens up a whole new world of options. This allows you to build all the functionality of the cmdlets (which you probably used previously) into your own home-grown functions.
For example, maybe I want to only output verbose messages to the console for logging purposes if I use the built-in –Verbose parameter. If I try to create a basic function without the CmdletBinding() keyword and append –Verbose to Get-Something, it does absolutely nothing. This is because a basic function doesn't have any of the built-in parameters that advanced functions and cmdlets do.
Let's change my previous reference of Write-Host to Write-Verbose and try it now:
Function Get-Something {
[CmdletBinding()]
Param($item)
Write-Verbose "You passed the parameter $item into the function"
}
PS> Get-Something –Verbose
PS> VERBOSE: You passed the parameter $item into the function
You'll see that the function now understands verbose output. This is also true for Write-Warning and Write-Error. You now have the ability to output different streams to indicate various event severity levels in your function.
Have you ever wonder how those cmdlets that you've been stringing together work? Now you can find out by building functions to accept pipeline input. Perhaps you'd like your function to accept Windows service names directly from the pipeline and take that output into a $Name parameter:
Function Get-Something {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName)]
$Name
)
process {
Write-Host "You passed the parameter $Name into the function"
}
}
By changing the $item parameter to $Name, using the Parameter() keyword with the ValueFromPipelineByPropertyName parameter attribute, and adding a process block into the function, we can now send objects from Get-Service directly to Get-Something as we'd expect:
PS> Get-Service | Get-Something
PS> You passed the parameter service1 into the function
PS> You passed the parameter service2 into the function
PS> You passed the parameter service3 into the function
PS> You passed the parameter service4 into the function
Have you ever played it safe when running a sensitive command on various cmdlets and used the –WhatIf or –Confirm parameters? These parameters allow you to perform a "test run" of an advanced function or cmdlet to see what it would actually do if it ran.
For example, maybe I have a function that removes a bunch of files from my computer. I'd rather not risk removing some sensitive files, so I can now add a little bit of code to my function to account for the –WhatIf parameter:
Function Remove-Something {
[CmdletBinding(SupportsShouldProcess)]
Param(
[Parameter(ValueFromPipelineByPropertyName)]
$File
)
if ($PSCmdlet.ShouldProcess($File)) {
Remove-Item –Path $File –Confirm:$false
}
}
Notice that I had to add the SupportsShouldProcess string inside of the CmdletBinding() parentheses. I also added an If statement to differentiate if the –WhatIf parameter was used. However, notice that I don't have a –WhatIf parameter. I simply have a –WhatIf parameter. This isn't possible with a basic function. –WhatIf functionality is built-in to all advanced functions.
If you got some value from this information and would like to dive deeper into PowerShell, advanced functions, and modules, be sure to check out my Pluralsight course about building advanced PowerShell functions and modules. I take you from an introduction to advanced functions, and I explain in detail nearly every feature that you now have at your disposal.
~Adam
Thank you, Adam. That is a really helpful introduction. Join us tomorrow when Adam will talk about accepting pipeline input into Windows PowerShell functions. It is a really useful technique.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy