PowerShell advanced functions provide modularity in automating system administration tasks. Advanced functions allow administrators to create reusable code snippets that can look and behave just like the in-box PowerShell cmdlets. In this article, I will show you how to structure an advanced function to transform your scripts and functions into reusable tools.

Start with a Script

The first step in modularizing your PowerShell scripts is to turn a script into a function. Scripts can easily be turned into functions by adding a function keyword and a parameter block. For example, consider the following script. It grabs some information from the WMI database on the local computer and displays it in a table format to the console.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $Dev = get -CimInstance -class Win32_OperatingSystem | ` Select-Object -expandproperty SystemDevice $Disk = Get-CimInstance -Class Win32_LogicalDisk ` -filter "drivetype='3'" | Select-Object -Property DeviceID , Size , FreeSpace $props = @ { "HardDisk" = $dev "DeviceID" = $Disk . DeviceID "DiskSize" = $Disk . Size "FreeSpace" = $Disk . FreeSpace } $obj = New-Object -TypeName PSObject -Property $props $obj | Format-Table -AutoSize

Turn the Script Into a Function

Next, changing the script into a function is easily accomplished by adding a function keyword on the first line and enclosing the entire script in curly braces. Adding a $ComputerName parameter in a param block will allow the function to be run against remote machines, instead of just the local machine. The get-CimInstance allows a -ComputerName parameter.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function get -myDiskInfo { param ( [ string ] $ComputerName ) $Dev = get -CimInstance -class Win32_OperatingSystem ` -computerName $Computername | ` Select-Object -expandproperty SystemDevice $Disk = Get-CimInstance -Class Win32_LogicalDisk ` computerName $ComputerName -filter "drivetype='3'" | Select-Object -Property DeviceID , Size , FreeSpace $props = @ { "HardDisk" = $dev "DeviceID" = $Disk . DeviceID "DiskSize" = $Disk . Size "FreeSpace" = $Disk . FreeSpace } $obj = New-Object -TypeName PSObject -Property $props $obj | Format-Table -AutoSize }

Break Down the Function

Breaking down your code may mean chopping apart your lengthy script into smaller pieces. As a best practice, a function should do only one thing. A retrieval cmdlet retrieves information and sends that information to the pipeline. Conversely, a functional cmdlet performs an act but not a retrieval act. It may take input from another cmdlet and act upon that input. It may or may not send output information to the pipeline. Lastly, output cmdlets format output in a desired display. As a result, this will allow us to use the pipeline more effectively to pass parameters between functions. In the above example, most of the function is a retrieval function. The exception is that it formats the output into a table with the last line. I will remove that line and let the user of the function decide how they want it formatted.

From Function to Advanced Function in One Easy Step

Now that we have turned the script into a function and pared down what the function does, it is time to turn the function into an advanced function. Simply add the cmdletbinding declaration on the second line. It will go between the function declaration and the param block. Now that the function is defined as an advanced function, you need to structure the rest of the function into an advanced function as well.



1 [ cmdletbinding ( ) ]

Begin, Process, End

An advanced function is comprised of three separate blocks. A begin block contains all the code that is needed to execute at the beginning of the function. Use this block for creating log files or needed variables. The end block executes at the end of the function. Use this block for cleanup at the end of the function. The process block contains the meat of the function. This contains the cmdlets that “do” whatever it is that the function is responsible for doing.

The Process Block

The process block behaves differently for pipeline input than for parameter input. A value passed by parameter name will be processed only once in the process block. However, if a value or set of values gets passed via the pipeline, the process block will execute once for each item on the pipeline. Consider the following example:



1 2 3 4 5 6 7 8 9 10 11 12 function foo { [ cmdletbinding ( ) ] Param ( [ parameter ( ValueFromPipeline = $True ) ] [ string ] $Name ) Begin { } Process { write-verbose $Name } End { } }



$Name only accepts a single value. Attempting to pass more than one value by parameter name, will result in an error.

However, if you pass a collection of values across the pipeline, then the process block will run once for each value.

Handling Parameter Input by Pipeline and by Parameter Name

To handle multiple values passed both by parameter and by pipeline, you need to add a foreach loop to accommodate the scenario that failed above. The following code shows the same function. Only this time, it accepts both named parameter input and pipeline input. With $Name now declared as multiple strings and the process block including a foreach loop, this function can be called using both parameter and pipeline input with multiple values.





1 2 3 4 5 6 7 8 9 10 11 12 13 14 function foo { [ cmdletbinding ( ) ] Param ( [ parameter ( ValueFromPipeline = $True ) ] [ string [ ] ] $Name ) Begin { } Process { foreach ( $N in $Name ) { write-verbose $N } } End { } }

Advanced Function Structure Is Established!

In conclusion, once your script or function turns into an advanced function, now you can take advantage of the additional benefits that an advanced function offers. First, you can use parameter validation right in the parameter declaration to ensure that the parameter being input is exactly what is needed. This helps in avoiding having to write excess code to check for parameter checking. In addition, you can also use advanced functions’ methods, such as error methods and write methods. Lastly, you could also support for the -whatif and -confirm parameters for your advanced function. As a result, your home-grown cmdlets can behave like compiled cmdlets once the advanced function structure has been created.