Summary: Boe Prox shows how to use Set-StrictMode to write better scripts.
Honorary Scripting Guy and Windows PowerShell MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. This is the third part in a series of five posts about troubleshooting Windows PowerShell scripts and functions. The series includes:
- Provide Support by Using Verbose and Debug Streams
- Troubleshoot by Using Set-PSDebug
- Enforce Better Script Practices by Using Set-StrictMode (this post)
- Trace Your Commands by Using Trace-Command
- Use the PowerShell Debugger
If you remember back in the VBScript days, if you wanted to enforce better practices with the handling of undeclared variables in your code, you would use Option Explicit. If you tried to throw in a variable that wasn’t defined at the top of the script, it would throw an error message and stop executing. Well, we’ve come further along with Windows PowerShell.
By using the Set-StrictMode cmdlet, we can enforce some common “best practice” coding techniques that you can use with your scripts. In yesterday's post, I used the –Strict parameter with Set-PSDebug. This enforces the variable declaration in a script. But with Set-StrictMode, we get that plus a little more. The scope is limited to the current scope and child scopes. By default, Set-StrictMode is turned off to prevent error messages while you are writing the script.
You only have two parameters to deal with: -Off and -Version.
Off | Turns off StrictMode. |
Version | There are three possible values that can be supplied with this parameter: -Version 1.0 -Version 2.0
-Version Latest |
Let’s start by showing what happens when you set the StrictMode version to 1.0. We can verify that if we use an uninitialized variable, it will not throw any error messages and act as though it might actually have something.
PS C:\> $i -gt 0
False
It returned $False, which is what I expected. Now let’s set the version to 1.0 and see what happens:
PS C:\> Set-StrictMode -Version 1.0
$i -gt 0
The variable '$i' cannot be retrieved because it has not been set.
At line:2 char:1
+ $i -gt 0
+ ~~
+ CategoryInfo : InvalidOperation: (i:String) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined
Well, that definitely played out differently. Instead of returning a Boolean value, I am greeted with an error message telling me that I should have initialized the variable first.
Another good use of this version is to make sure that you do not mistype a variable that might have been declared earlier and might not otherwise be caught.
I would usually turn this off by using the –Off parameter, but instead, I am going to move to the next part of Set-StrictMode by running a few commands that will still succeed with version 1.0 enabled:
PS C:\> Function Invoke-Test {
>> Param ($Item1, $Item2)
>> $Item1 + $Item2
>> }
>>
PS C:\> Invoke-Test(1,4)
1
4
Calling the function like we would call a method worked—even with the version set to 1.0. This, of course, is not how a function should be called. It can cause undesirable problems if it happens to “work” because it treats the items separated by a comma as a collection. Setting the version to 2.0 (or to Latest) helps us understand that this is not an acceptable practice:
PS C:\> Set-StrictMode -Version 2.0
PS C:\> Invoke-Test(1,4)
The function or command was called as if it were a method. Parameters should be separated by spaces. For information about parameters, see the about_Parameters Help topic.
At line:1 char:1
+ Invoke-Test(1,4)
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : StrictModeFunctionCallWithParens
That is more like it! After setting the version to 2.0 (or to Latest), my attempt to run the function like a method is met with an error message that explicitly says it was being called like a method. Remember that parameters are positional, and they should be separated by a space instead of a comma.
Now I’ll turn off StrictMode to demonstrate another example where something that shouldn’t be used doesn’t throw an error message. Then I'll repeat the process with StrictMode enabled and set to Latest.
PS C:\> Set-StrictMode –Off
Now I will attempt to call a property on an object that doesn’t exist:
PS C:\> $Item = 'test'
PS C:\> $item.date
Nothing happens. The property doesn’t exist, but no error is thrown to let us know this.
Now I enable StrictMode and observe what happens:
PS C:\> Set-StrictMode -Version Latest
PS C:\> $item.date
The property 'date' cannot be found on this object. Verify that the property exists.
At line:1 char:1
+ $item.date
+ ~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], PropertyNotFoundException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
As with my last demo, an attempt to use a property that doesn’t exist on the object is rejected, and an associated error message shows that you are not calling a valid property name.
Join me tomorrow when I discuss using Trace-Command to help in troubleshooting the pipeline and parameter binding.
We invite you to follow the Scripting Guy on Twitter and Facebook. If you have any questions, send email to the Scripting Guy at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Until then, see ya!
Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy