Introduction:
Windows Management Framework 5.0 Preview February 2015 includes the addition of a new Windows PowerShell Module called PSScriptAnalyzer. This is a static code checker for Windows PowerShell modules and scripts and is installed in $env:ProgramFiles/WindowsPowerShell/Modules.
PSScriptAnalyzer checks the quality of Windows PowerShell code by running a set of rules. The rules are based on PowerShell best practices identified by PowerShell Team and the community. PSScriptAnalyzer generates DiagnosticResults (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements.
PSScriptAnalyzer is shipped with a collection of built-in rules that checks various aspects of PowerShell code such as presence of uninitialized variables, usage of PSCredential Type, usage of Invoke-Expression etc. Additional functionalities such as exclude/include specific rules are also supported.
Custom rules written as PowerShell scripts can be supplied to the Script Analyzer. Read more about this in the last section. Code samples are also posted.
Usage and Scenarios:
- PSScriptAnalyzer is shipped with the following built-in rules.
- Excluding specified rules when using PSScriptAnalyzer
- Run only a specific set of rules, by adding the IncludeRule parameter:
Authoring Custom/External Rules as PowerShell Scripts:
PSScriptAnalyzer can consume modules containing definitions for rules. The requirement here is that the exported function containing the rule definition must have a parameter of type “Ast” or “Token”. The engine calls into the function and supplies the Ast of the PowerShell script under analysis. The function can do the actual work of validating the Ast. Here is the usage:
By using the “CustomizedRulePath” parameter in Invoke-ScriptAnalyzer cmdlet, one can point to a folder/module containing external script rules.
Example 1 – Rule to detect the presence of Write-Host:
<#
.SYNOPSIS
You should never use Write-Host to create any script output whatsoever.
.DESCRIPTION
It is generally accepted that you should never use Write-Host to create any script output whatsoever, unless your script (or function, or whatever) uses the Show verb (as in, Show-Performance).
That verb explicitly means “show on the screen, with no other possibilities.” Like Show-Command.
To fix a violation of this rule, please replace Write-Host with Write-Output in most scenarios.
.EXAMPLE
Test-WriteHost -CommandAst $CommandAst
.INPUTS
[System.Management.Automation.Language.CommandAst]
.OUTPUTS
[PSCustomObject[]]
.NOTES
Reference: Output, The Community Book of PowerShell Practices.
#>
function Test-WriteHost
{
[CmdletBinding()]
[OutputType([PSCustomObject[]])]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.Language.CommandAst]
$CommandAst
)
Process
{
try
{
$commandName = $CommandAst.GetCommandName()
# Checks command name, if the command name matches Write-Host or
# user-defined aliases, this rule is triggered.
if ($null -ne $commandName)
{
$alias = (Get-Alias -Definition “Write-Host” -ErrorAction SilentlyContinue).Name
if (($commandName -eq “Write-Host”) -or
($alias -eq $commandName))
{
[PSCustomObject]@{Message = “Avoid Using Write-Host”;
Extent = $CommandAst.Extent;
RuleName = $PSCmdlet.MyInvocation.InvocationName;
Severity = “Warning”}
}
}
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Example 2 – Rule for Try-Catch-Finally for handling terminating errors:
<#
.SYNOPSIS
Considers to use try-catch-finally statements instead of using $?.
.DESCRIPTION
When you need to examine the error that occurred, try to avoid using $?. It actually doesn’t mean an error did or did not occur; it’s reporting whether or not the last-run command considered itself to have completed successfully.
You get no details on what happened. To fix a violation of this rule, please consider to use try-catch-finally statements.
.EXAMPLE
Test-QuestionVariable -ScriptBlockAst $ScriptBlockAst
.INPUTS
[System.Management.Automation.Language.ScriptBlockAst]
.OUTPUTS
[PSCustomObject[]]
.NOTES
Reference: Trapping and Capturing Errors, Windows PowerShell Best Practices.
#>
function Test-QuestionVariable
{
[CmdletBinding()]
[OutputType([PSCustomObject[]])]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.Language.ScriptBlockAst]
$ScriptBlockAst
)
Process
{
try
{
# Gets question variables
$questionVariables = $ScriptBlockAst.FindAll(
{$args[0] –is [System.Management.Automation.Language.VariableExpressionAst] –and
$args[0].VariablePath.UserPath -eq ’?’},$true)
foreach ($questionVariable in $questionVariables)
{
[PSCustomObject]@{Message = “Avoid Using Question Variable”;
Extent = $questionVariable.Extent;
RuleName = $PSCmdlet.MyInvocation.InvocationName;
Severity = “Warning”}
}
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Note that in the next release we will be moving to using a specific type (called DiagnosticRecord), instead of using PSCustomObject, when returning results of the rule.
Also, the next blog post will talk about authoring rules in C#
That’s it for now! Run this framework against your scripts/modules and let us know of your feedback/suggestions.
Feedback and Contact:
https://connect.microsoft.com/PowerShell/Feedback
You can also follow us @: http://blogs.msdn.com/b/powershell/
Thanks!
Raghu Shantha [MSFT]
PowerShell ScriptAnalyzer Team
When I initially left a comment I appear to have clicked the -Notify me
when new comments are added- checkbox and from now on every time a comment is added
I get four emails with the exact same comment. There has to be a
way you can remove me from that service? Cheers!
We looked into this and it does not appear that we can control that subscription. It may be within your profile.
It’d be nice to have the full file name, or an object instead of a string for ScriptName. Running it recursively it not convenient without the full path. That would also help calling it from VisualStudio since now running an it as an External Tool, I have to run one file at a time and use $(ItemPath) from VS to get the file name. The nice thing about this is I can click the message in the output window to jump to the offending line.
This is the value for Arguments for the External Tool in VS
-noprofile -command “({1},{2}): {3}: {4}’ -f ‘$(ItemPath)’,$_.Line,$_.Column,$_.Severity,$_.Message }}”
@Robert: Absolutely! FxCop is a static code analysis tool that's used with compiled C# object code. In a PowerShell context, it's used for analyzing the design and quality of compiled C# (i.e. binary) cmdlets.
On other hand, the PowerShell Script Analyzer is used for analyzing scripts, functions, cmdlets, and (soon) DSC resources that are written using PowerShell scripting (i.e. the actual PowerShell language).
In short, FxCop understands .dlls, and the Script Analyzer understands .ps1, .psm1, etc.
-Joey
PM, PowerShell
Can you talk about the similarities/differences of when you would use this versus the FxCop Rules for PowerShell (blogs.msdn.com/…/testing-cmdlets-with-fxcop.aspx)?
I understand that WMF5 preview doesn't install on Windows 10 and I guess (hope!) we'll all get the latest bits in the next preview release of Windows.
However, I thought the whole idea of moving PowerShell out of the core OS and putting into a separate download was to enable faster releases. Is there any reason why you wouldn't support W10? I guess it reduces the 'spurious bugs that are caused by a pre-release OS and aren't PS problems' – but wouldn't the feedback be useful anyway?
Here is what I get with the proposed fix :
PS C:Usersme> Invoke-ScriptAnalyzer -Path "mylongscript.ps1" -ExcludeRule "AvoidUsingFilePath", "AvoidUnloadableModule"
Invoke-ScriptAnalyzer : An item with the same key has already been added.
At line:1 char:1
+ Invoke-ScriptAnalyzer -Path …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-ScriptAnalyzer], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.Windows.Powershell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand
@sodawillow and @Illegal characters in path:
This is a bug in the Feb. WMF release caused by the rules AvoidUsingFilePath and AvoidUnloadableModule. For now you can run Invoke-ScriptAnalyzer -ExcludeRule "AvoidUsingFilePath","AvoidUnloadableModule" to disable to rules.
It will be fixed in the coming release. Sorry for the confusion!
Hi,
I threw it on my scripts folder and get this error with most of them:
"Invoke-ScriptAnalyzer : Illegal characters in path."
At line:1 char:65
+ … ame -foregroundcolor green; Invoke-ScriptAnalyzer -Path $_.FullName }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-ScriptAnalyzer], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.Windows.Powershell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand
But some of them work fine. I couldn't figure what could be the reason. The line/char are not related to the script but to the command which does not make sense.
Try-catch-finally is awesome, but what about all those pesky Exchange remote PowerShell cmdlets? Implicit remoting/'no language' mode gives you no real choice as to what to use for error handling. Would be nice if you guys collaborate with the Exchange team and put some official guidance on that.
Hi,
That's a very nice addition, but I does not seem to run with the main script I'm working on (about 3k lines), it throws this :
Invoke-ScriptAnalyzer : Illegal characters in path.
At line:1 char:1
+ Invoke-ScriptAnalyzer -Path "C:UsersmeDesktoptest.ps1"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-ScriptAnalyzer], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.Windows.Powershell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand
How can I avoid this ? IT works with my other simpler scripts, but not this one.
The path is good, and the file runs in ISE or in the console.
Thanks for your help and all the work : )
@Adrian
Our guidance on using secure types is following:
1) Use PSCredential for username password combos
2) Use SecureString for other single items that might be considered sensitive.
Not sure when it might be less than desirable.
@Francois – Thanks! We hope greater community involvement will make the ScriptAnalyzer even better
@Willy – We will be releasing updates to ScriptAnalyzer on a regular cadence – WMF. We need the community to self-host and help us improve the experience.
@ ILYA
Can you share your OS build information?
Get-Item 'HKLM:SOFTWAREMicrosoftWindows NTCurrentVersion'
After installing Windows Management Framework 5.0 Preview February 2015 I cannot find PSScriptAnalyzer. The $env:ProgramFiles/WindowsPowerShell/Modules is empty dir. (Windows 8.1 x64)
How close are we into having this in production?
Awesome work guys!
Under what circumstances is the usage of the PSCredential Type less than desirable?