Working with Functions in Windows PowerShell

  • 11/11/2015

Using functions to provide ease of modification

It is a truism that a script is never completed. There is always something else to add to a script—a change that will improve it, or additional functionality that someone requests. When a script is written as one long piece of inline code, without recourse to functions, it can be rather tedious and error-prone to modify.

An example of an inline script is the InLineGetIPDemo.ps1 script. The first line of code uses the Get-WmiObject cmdlet to retrieve the instances of the Win32_NetworkAdapterConfiguration WMI class that IP enabled. The results of this WMI query are stored in the $IP variable. This line of code is shown here.

$IP = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"

When the WMI information has been obtained and stored, the remainder of the script prints information to the screen. The IPAddress, IPSubNet, and DNSServerSearchOrder properties are all stored in an array. For this example, you are only interested in the first IP address, and you therefore print element 0, which will always exist if the network adapter has an IP address. This section of the script is shown here.

"IP Address: " + $IP.IPAddress[0]
"Subnet: " + $IP.IPSubNet[0]
"GateWay: " + $IP.DefaultIPGateway
"DNS Server: " + $IP.DNSServerSearchOrder[0]
"FQDN: " + $IP.DNSHostName + "." + $IP.DNSDomain

When the script is run, it produces output similar to the following.

IP Address: 192.168.2.5
Subnet: 255.255.255.0
GateWay: 192.168.2.1
DNS Server: 192.168.2.1
FQDN: w8client1.nwtraders.com

The complete InLineGetIPDemo.ps1 script is shown here.

InLineGetIPDemo.ps1

$IP = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"
"IP Address: " + $IP.IPAddress[0]
"Subnet: " + $IP.IPSubNet[0]
"GateWay: " + $IP.DefaultIPGateway
"DNS Server: " + $IP.DNSServerSearchOrder[0]
"FQDN: " + $IP.DNSHostName + "." + $IP.DNSDomain

With just a few modifications to the script, a great deal of flexibility can be obtained. The modifications, of course, involve moving the inline code into functions. As a best practice, a function should be narrowly defined and should encapsulate a single thought. Though it would be possible to move the entire previous script into a function, you would not have as much flexibility. There are two thoughts or ideas that are expressed in the script. The first is obtaining the IP information from WMI, and the second is formatting and displaying the IP information. It would be best to separate the gathering and the displaying processes from one another, because they are logically two different activities.

To convert the InLineGetIPDemo.ps1 script into a script that uses a function, you only need to add the Function keyword, give the function a name, and surround the original code with a pair of braces. The transformed script is now named GetIPDemoSingleFunction.ps1 and is shown here.

GetIPDemoSingleFunction.ps1

Function Get-IPDemo
{
 $IP = Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"
 "IP Address: " + $IP.IPAddress[0]
 "Subnet: " + $IP.IPSubNet[0]
 "GateWay: " + $IP.DefaultIPGateway
 "DNS Server: " + $IP.DNSServerSearchOrder[0]
 "FQDN: " + $IP.DNSHostName + "." + $IP.DNSDomain
} #end Get-IPDemo

# *** Entry Point To Script ***

Get-IPDemo

If you go to all the trouble to transform the inline code into a function, what benefit do you derive? By making this single change, your code will become

  • Easier to read
  • Easier to understand
  • Easier to reuse
  • Easier to troubleshoot

The script is easier to read because you do not really need to read each line of code to understand what it does. You can tell that there is a function that obtains the IP address, and it is called from outside the function. That is all the script does.

The script is easier to understand because you can tell there is a function that obtains the IP address. If you want to know the details of that operation, you read that function. If you are not interested in the details, you can skip that portion of the code.

The script is easier to reuse because you can dot-source the script, as shown here. When the script is dot-sourced, all the executable code in the script is run.

As a result, because each of the scripts prints information, the following is displayed.

IP Address: 192.168.2.5
Subnet: 255.255.255.0
GateWay: 192.168.2.1
DNS Server: 192.168.2.1
FQDN: C10.nwtraders.com


 C10 free disk space on drive C:
    48,767.16 MegaBytes


This OS is version 10.0

The DotSourceScripts.ps1 script is shown following. As you can tell, it provides you with a certain level of flexibility to choose the information required, and it also makes it easy to mix and match the required information. If each of the scripts had been written in a more standard fashion, and the output had been more standardized, the results would have been more impressive. As it is, three lines of code produce an exceptional amount of useful output that could be acceptable in a variety of situations.

DotSourceScripts.ps1

. C:\Scripts\GetIPDemoSingleFunction.ps1
. C:\Scripts\Get-FreeDiskSpace.ps1
. C:\Scripts\Get-OperatingSystemVersion.ps1

A better way to work with the function is to think about the things the function is actually doing. In the FunctionGetIPDemo.ps1 script, there are two functions. The first connects to WMI, which returns a management object. The second function formats the output. These are two completely unrelated tasks. The first task is data gathering, and the second task is the presentation of the information. The FunctionGetIPDemo.ps1 script is shown here.

FunctionGetIPDemo.ps1

Function Get-IPObject
{
 Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"
} #end Get-IPObject

Function Format-IPOutput($IP)
{
 "IP Address: " + $IP.IPAddress[0]
 "Subnet: " + $IP.IPSubNet[0]
 "GateWay: " + $IP.DefaultIPGateway
 "DNS Server: " + $IP.DNSServerSearchOrder[0]
 "FQDN: " + $IP.DNSHostName + "." + $IP.DNSDomain
} #end Format-IPOutput

# *** Entry Point To Script

$ip = Get-IPObject
Format-IPOutput -ip $ip

By separating the data-gathering and the presentation activities into different functions, you gain additional flexibility. You could easily modify the Get-IPObject function to look for network adapters that were not IP enabled. To do this, you would need to modify the -Filter parameter of the Get-WmiObject cmdlet. Because most of the time you would actually be interested only in network adapters that are IP enabled, it would make sense to set the default value of the input parameter to $true. By default, the behavior of the revised function is exactly as it was prior to modification. The advantage is that you can now use the function and modify the objects returned by it. To do this, you supply $false when calling the function. This is illustrated in the Get-IPObjectDefaultEnabled.ps1 script.

Get-IPObjectDefaultEnabled.ps1

Function Get-IPObject([bool]$IPEnabled = $true)
{
 Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $IPEnabled"
} #end Get-IPObject

Get-IPObject -IPEnabled $False

By separating the gathering of the information from the presentation of the information, you gain flexibility not only in the type of information that is garnered, but also in the way the information is displayed. When you are gathering network adapter configuration information from a network adapter that is not enabled for IP, the results are not as impressive as for one that is enabled for IP. You might therefore decide to create a different display to list only the pertinent information. Because the function that displays the information is different from the one that gathers the information, a change can easily be made to customize the information that is most germane. The Begin section of the function is run once during the execution of the function. This is the perfect place to create a header for the output data. The Process section executes once for each item on the pipeline, which in this example will be each of the non-IP-enabled network adapters. The Write-Host cmdlet is used to easily write the data out to the Windows PowerShell console. The backtick-t character combination (`t) is used to produce a tab.

The Get-IPObjectDefaultEnabledFormatNonIPOutput.ps1 script is shown here.

Get-IPObjectDefaultEnabledFormatNonIPOutput.ps1

Function Get-IPObject([bool]$IPEnabled = $true)
{
 Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $IPEnabled"
} #end Get-IPObject

Function Format-NonIPOutput($IP)
{
  Begin { "Index #  Description" }
 Process {
  ForEach ($i in $ip)
  {
   Write-Host $i.Index `t $i.Description
  } #end ForEach
 } #end Process
} #end Format-NonIPOutPut

$ip = Get-IPObject -IPEnabled $False
Format-NonIPOutput($ip)

You can use the Get-IPObject function to retrieve the network adapter configuration, and you can use the Format-NonIPOutput and Format-IPOutput functions in a script to display the IP information as specifically formatted output, as shown in the CombinationFormatGetIPDemo.ps1 script shown here.

CombinationFormatGetIPDemo.ps1

Function Get-IPObject([bool]$IPEnabled = $true)
{
 Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $IPEnabled"
} #end Get-IPObject

Function Format-IPOutput($IP)
{
 "IP Address: " + $IP.IPAddress[0]
 "Subnet: " + $IP.IPSubNet[0]
 "GateWay: " + $IP.DefaultIPGateway
 "DNS Server: " + $IP.DNSServerSearchOrder[0]
 "FQDN: " + $IP.DNSHostName + "." + $IP.DNSDomain
} #end Format-IPOutput

Function Format-NonIPOutput($IP)
{
  Begin { "Index #  Description" }
 Process {
  ForEach ($i in $ip)
  {
   Write-Host $i.Index `t $i.Description
  } #end ForEach
 } #end Process
} #end Format-NonIPOutPut

# *** Entry Point ***
$IPEnabled = $false
$ip = Get-IPObject -IPEnabled $IPEnabled
If($IPEnabled) { Format-IPOutput($ip) }
ELSE { Format-NonIPOutput($ip) }