Putting production-quality functions together in Windows PowerShell
PowerShell MVP Brandon Shell reviews what it takes for a function to be truly “production-ready” and provides a complete example, called Test-Host.
Past articles have gone over the nuts and bolts of production-quality functions. Now we’ll put everything together...
Continue Reading This Article
Enjoy this article as well as all of our content, including E-Guides, news, tips and more.
and look at an example using the three components we’ve discussed: input, output and error handling.
Before we get into the example, let’s take a moment to recap what “production-quality” really means. These are functions that are meant to run in a production environment and thus require a higher level of quality. They are generally comprised of three main design criteria:
- Input -- how the function accepts data
- Error handling -- how the function deals with error conditions
- Output -- data the function returns
Below is an example function called Test-Host. I use this function often and it has to be reliable both in functionality and expected output. We can’t go through it line by line, but I want to take some time to cover the key parts.
-
Documentation -- You may notice the< #...# > section near the beginning of the function. This is called inline help, and it gives the function writer the ability to provide help to the consumer of the function.
-
Parameters -- The param() statement is where we set up the parameters (input) that the function takes. In this case I tested ComputerName, TCPPort, Timeout and Property.
-
Script Flow -- The Begin script block is used for script setup and the Process script block is used to process items (objects) as they are passed in.
-
Error Handling -- Try/Catch script blocks are used to execute a branch of code and provide some feedback if the code fails.
-
Output -- If it detects an object was passed in, it does the test. If it passes, it outputs the same object. If no object is passed in, it returns the same value of ComputerName.
Below is the full function. It takes input in the form of a parameter called ComputerName, but you can specify a specific property you want to test. Once the function determines what to test, it does a simple ping or creates a TCP connection to the given port.
function Test-Host
{
<#
.Synopsis
Test a host for connectivity using either WMI ping or TCP port
.Description
Allows you to test a host for connectivity before further processing
.Parameter Server
Name of the Server to Process.
.Parameter TCPPort
TCP Port to connect to. (default 135)
.Parameter Timeout
Timeout for the TCP connection (default 1 sec)
.Parameter Property
Name of the Property that contains the value to test.
.Example
cat ServerFile.txt | Test-Host | Invoke-DoSomething
Description
-----------
To test a list of hosts.
.Example
cat ServerFile.txt | Test-Host -tcp 80 | Invoke-
DoSomething
Description
-----------
To test a list of hosts against port 80.
.Example
Get-ADComputer | Test-Host -property dnsHostname | Invoke-DoSomething
Description
-----------
To test the output of Get-ADComputer using the dnshostname property
.OUTPUTS
System.Object
.INPUTS
System.String
.Link
Test-Port
NAME: Test-Host
AUTHOR: YetiCentral\bshell
Website: www.bsonposh.com
LASTEDIT: 02/04/2009 18:25:15
#Requires -Version 2.0
#>
[CmdletBinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,
Mandatory=$True)]
[string]$ComputerName,
[Parameter()]
[int]$TCPPort,
[Parameter()]
[int]$timeout=3000,
[Parameter()]
[string]$property
)
Begin
{
function PingServer
{
Param($MyHost)
$ErrorActionPreference = "SilentlyContinue"
Write-Verbose " [PingServer] :: Pinging [$MyHost]"
try
{
$pingresult = Get-WmiObject win32_pingstatus -f "address='$MyHost'"
$ResultCode = $pingresult.statuscode
Write-Verbose " [PingServer] :: Ping returned $ResultCode"
if($ResultCode -eq 0) {$true} else {$false}
}
catch
{
Write-Verbose " [PingServer] :: Ping Failed with Error: ${error[0]}"
$false
}
}
}
Process
{
Write-Verbose " [Test-Host] :: Begin Process"
if($ComputerName -match "(.*)(\$)$")
{
$ComputerName = $ComputerName -replace "(.*)(\$)$",'$1'
}
Write-Verbose " [Test-Host] :: ComputerName : $ComputerName"
if($TCPPort)
{
Write-Verbose " [Test-Host] :: Timeout : $timeout"
Write-Verbose " [Test-Host] :: Port : $TCPPort"
if($property)
{
Write-Verbose " [Test-Host] :: Property : $Property"
$Result = Test-Port $_.$property -tcp $TCPPort -timeout $timeout
if($Result)
{
if($_){ $_ }else{ $ComputerName }
}
}
else
{
Write-Verbose " [Test-Host] :: Running - 'Test-Port $ComputerName -tcp $TCPPort -timeout $timeout'"
$Result = Test-Port $ComputerName -tcp $TCPPort -timeout $timeout
if($Result)
{
if($_){ $_ }else{ $ComputerName }
}
}
}
else
{
if($property)
{
Write-Verbose " [Test-Host] :: Property : $Property"
try
{
if(PingServer $_.$property)
{
if($_){ $_ }else{ $ComputerName }
}
}
catch
{
Write-Verbose " [Test-Host] :: $($_.$property) Failed Ping"
}
}
else
{
Write-Verbose " [Test-Host] :: Simple Ping"
try
{
if(PingServer $ComputerName){$ComputerName}
}
catch
{
Write-Verbose " [Test-Host] :: $ComputerName Failed Ping"
}
}
}
Write-Verbose " [Test-Host] :: End Process"
}
}
Miss a column? Check out our Scripting School archive.
You can follow SearchWindowsServer.com on Twitter @WindowsTT.
ABOUT THE AUTHOR
Brandon Shell has been in the IT industry since 1994. He started out as a PC tech and general fix-it guy for numerous companies. In 2007, he joined the PowerShell MVP ranks, and Shell has spent the past several years building his PowerShell knowledge and helping others build theirs.
Start the conversation
0 comments