Trap

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

In order to catch and handle errors in PowerShell we can use the Trap keyword. Trap has the following Syntax:


trap [[error type]] {statement list}

If we try to merge a value of the type System.Int32 with a value of type System.String an error occurs.


PS > [int]1 + [string]"Hello"

Cannot convert value "Hello" to type "System.Int32". Error: "Input string was not in
a correct format."
At line:1 char:9
+ [int]1 + <<<< [string]"Hello"
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

We can get the type of error that occured by checking the InnerException.


PS > $Error[0].Exception.InnerException.GetType().FullName

System.Management.Automation.PSInvalidCastException

With this information, we can handle the errors of the type System.Management.Automation.PSInvalidCastException using a simple trap. In the example below
we create a funtion that shows how to handle these types of errors.


function TrapError([scriptblock]$Command) {
  Trap [System.Management.Automation.PSInvalidCastException] {
    Write-Host "My Custom Error Message" -ForegroundColor Red
    Continue
  }
  & $Command
}

When we call the function our custom error message is returned instead of the default error message.


PS > TrapError -Command { [int]1 + [string]"Hello" }

My Custom Error Message

But what if we generate another kind of error. Let's try to divide 1 by $null.


PS > TrapError -Command { 1/$null }

Attempted to divide by zero.
At line:1 char:24
+ TrapError -Command { 1/ <<<< $null }
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : RuntimeException

This time the error was not handled since it's a different type of error. We can of course modify our function to handle these types of errors as well.


function TrapError([scriptblock]$Command) {
  Trap [System.Management.Automation.PSInvalidCastException] {
    Write-Host "My Custom Error Message" -ForegroundColor Red
    Continue
  }

  Trap [System.DivideByZeroException] {
    Write-Host "1/`$null doesn't really work.." -ForegroundColor Red
    Continue
  }

  & $Command
}

If we try the same command again, the error is handled.


PS > TrapError -Command { 1/$null }

1/$null doesn't really work..

We can also add a Trap keyword without a specific error type to handle other types of errors that may occur.


function TrapError([scriptblock]$Command) {
  Trap [System.Management.Automation.PSInvalidCastException] {
    Write-Host "My Custom Error Message" -ForegroundColor Red
    Continue
  }

  Trap [System.DivideByZeroException] {
    Write-Host "1/`$null doesn't really work.." -ForegroundColor Red
    Continue
  }

  Trap {
    Write-Host "This really doesn't work.." -ForegroundColor Red
    Continue
  }

  & $Command
}

If we try to create an instance of a .NET Object and specify a nonexisting class, the error is handled by the trap.


PS > TrapError -Command { New-Object System.Blah }

This really doesn't work..

Some Errors are handled in other ways in commands and scripts. a typical example is the Get-ChildItem cmdlet.
If we use our function and use Get-ChildItem and try to retrieve a folder that doesn't exist, the cmdlet will handle
the error.


PS > TrapError -Command { Get-ChildItem C:\Blah }

Get-ChildItem : Cannot find path 'C:\Blah' because it does not exist.
At line:1 char:35
+ TrapError -Command { Get-ChildItem <<<<  C:\Blah }
    + CategoryInfo          : ObjectNotFound: (C:\Blah:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

We can override this and manage the error in our trap by setting the ErrorAction parameter to Stop.


PS > TrapError -Command { Get-ChildItem C:\Blah -ErrorAction Stop }

This really doesn't work..

Rating 3.00 out of 5
[?]

Try/Catch/Finally

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

In PowerShell V2 we can use Try/Catch and Finally to control how scripts handle errors. Just as with the Trap statement, we can handle different types of errors.
Here’s a short function that shows how Try/Catch/Finally works.


function TryCatchError([scriptblock]$Command) {
  Try {
    & $Command
  }
  Catch [System.Management.Automation.PSInvalidCastException] {
    Write-Host "A PSInvalidCastException error occured" -ForegroundColor Red
  }
  Catch [System.DivideByZeroException] {
    Write-Host "A DivideByZeroException error occured" -ForegroundColor Red
  }
  Catch {
    Write-Host "Some other error occured" -ForegroundColor Red
  }
  Finally {
    "This part always executes, even if an error occurs."
  }
}

First, let’s generate an error of the type PSInvalidCastException.


PS > TryCatchError -Command { [int]1 + [string]"Hello" }

A PSInvalidCastException error occured
This part always executes, even if an error occurs.

Next we try to divide 1/$null.


PS > TryCatchError -Command { 1/$null }

A DivideByZeroException error occured
This part always executes, even if an error occurs.

If we try to create an instance of a .NET Object and specify a nonexisting class, the error is handled as expected.


PS > TryCatchError -Command { New-Object System.Blah }

Some other error occured
This part always executes, even if an error occurs.

Now let’s try Get-ChildItem and specify a nonexisting folder.


PS > TryCatchError -Command { Get-ChildItem C:\Blah }

Get-ChildItem : Cannot find path 'C:\Blah' because it does not exist.
At line:1 char:39
+ TryCatchError -Command { Get-ChildItem <<<<  C:\Blah }
    + CategoryInfo          : ObjectNotFound: (C:\Blah:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

This part always executes, even if an error occurs.

The Error is handled by the Get-ChildItem cmdlet rather than our Catch statement. Setting the ErrorAction parameter to Stop lets our Catch statement handle the error instead.


PS > TryCatchError -Command { Get-ChildItem C:\Blah -ErrorAction Stop }

Some other error occured
This part always executes, even if an error occurs.

Rating 3.00 out of 5
[?]

Advanced Functions

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

If you have a set of commands or code that you want to reuse multiple times in a script or in your session you can place the code in a function.
This allows you to call the function instead of typing the same piece of code over and over again.

Just as an example, we’ll use Get-Process and display the Process Name and Company property.


PS > $Name = "powershell"
PS > $process = Get-Process $Name
PS > Write-Host ProcessName: $Process.Name

ProcessName: powershell

PS > Write-Host Company: $Process.Company

Company: Microsoft Corporation

Instead of typing the lines above everytime we want to check the Name and Company of a process we can place the code in a function.


function Get-Company([string]$Name) {
  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

Now we can use the function instead.


PS > Get-Company -Name powershell

ProcessName: powershell
Company: Microsoft Corporation

Calling a function in PowerShell is similar to calling one of the built-in cmdlets. You type the functions name followed by one or more parameters.

You can also use Get-Help to find out additional information about a function, just as you do with CmdLets.


PS > Get-Help Get-Company

Get-Company [[-Name] ]

CmdLets in powershell usually have a set of common parameters such as Verbose, Debug, OutVariable and so on. You can add these additional parameters by using the [CmdletBinding()] attribute.


function Get-Company {
  [cmdletbinding()]
  param([string]$Name)

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

If we use the Get-Help CmdLet additional parameters are available in our function.


PS > Get-Help Get-Company

Get-Company [[-Name] ] [-Verbose] [-Debug] [-ErrorAction ]
[-WarningAction ] [-ErrorVariable ] [-WarningVariable ]
[-OutVariable ] [-OutBuffer ]

It’s also possible to set a parameter as mandatory. Here’s an example.


function Get-Company {
  param(
    [Parameter(Mandatory = $true)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

If we call the function without a value to the Name parameter we are automatically prompted for it.


PS > Get-Company

cmdlet Get-Company at command pipeline position 1
Supply values for the following parameters:
Name: powershell

ProcessName: powershell
Company: Microsoft Corporation

If we want to validate the input to a parameter we can use the [ValidateSet()] attribute. Here’s an example.


function Get-Company {
  param(
    [Parameter(Mandatory = $true)]
    [ValidateSet("powershell","notepad")]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

The example above only accepts “powershell” or “notepad” as input.


PS > Get-Company -Name powershell

ProcessName: powershell
Company: Microsoft Corporation

If we type anything else an error occurs.


PS > Get-Company -Name calc

Get-Company : Cannot validate argument on parameter 'Name'. The argument "calc" does
not belong to the set "powershell,notepad" specified by the ValidateSet attribute.
Supply an argument that is in the set and then try the command again.
At line:1 char:18
+ Get-Company -Name <<<<  calc
    + CategoryInfo          : InvalidData: (:) [Get-Company], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Get-Company

We can also position the parameter. This applies when we provide parameter values without specifying the parameter.


function Get-Company {
  param(
    [Parameter(Position = 0)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

PS > Get-Company powershell

ProcessName: powershell
Company: Microsoft Corporation

We can also let the function accept input from a pipeline by using ValueFromPipeLine.


function Get-Company {
  param(
    [Parameter(
      Position = 0,
      ValueFromPipeLine= $true)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

Now we can use a pipeline when calling the function.


PS > "powershell" | Get-Company
ProcessName: powershell
Company: Microsoft Corporation

What if we send two inputs through the pipeline?


PS > "powershell","winword" | Get-Company

ProcessName: WINWORD
Company: Microsoft Corporation

Note that only the last input value was returned by the function. Adding a Process block solves this.


function Get-Company {
  param(
    [Parameter(
      Position = 0,
      ValueFromPipeLine= $true)]
    [string]$Name
  )
  Process {
    $process = Get-Process $Name
    Write-Host ProcessName: $Process.Name
    Write-Host Company: $Process.Company
  }
}

Now we can send additional values through the pipeline.


PS > "powershell","winword" | Get-Company

ProcessName: powershell
Company: Microsoft Corporation
ProcessName: WINWORD
Company: Microsoft Corporation

We can also use propertynames from an object passed through a pipeline. Here's an example.


function Get-Company {
  param(
    [Parameter(
      ValueFromPipeLineByPropertyName= $true)]
    [string]$Name,
    [Parameter(
      ValueFromPipeLineByPropertyName= $true)]
    [string]$Company
  )
  Process {
    Write-Host ProcessName: $Name
    Write-Host Company: $Company
  }
}

Now we can use Get-Process and pipe the objects to our function as shown below.


PS > Get-Process powershell | Get-Company

ProcessName: powershell
Company: Microsoft Corporation

PS > Get-Process powershell,winword | Get-Company

ProcessName: powershell
Company: Microsoft Corporation
ProcessName: WINWORD
Company: Microsoft Corporation

In an earlier example we took a look at how to add common parameters to a function, but how do we use them?
Well, it turns out that advanced functions in powershell give us access to the $pscmdlet automatic variable.
The variable exposes support for Verbose, Whatif and more. The function below calls the variable and displays it's properties.


function Get-PSCmdLet {
  [cmdletbinding()]
  param()

  $psCmdLet
}

If we call the function, the variable properties are displayed.


PS > Get-PSCmdLet


ParameterSetName     : __AllParameterSets
MyInvocation         : System.Management.Automation.Invoc
InvokeCommand        : System.Management.Automation.Comma
Host                 : System.Management.Automation.Inter
SessionState         : System.Management.Automation.Sessi
Events               : System.Management.Automation.PSLoc
JobRepository        : System.Management.Automation.JobRe
InvokeProvider       : System.Management.Automation.Provi
Stopping             : False
CommandRuntime       : Get-PSCmdLet
CurrentPSTransaction :
CommandOrigin        : Runspace

If we pipe the object to Get-Member we'll see all Methods and Properties available on the object.


PS > Get-PSCmdLet | Get-Member


   TypeName: System.Management.Automation.PSScriptCmdlet

Name                                MemberType Definition
----                                ---------- ----------
CurrentProviderLocation             Method     System.Managem
Dispose                             Method     System.Void Di
Equals                              Method     bool Equals(Sy
GetDynamicParameters                Method     System.Object
GetHashCode                         Method     int GetHashCod
GetResolvedProviderPathFromPSPath   Method     System.Collect
GetResourceString                   Method     string GetReso
GetType                             Method     type GetType()
GetUnresolvedProviderPathFromPSPath Method     string GetUnre
GetVariableValue                    Method     System.Object
Invoke                              Method     System.Collect
ShouldContinue                      Method     bool ShouldCon
ShouldProcess                       Method     bool ShouldPro
ThrowTerminatingError               Method     System.Void Th
ToString                            Method     string ToStrin
TransactionAvailable                Method     bool Transacti
WriteCommandDetail                  Method     System.Void Wr
WriteDebug                          Method     System.Void Wr
WriteError                          Method     System.Void Wr
WriteObject                         Method     System.Void Wr
WriteProgress                       Method     System.Void Wr
WriteVerbose                        Method     System.Void Wr
WriteWarning                        Method     System.Void Wr
CommandOrigin                       Property   System.Managem
CommandRuntime                      Property   System.Managem
CurrentPSTransaction                Property   System.Managem
Events                              Property   System.Managem
Host                                Property   System.Managem
InvokeCommand                       Property   System.Managem
InvokeProvider                      Property   System.Managem
JobRepository                       Property   System.Managem
MyInvocation                        Property   System.Managem
ParameterSetName                    Property   System.String
SessionState                        Property   System.Managem
Stopping                            Property   System.Boolean

Now let's see how we can use this in our function. Say we want to write to the Verbose message stream. We can do this by simply using the WriteVerbose() method as shown below.


function Get-Company {
  [cmdletbinding()]
  param([string]$Name)

  $process = Get-Process $Name
  $psCmdLet.WriteVerbose("ProcessName: $($Process.Name)")
  $psCmdLet.WriteVerbose("Company: $($Process.Company)")
}

When we call the function nothing seems to happen.


PS > Get-Company -Name powershell

But if we add the -Verbose parameter the verbose messages are returned.


PS > Get-Company -Name powershell -Verbose

VERBOSE: ProcessName: powershell
VERBOSE: Company: Microsoft Corporation

Rating 4.50 out of 5
[?]

Remoting

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

Here’s a quick introduction to Windows PowerShell remoting. First, we need to Enable remoting on the computers that we want to manage. This is done by using the Enable-PSRemoting cmdlet. Here’s an example.


PS > Enable-PSRemoting


WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): Y
WinRM already is set up to receive requests on this machine.
WinRM has been updated for remote management.
Created a WinRM listener on HTTP://* to accept WS-Man requests to any IP on this machine.
WinRM firewall exception enabled.

Confirm
Are you sure you want to perform this action?
Performing operation "Registering session configuration" on Target "Session configuration "M
not found. Running command "Register-PSSessionConfiguration Microsoft.PowerShell32 -processo
to create "Microsoft.PowerShell32" session configuration. This will restart WinRM service.".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): Y

When remoting is enabled on both the client and target computers you can run commands remote using WinRM. PowerShell includes a couple of CmdLets we can use. If we want to start an interactive session against a computer we can use Enter-PSSession.


PS > Enter-PSSession Client01 -Credential powershell\administrator

[client01]: PS C:\Users\Administrator\Documents>

Notice how we use the Credential parameter to specify different credentials.

When we are finished we call the Exit-PSSession CmdLet to end the Remote Session.


[client01]: PS C:\Users\Administrator\Documents> Exit-PSSession

PS >

If we want to run a set of commands against one or more remote computers without starting an interactive session we use the Invoke-Command cmdlet. Here’s an example.


PS > Invoke-Command -ComputerName Client01,Client02 -ScriptBlock {
>> Get-Process
>> } -Cred powershell\administrator
>>

In the example above we run Get-Process against Client01 and Client02. If you study the output from the example above you’ll notice that an additional property PSComputerName
is added. Also note that the object returned to your session are Deserialized “property bags”. These are not live objects, they are snapshots that have properties, but no methods.

When using the ComputerName parameter, each remote connection is opened when the command executes and closed after execution. It’s possible to keep a session open to perform additional actions using the
New-PSSession CmdLet. Here’s an example.


PS > $sessions = New-PSSession -ComputerName DC01,SP01 -Credential powershell\administrator

Now we can use the Session parameter when executing commands remote. Notice that we do not have to specify any Credentials since they are stored in the PSSession opened earlier.


PS > Invoke-Command -Session $sessions -ScriptBlock { Get-Process }

Finally, let’s take a look at CredSSP. CredSSP is used when we want our remote session to have full access to network resources. This is where CredSSP comes into play. You’ll also want to use
CredSSP in double-hop scenarios such as SharePoint remoting. To enable CredSSP you use the Enable-WSManCredSSP CmdLet both on the Client and Target computer. On the target machine, type:


PS > Enable-WSManCredSSP -Role Server


CredSSP Authentication Configuration for WS-Management
CredSSP authentication allows the server to accept user credentials
authentication on the server, the server will have access to the us
client computer sends them. For more information, see the Enable-WS
Do you want to enable CredSSP authentication?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): Y

cfg               : http://schemas.microsoft.com/wbem/wsman/1/confi
lang              : en-US
Basic             : false
Kerberos          : true
Negotiate         : true
Certificate       : false
CredSSP           : true
CbtHardeningLevel : Relaxed

On the Client computer we use the same command but change the Role to Client. We also specify the Server in the DelegateComputer parameter as shown below.


PS > Enable-WSManCredSSP -role client -delegatecomputer SP01.powershell.nu

Now we can run remote commands using CredSSP.


PS > Enter-PSSession -ComputerName SP01.powershell.nu -Authentication CredSSP -Credential powershell\spAdmin

Note that if you are running within a workgroup you have to enable CredSSP over NTLM.

Rating 4.00 out of 5
[?]

Jobs

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

In PowerShell V2 you can use background jobs to perform various tasks. You can start a new job by using the Start-Job CmdLet.


PS > Start-Job -Name MyJob -ScriptBlock { Get-Process powershell }

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               MyJob           Running    True            localhost             Get-Process powershell

We can use Get-Job to display the jobs that are available.


PS > Get-Job

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               MyJob           Completed  True            localhost             Get-Process powershell

Notice that the State chenges from Running to Complete after the job completes. To check the result of the Job we use the Receive-Job CmdLet.


PS > Receive-Job -Id 1

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    256      24    53372      60192   566     0,58   1304 powershell
    466      25    61036      71124   570     1,37   5404 powershell

If we run Receive-Job again nothing is returned.


PS > Receive-Job -Id 1

This is because the job results are deleted from the system after you receive them. You can use the Keep parameter to save the results so that you can receive them again.

We can remove a job by using the Remove-Job CmdLet. Here’s a short example.


PS > Get-Job -Id 1 | Remove-Job
PS > Get-Job

If we want to wait for a job to complete we use the Wait-Job CmdLet.


PS > Start-Job -Name MyJob -ScriptBlock { "Hello"; Start-Sleep -s 5 } | Wait-Job

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               MyJob           Completed  True            localhost             "Hello"; Start-Sleep ...

Once the job completes we can use Receive-Job to check the result.


PS > Receive-Job -Name MyJob

Hello

Here’s another example where we add Receive-Job to the pipeline.


PS > Start-Job -Name MyJob -ScriptBlock { "Hello"; Start-Sleep -s 5 } | Wait-Job | Receive-Job

Another cool thing with jobs is that they can be run on Remote Computers. In the Example below we use Invoke-Command and add the AsJob parameter to run the commands as background jobs. Here’s an example.


PS > Invoke-Command -ComputerName DC01,SP01 -ScriptBlock { Get-Process } -AsJob -Credential powershell\administrator

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Running    True            dc01,sp01            Get-Process

When running jobs on multiple computers remote each job is stored as a childjob. If we check out the ChildJob parameter on our job we’ll see that there are currently 2 childjobs available, one for each remote computer.


PS > (Get-Job -Id 1).ChildJobs

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
2               Job2            Completed  True            dc01                  Get-Process
3               Job3            Completed  True            sp01                  Get-Process

If we want to see the result from DC01 we use the Receive-Job CmdLet and point out the Id or Name of that job.


PS > Receive-Job -Name Job2

Rating 3.00 out of 5
[?]