Powershell Simplified Part 7: Error Handling

Exception handling in Powershell is primarily based on try-catch-finally and trap. Let’s see some scenarios,

function throws-error { throw "an error occured here" }
throws-error        # unhandled exception - script will error out

If we want to catch an error, we can use the try-catch construct,

function throws-error {
    # we can throw a .NET exception too
    throw [System.IO.FileNotFoundException] "throwing a filenotfoundexception"
}

try { throws-error }
catch {
    # let's get some more information about the error
    write-host $_
    $_.GetType().FullName              # the type of $_ is 'System.Management.Automation.ErrorRecord'
    $_.Exception
    $_.Exception.GetType().FullName    # the exception type name is 'System.IO.FileNotFoundException'
    $_.Exception.Message               # the exception message 
}

Just like in C#, we can catch different types of exceptions with a single try. Order is important, catch the most specific exceptions first.

function throw-errors([int]$errorType) {
    switch ($errorType) {
        1 { throw [System.IO.FileNotFoundException] "throwing a filenotfoundexception" }
        2 { throw [System.IO.IOException]  "throwing a ioexception" }
        3 { throw [System.SystemException]  "throwing a systemexception" }
        default { throw [System.Exception] "a default error" }
    }
}

try { throw-errors 2 }
catch [System.IO.FileNotFoundException] { "caught filenotfoundexception" }    # catch the most specific exception first
catch [System.IO.IOException] { "caught ioexception" }
catch [System.SystemException] { "caught systemexception" } 
catch [System.Exception] { "caught exception" }                               # catch the most generic exception last
finally { "done" }

Every pre-defined Powershell cmdlet comes with built-in error handling via a common parameter called –ErrorAction. To see this is action try the commands below,

get-process –fileversion                                # you'll get a few errors in the output
get-process -fileversion -erroraction silentlycontinue  # ignore the errors
get-process -fileversion -ea 0                          # 'ea' is the alias for erroraction
get-process -fileversion -errorvariable errors          # get an array of errors that occured
write-host $errors.count                                # how many errors occured?
foreach ($error in $errors) { write-host "Error:" $error }

If you want to import the common parameters into your function then use the CmdletBinding.

function throws-error {
    [CmdletBinding()]              # our function now supports the ErrorAction param implicitly
    param()

    throw "an error occured here"
}

function throws-error2 {
    # ErrorAction actually sets the $ErrorActionPreference, and we can do this ourselves
    $ErrorActionPreference = "silentlycontinue" 

    throw "an error occured here"
}

throws-error -ea 0    # no error is thrown
throws-error2         # no error is thrown

Now let’s look at the other Powershell way of handling exceptions – trap. Traps handle the exception and allow script execution to continue.

trap [IO.FileNotFoundException] { "trapped outside " + $_.Exception; continue; }

function test-trap {
    # break results in re-throwing the exception, so it can be handled by the outer trap
    trap [IO.FileNotFoundException] { "trapped inside " + $_.Exception; break; }

    throw [IO.FileNotFoundException] "a filenotfoundexception"
    "will not output this line"
}

test-trap

The ‘will not output this line’ doesn’t get output because with ‘continue’, execution is returned to the scope the trap is in and the next command is executed. To see this more clearly,

function test-trap {
   trap { "outer " + $_.Exception; continue; }

   if($true) {
        trap { "inner " + $_.Exception; break; }

        throw "an error occured";
        "will not output this line"
   }

   "will output this line"
}

test-trap

We can also trap multiple exceptions, like catch blocks,

function test-trap {   
    throw [IO.FileNotFoundException] "a filenotfoundexception"
    throw (new-object IO.IOException("an ioexception"))        # this is executed because of the 'continue' in the filenotfoundexception trap
    throw (new-object SystemException("a systemexception"))    # note the different way throwing
    throw [Exception] "a default error"

    # order of traps not important
    trap [SystemException]           { $_.Exception; continue; }        # 'continue' execute the next command in the scope of the 'trap'
    trap [IO.FileNotFoundException]  { $_.Exception; continue; }        # 'continue' causes the throw ioexception statement to execute
    trap [Exception]                 { $_.Exception; continue; }
    trap [IO.IOException]            { $_.Exception; continue; }
    
    "done"
}

test-trap

As you can see, using traps are a little tricky, using try-catch should cover most (if not all) your cases.

Powershell Simplified Part 6: Functions

Functions in Powershell scripts work just like you’d expect.

# define a function
function get-capital($country) {
    $htable = @{UK = "London"; Austria = "Vienna"; France = "Paris"}
    if ($htable.ContainsKey($country)) { return $htable[$country] }
    else { return "Not found" }
}

# call the function
$capital = get-capital("Austria")
write-host $capital

One interesting thing about Powershell functions is that anything you ‘leave behind’ will be automatically assigned as return value. If you leave behind more than one piece of information, it will be returned as an array. Thus we can get rid of the ‘return‘ keyword, but it’s much more readable to use it.

# define a function
function get-capital($country) {
    $htable = @{UK = "London"; Austria = "Vienna"; France = "Paris"}
    if ($htable.ContainsKey($country)) { $htable[$country] }
    else { "Not found" }
}

Adding function parameters is use for making sure your function is called with the right params.

function get-capital {
    param (
        [ValidateSet("prod","ppe","dev")]           # must be a value from this set
        [parameter(Mandatory=$false, position=0)]   # assign a position so that arguments can be passed without a parameter name
        [alias("env")]                              # alias for this param
        [string]                                    # param type is string
        $environment = "dev",                       # use a default value if none specified

        [ValidateRange(1,99)]
        [parameter(Mandatory=$true, position=1)]    # note no alias specified
        [string]
        $country,

        [ValidateSet("UK","Austria","France")]      # also see ValidateScript and ValidatePattern
        [parameter(Mandatory=$false, position=2)]   # This param is optional
        [alias("i")]
        [int]
        $id = 42
    )

    $htable = @{UK = "London"; Austria = "Vienna"; France = "Paris"}
    if ($htable.ContainsKey($country)) { return $htable[$country] }
    else { return "Not found" }
}

# different ways to invoke the function
$capital = get-capital -country "France"
$capital = get-capital -env "prod" -country "France"
$capital = get-capital -env "prod" -country "France" -i 9
$capital = get-capital "prod" "France" 9

For more advanced parameter options see here. Let’s take a look at two more advanced parameter options: switch and pipeline-aware.

function get-capital { param ( # receive incoming pipeline input and bind to this arg [parameter(Mandatory=$true, ValueFromPipeline=$true)] [string] $env, # switch parameters do not take arguments - they are either present or not [switch] $flag ) # check for the presence of 'flag' in the param list if ($flag) { write-host "has flag" } else { write-host "no flag" } write-host $env } get-capital -env "Prod" # 'flag' is optional get-capital -env "Prod" –flag # using the 'flag' param (no param) "PPE" | get-capital # pipe the string value to the function which binds it to $env

Powershell Simplified Part 5: Services and EventLogs

Working with Windows services is easy with Powershell.

get-service                    # list of all services
get-service netlogon           # service details
stop-service netlogon -force   # stop a service
start-service netlogon         # start a service

# to view other service related commands use: get-command *-service

Powershell allows manipulating services via WMI too, which we’ll see in another post. Working with the eventlog is also easy.

# get a particular log then filter by source
get-eventLog -logname "Ijv.Platform" -source "Service Control Manager"
# register an event source 'myscript'
new-eventlog -logname application -source myScript 
# write to the eventlog
write-eventlog -logname application -source myScript -entrytype information -eventid 123 -message 'Test event log entry'
get-eventlog -logname application -newest 10         # get top 10 entries

Powershell Simplified Part 4: Registry and Processes

Powershell ships with some built-in ‘providers’. A ‘provider’ simply provides easy access to data stores. To get a list of the available providers use: Get-PSProvider

Using the cmd above you’ll see that there’s a ‘Registry’ provider included in Powershell. This registry provider exposes the Windows registry as two drives: HKLM and HKCU. Thus the registry items can be accessed just like you would a typical disk drive.

cd HKCU:  # go to the registry (represented as a drive)
dir       # view contents of the current drive (displays top level keys under HKCU)
Get-ChildItem -Path HKCU:                # also to view contents of the HKCU drive
Get-ChildItem -Path HKCU: –recurse       # list all registry keys in the HKCU drive

# we can create a new drive for the HKEY_USERS hive
new-psdrive -name HKURS -psprovider Registry -root HKEY_USERS 

# read some registry values
$getItem = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ProductName, EditionID
write-host $getItem.ProductName       # write the registry value

New-Item HKCU:\Software\TestKey -Value 'Default Text' -Type String      # create a new registry key
Set-ItemProperty HKCU:\Software\TestKey -Name ID -Value 12 -Type DWORD  # add a key-value pair to the registry key
Remove-Item -Path HKCU:\Software\TestKey                                # delete a registry key

Working with process is also simple in Powershell.

get-process                     # get all processes on local machine
get-process -name exp*, power*  # regex match on name of the process
get-process -computername server01 | format-table -property ProcessName, MachineName  # view processes on a different machine

start-process "C:\test\test.msi"              # start a process
start-process notepad -windowstyle maximized  # change windowstyle
start-process notepad –wait                   # wait for the process to complete before accepting more input
write-host (get-process notepad).Count        # count the number of notepad instances open

stop-process -name notepad                    # close all notepad instances
get-process | where-object -filterscript {$_.Responding -eq $false} | stop-process   # stop all unresponsive processes

Powershell Simplified Part 3: Variables and Objects

Powershell creates new variables automatically, so there is no need to ‘declare’ them. Variable names are case-insensitive.

$val1 = 10                     # declare and assign a variable 
($val1).GetType().Name         # type is Int32
$val1 = “Hello”                # reassign the variable
($val1).GetType().Name         # type is String
[int]$val2 = 10                # strongly typed variable 
#$val2 = “Hello”               # throws error - cannont be assigned to string 

Powershell supports all .NET data types, this is very useful because you get access to all the type’s properties and methods…

# a simple XML string
$a = "<books><book name='1984' author='Orwell'/><book name='Macbeth' author='Shakespeare'/></books>"  
[xml]$booksXML = $a             # cast string to XML ('booksXML' is of type XML)
$booksXML.books.book            # Now we can XPATH into the XML

There’s also a different way to create a variable, by creating a variable object,

New-Variable val3 -value 10                  # a different way to create a variable 
$val3 = 5                                    # this can be reassigned
New-Variable val4 -value 10 -option ReadOnly # also see '-option Constant'
$val4 = 5                                # throws error: val4 cannot be reassigned 

Powershell provides access to some Windows environment variables for your convenience,

Get-Childitem env:            # get the full list
# or you could iterate over each element...
Get-Childitem env: | ForEach-Object {"{0} : {1}" -f $_.Name,$_.Value} 
write-host $env:OS            # or get individual members
write-host $env:windir 

Powershell has powerful (no pun intended) support for objects. One of the best parts about Powershell is that you can use all your regular .NET classes!

# In C#, string combined = System.IO.Path.Combine(path1, path2);
$combined = [System.IO.Path]::Combine($path1, $path2)

# In C#, string hostname = System.Net.Dns.GetHostName();
$hostname = [System.Net.Dns]::GetHostName()

# In C#, byte[] converted = Convert.FromBase64String(encrypted);
$converted = [Convert]::FromBase64String($encrypted)

# Let's go one step furhter and load a .NET assembly,
[void][reflection.assembly]::LoadWithPartialName("Microsoft.VisualBasic")

# What static types did we just load?
[Microsoft.VisualBasic.Interaction] | Get-Member -static

# Great! Let's use some,
[microsoft.VisualBasic.Interaction]::MsgBox("Hello VB!")
$name = [microsoft.VisualBasic.Interaction]::InputBox("Enter Name", "Name", "$env:username")
write-host "User typed:" $name

That was fun, now let’s create some objects in Powershell.

# First let's instantiate a .NET type with new-object,
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Administration") > $null
$iis = new-object Microsoft.Web.Administration.ServerManager 
$appPools = $iis.ApplicationPools 
foreach($app in $appPools)  { write-host $app.Name "is" $app.State } 

# Now let's create an object in Powershell,
$myObj = new-object Object
add-member -inputobject $myObj -membertype noteproperty -name Color -Value Red
add-member -inputobject $myObj -membertype noteproperty -name OS -Value $env:OS
add-member -inputobject $myObj -membertype scriptmethod -name SetColor( { param($color) $this.Color = $color } )
# To see what the args mean use: get-help add-member -full 

write-host "Color is: " $myObj.Color                        # Get the Color property
$myObj.Color = "Orange"                                     # Set the Color property
write-host "Color is: " $myObj.Color
write-host "Color type is: " $myObj.Color.GetType().FullName    # Property type is System.String by default
$myObj.SetColor("Blue")                                     # Set the property using a method 
write-host "Color is: " $myObj.Color

Powershell Simplified Part 2: Arrays and Hashtables

Today we’ll do some Powershell programming using some basic constructs. First let’s take a look at arrays in Powershell, turns out they are not much different from arrays in C#.

Try out these in a PS1 file (to run the ps1 file see the instructions in Part 1 of this series),

$array = @(1, "Hello", 3.14)                     # create a mixed Powershell array 
write-host “array length is” $array.length       # output the length of the array 
$array += "World"                                # append to the array  
write-host “third element is” $array[2]          # get the 3rd element in the array $array[3] = "Powershell"                         # update an element in the array $array = $array | Sort-Object                    # sort the array write-host “sorted array:” $array           # sorted array: 1 3.14 Hello Powershell $array = $array[0..1] + $array[3]           # remove the 3rd element from the array write-host $array $array = @()                                     # clears the array

Use foreach to loop through the array: foreach ($element in $array) { write-host $element }
Search the array for an item: $array –contains “2.18” (this evaluates to false in our case)
We can also wildcard search for an item: $array –like “He” (this evaluates to true in our case)

A hashtable also works as you would expect in Powershell,

$htable = @{UK = "London"; Austria = "Vienna"; France = "Paris"} # create a hashtable 
write-host $htable["UK"]                       # get the value of a key 
write-host $htable.France                      # another way to get a value 
write-host $htable.keys                        # get all the keys 
$htable["US"] = "Washington D.C."              # insert or update a key 
$htable.add("India", "New Delhi")              # another way to insert 
$htable.remove("UK")                           # remove a key 
foreach ($key in $htable.keys) { write-host $key ":" $htable[$key] }   # enumerate

Use ContainsKey or ContainsValue to search inside the hashtable: $hashtable.ContainsKey("India")

Powershell Simplified Part 1: Basics

You can start the Powershell command line by opening the cmd window and typing ‘powershell’.
Alternately, you can click on the Powershell icon or search for Windows Powershell in the StartMenu.

To check what Powershell version you’re running type $host.

image

See all the built-in commands Powershell provides: get-command
You can also filter to get a subset of the commands: get-command get-*
To filter by CommandType: get-command –commandtype cmdlet

To get more information on how to use a particular command you can use get-help: get-help get-date
Or simply add a –? at the end of a command: get-eventlog –?

Let’s try out some simple commands at the Powershell command prompt. These should be self explanatory.

get-process
get-date
start-process notepad

Now let’s try some basic scripting. Create a document called say test.ps1, and copy the three commands into the file. At the Powershell prompt go to the folder which has this file (using the regular cd command – if you notice cd is defined as an alias in Powershell, check using ‘cd –?’). Then type the following at the prompt (note the dot and slash before the file name).

PS C:\> .\test.ps1

You should see the output on the console window and a new notepad instance.