Skip to main content

Invoke-PSFProtectedCommand

Synopsis

Explains how the Invoke-PSFProtectedCommand command can empower your code, while driving readability and reducing overhead.

Description

The Problem

There are many factors that are used to measure the maturity and quality of code. Error handling, supporting -WhatIf and -Confirm as well as message handling are all part of that. However, faithfully implementing all of that can get tedious fast.

An example of what that might look like:

if ($PSCmdlet.ShouldProcess($Path, "Delete")) {
try {
Write-PSFMessage -Message "Deleting $Path" -Target $Path
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
Write-PSFMessage -Message "Successfully deleted $Path" -Target $Path
}
catch {
Write-PSFMessage -Level Warning -Message "Failed to delete $Path" -Target $Path -ErrorRecord $_
Write-Error $_
continue
}
}

The Solution

This is exactly where Invoke-PSFProtectedCommand comes in:

Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $EnableException -Continue

This performs all the steps that were taken in the previous example:

  • Implements and respects -WhatIf and -Confirm
  • Implements error handling and reaction (more on that below)
  • Implements all message handling

Error Handling peculiarities

This command implements the same opt-in exception system as Stop-PSFFunction and Write-PSFMessage implement. This means, by default it will not throw an exception on failure. Instead it will flag the calling command as failed if an error happens in the scriptblock provided for it.

Use the -EnableException parameter to enable terminating exceptions. If the exception should not be terminating (but still be an exception), also include the -NonTerminating switch:

# Guaranteed Terminating if failed
Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $true -Continue

# Non-Terminating if failed
Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $true -NonTerminating -Continue

More details on the opt-in exception system can be found on its dedicated page

The -Continue Parameter

If in case of error you do not want to kill the entire function but continue working on the next item - for example within a foreach loop, processing many paths - it is recommended to use the -Continue parameter.

Using that parameter will cause the Invoke-PSFProtectedCommand cmdlet to call continue when a scriptblock fails. This also supports the use of labels, by using the -ContinueLabel parameter. For more information on using labels to control loops, check Get-Help about_Break.

Trying again

In some cases, it may be beneficial to try again on an error. For example in commands that are timing sensitive or remoting calls in regions with unstable connectivity.

So ... it would be beneficial to give a command a second chance, right?

That is where the three retry-specific parameters come in:

  • -RetryCount
  • -RetryWait
  • -RetryErrorType

In its basic form, this might look like this:

Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $EnableException -Continue -RetryCount 3

In this case, the command will now attempt four times - once plus three retries - to delete the file in the specified path. It will wait for five seconds inbetween attempts (five seconds is the default waiting time).

If a different time is desired, use -RetryWait, which is a Timespan Parameter Class. For example, this will cut the waiting time down to two seconds:

Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $EnableException -Continue -RetryCount 3 -RetryWait 2s

Finally, it may be beneficial to not attempt again on all errors. Often enough we can closely determine, when another attempt is worthwhile. For example, in a scenario where we wait for a file to be created before deleting it again, trying again when the file was not found would be useful. Trying again when access was denied however would be less so.

Using the -RetryErrorType parameter, it is possible to specify on exactly which exception types to keep trying:

Invoke-PSFProtectedCommand -Action Delete -Target $Path -ScriptBlock {
Remove-Item -Path $Path -Recurse -Force -Confirm:$false -ErrorAction Stop
} -EnableException $EnableException -Continue -RetryCount 3 -RetryErrorType 'System.Management.Automation.ItemNotFoundException'

This will try again if the file could not be found, but not on any other error.

Error Event

Using the -ErrorEvent parameter, it is possible to cause a specific action if the code fails, before processing the error itself. For example, this allows including a cleanup step before killing the entire command:

Invoke-PSFProtectedCommand -Action "Failing at Math" -Target 0 -ScriptBlock {
1 / 0
} -EnableException $true -ErrorEvent {
param ($Fail)
Write-Host "This was (maybe) not so smart: $($fail)"
}

Localization

The command also supports the PSFramework localization feature (full online documentation pending). This means that using the -ActionString and -ActionStringValues parameters the messages written can be localized.

Due to a limitation in the message system, message language is currently snapshotted to the display language at the time the message is written. This might be inconvenient if logging in a different language.

For more details on the localization feature, see:

Get-Help Import-PSFLocalizedString -Detailed