Skip to main content

PowerShell - 2013 Scripting Games Advanced Event 1 - Error Handling

One more suggestion I received on my entry was better error handling. If you didn't get this already, but I am a naturalist when it comes to PowerShell, e.g. let PowerShell do the heavy lifting and what it does naturally best.

Though, thinking through possible error conditions in my script, I do have opportunities for improvement. Namely around the scenario where the target directory could not be created by New-Item, the script continues on when that really is a terminating error and should stop.

I've been reading peoples feedback and other blog posts about how to handle errors with this event. I saw a few people very passionately advocate for Write-Warning as a better solution than Write-Error. Write-Warning is not a suitable replacement for Write-Error, they have two very specific meanings for the user.

While I agree, the implied usage of each could leave this error gray, I think it is easy to set a few best practices I think everyone could agree with.

Write-Warning should be used when a condition occurs that the user may need to know about but the cmdlet is able to work with anyway.

Example of proper Write-Warning is perhaps where an input object that was passed didn't meet some requirement, e.g. Get-ADUser | YourCmdlet and the ADUser is disabled and you don't work with disabled users, so you could just simply Write-Warning "User '$_' is being excluded as they are disabled.".

You can continue with whatever your cmdlet does, but the user knows, that user won't be included in whatever the result is.

Write-Error should be used when a non-terminating error has occurred.

This behavior can be seen in the Move-Item command. If you are processing several files in the pipeline, if one of them fails, by default, you can just log that one failure and continue on with the rest of the files in the pipeline. Unless the user changes the behavior with the -ErrorAction parameter or $ErrorActionPreference, this is fine.

This is where Write-Warning and Write-Error differ. An error occurred while processing a single item, you should report it as an error verses a warning message that notified the user they may need to do something. For example, Write-Warning is inappropriate to notify the user that a file failed to be moved-failure implies error. How many of you expect a program that encounters an error to display it was a warning in a log? Personally I mostly ignore warnings, errors generally you want to correct. Warning would tell me something isn't optimal but it is working. Error tells me something isn't working 100% of the time.

$ErrorActionPreference should not be changed from within a cmdlet.

Perfect diversion into the next topic. I am guilty. I do this and it is wrong, more so lazy. If you are just writing a script that won't be reused, I believe this is fine if you want the global behavior to be whatever you specify as the value.

For a reusable cmdlet, you should not do this, you should use the -ErrorAction parameter on the cmdlets that are invoked. Or just let the ErrorAction that is passed to you apply to them. In most cases this is preferred but there are situations where you need an error to be a terminating one.

Throw should be used when a terminating error has occurred.

Unlike Write-Error that will just display an error and continue, throw will almost always terminate the current pipeline, unless the user specified an ActionPreference that causes a different behavior.

Use the throw statement when you intend it to be a terminating error. One feature I really like that isn't used often with throw is throwing the actual object that caused the issue. The help for throw shows this as throw (Get-Process PowerShell) and then inspecting the failed object as such $Error[0].TargetObject. I honestly haven't seen this used much but come in handy so keep it in mind.

Use the break statement to ensure a script does not continue after an error.

So despite throw almost always being a terminating error, the user could still set the $ErrorActionPreference variable or -ErrorAction parameter to SilentlyContinue and it will do just that, ignore your error and try to keep plugging along with the pipeline.

This can cause unexpected behavior. For example with the advanced event 1, if the destination directory New-Item cmdlet fails, and it is a terminating error, SilentlyContinue would then attempt to move on and Move-Item all the files when it shouldn't.

So, checking for an error, and using the break statement will change all that and give you consistent error handling behavior.

There is another way to ensure a cmdlet produces a terminating error and properly stops.

Customize an error message only when you contribute valuable information to the content of the error.

Some people love to customize error messages. My general rule of thumb is that unless I can contribute value to the error message I don't need to create a custom error message and I can just let PowerShell do what it does best, be awesome!

So, just leave it a lone if you aren't contributing useful information that will help your user understand the error better and how to fix it.

When invoking a cmdlet -ErrorAction Stop should be used when you want a non-terminating error statement to produce a terminating error.

So, if you follow the rule about custom error messages, and you aren't going to contribute any more information then just provide the -ErrorAction parameter to whatever cmdlet you are invoking and tell it to Stop on error.

An example for this is New-Item cmdlet from the submission I made, with -ErrorAction Stop on it, the pipeline will stop if the destination directory can't be created for whatever reason. It will stop regardless of what the user has set in $ErrorActionPreference or -ErrorAction parameter when calling the script. This is because the -ErrorAction parameter essentially overrides the $ErrorActionPreference for the scope of the cmdlets pipeline.

Conclusion

So what do all these guidelines or best practices or rules or whatever you want to call them gain you. Consistent behavior with how PowerShell naturally works. It means your users won't be confused, it means the person that comes in after you to handle your script will be less confused with complex error handling code that is unnecessary in many cases.

So again, this is a learning process for us all and I hope these learning that I've acquired help you and hopefully you have some knowledge or wisdom or insight to share with me so that I can continue to get better at writing quality PowerShell scripts.

Based on all of this the only thing I would change in my advanced event 1 script is the New-Item cmdlet.
  1. New-Item -Path $_ -ItemType Container -Confirm:$false -ErrorAction Stop | Out-Null  
I don't have any more information to provide that New-Item won't provide for me to the user, it has the context of the item and will report that path when it can and should. Also, this one change would be enough to prevent the pipeline from continuing, at least from the testing I've done.

I'm wise enough to know that I can't declare this the perfect error handling solution. There could be situations where this wouldn't stop it appropriately but I can only code against the failure scenarios I can anticipate. Suggestions are very much appreciated!

Happy scripting!

Comments

Popular posts from this blog

PowerShell SupportsShouldProcess Worst & Best Practices

This has been a very big discussion within the Scripting Games 2013 community and I want to add my two cents in an official blog post.

I've left several people comments on how they might be misunderstanding how SupportsShouldProcess works, but I also realize, everyone of these individuals has given me more insight into its use and perhaps, how it should best be utilized.

For those of you that don't know, SupportsShouldProcess is a parameter on the CmdletBinding attribute you can place on your cmdlets that automatically adds the -WhatIf and -Confirm parameters. These will naturally flow into other cmdlets you use that also SupportsShouldProcess, e.g. New-Item, Move-Item.

The major discussion has been around, should you just let the other cmdlets handle the $PSCmdlet.ShouldProcess feature, and if not how should you implement it. ShouldProcess has the following definitions.
OverloadDefinitions�����������������������������������������������������������������������������������������…

FIM 2010 R2 Self-Service Password Reset Auto-Registration

I have been working on our FIM 2010 R2 SP1 lab environment looking for ways to simplify some of the overly complicated scenarios we had to implement to workaround the limitations in FIM 2010. One of those workarounds was the auto-registration of SSPR for new employees. When we onboard a new employee, we want to create a simple SSPR for them to get their first-time password reset.

Prior to the R2 release we were using the http://fim2010client.codeplex.com/ client and PowerShell to complete the registration process on a daily schedule. I was working on converting this to use the R2 registration cmdlets and combined it with PowerShell 3.0 Workflows to get to a solution similar to this.
[CmdletBinding()]  param()  begin {      workflow Invoke-RegisterSSPR {          InlineScript {              Import-Module FIM          }  $workflow = InlineScript { Get-FIMResource -Filter '/WorkflowDefinition[DisplayName = "Password Reset AuthN Workflow for New Employees"]' }  $members  …

PowerShell Error Handling Behavior Debunked

Note: I am using simple error messages as an example, please reference the best practices and guidelines I outlined on when to use custom error messages.

I have been churning in my mind for the last few days all the entries in the 2013 Scripting Games and how they handle errors, or lack thereof.

I am coming to the conclusion through some testing that the simple fact of seeing a try..catch or throw statements does not mean there is proper error handling.

I've been testing several variations and forms of error handling, so lets start with the basics.
function Test-WriteError {      [CmdletBinding()] param()  "Test-WriteError::ErrorActionPreference = $ErrorActionPreference"Move-Item -Path 'C:\Does\Not\Exists.log' -Destination 'C:\No\Where'"Test-WriteError::End"}   Test-WriteError::ErrorActionPreference = Continue
Move-Item : Cannot find path 'C:\Does\Not\Exists.log' because it does not exist.
At line:6 char:5
+     Move-Item -Path 'C:\Does\N…