PSScriptAnalyzer–FxCop for PowerShell script

As a C and now C# developer I’ve always liked getting help with my code in the form of either Lint for C or code analysis a.k.a. FxCop for C#.  These tools are great at pointing out real and potential issues that are usually worth addressing.  As the old manufacturing saying goes, the earlier you catch a problem, the cheaper it is to fix.  This is also true for software including shell scripts.  Bugs can range from benign to frustrating to downright  catastrophically damaging.  I had a co-worker many years ago write a Korn shell script with a line like this:

rm –rf $folder

At one point, he made a change to the script that resulted in $folder having only “/” assigned to it.  That wasn’t the intention.  Needless to say, he and our system adminstrator, weren’t too happy the next day.

I’m very excited about PSScriptAnalyzer and its potential to help folks improve their scripts by having the collective wisdom of the PowerShell team and greater community embodied in rules that can detect potential problems before they become real problems.  Problems that can potentially take hours to track down. 

The following is the about topic for PSScriptAnalyzer that I wrote.  Hopefully it gives you a good understanding of how to use PSScriptAnalyzer.  It also demonstrates that the PSScriptAnalyzer team is taking community contributions via pull requests on GitHub specifically at http://github.com/powershell/psscriptanalyzer

As you try the script analyzer on your scripts, keep in mind that it is the early days for this tool.  You’ll likely find issues that indicate a bug in the tool rather than your script.  Please submit these as issues on the PSScriptAnalyzer GitHub site.  And if you’re “that” type, fix the issue yourself and submit a PR.  As we all make the tool better, every user benefits.

The team holds a community meeting once every three weeks or so – at least for these early stages of the development of PSScriptAnalyzer.  These meetings are open to the community.  You can catch up on past meetings via the meeting notes on the project’s wiki.

BTW if you write PowerShell modules for the public, please consider writing an about topic for your module.  Module level about topics should be the primary/intro help topic folks can rely upon to find out information about a module. While folks can probably piece together the necessary information from the help topics of individual module commands, why make them do that when an about topic is so easy to write?

TOPIC
        about_PSScriptAnalyzer
        
SHORT DESCRIPTION
        PSScriptAnalyzer is a static code checker for PowerShell script.

LONG DESCRIPTION
        PSScriptAnalyzer checks the quality of Windows PowerShell script by evaluating
        that script against a set of rules.  The script can be in the form of a
        stand-alone script (.ps1 files), a module (.psm1, .psd1 and .ps1 files) or
        a DSC Resource (.psm1, .psd1 and .ps1 files).
        
        The rules are based on PowerShell best practices identified by the 
        PowerShell Team and the community. These rules can help you create more 
        readable, maintainable and reliable scripts. PSScriptAnalyzer generates 
        DiagnosticResults (errors and warnings) to inform you about potential script 
        issues, including the reason why there might be an issue, and provide you  
        with guidance on how to fix the issue.

        PSScriptAnalyzer is shipped with a collection of built-in rules that check 
        various aspects of PowerShell code such as presence of uninitialized 
        variables, usage of the PSCredential Type, usage of Invoke-Expression, etc.
         
        The following additional functionality is also supported:
        
        * Including and/or excluding specific rules globally
        * Suppression of rules within script
        * Creation of custom rules
        * Creation of loggers
        
RUNNING SCRIPT ANALYZER

        There are two commands provided by the PSScriptAnalyzer module, those are:
        
        Get-ScriptAnalyzerRule [-CustomizedRulePath <string[]>] [-Name <string[]>] 
                               [-Severity <string[]>] 
                               [<CommonParameters>]

        Invoke-ScriptAnalyzer  [-Path] <string> [-CustomizedRulePath <string[]>] 
                               [-ExcludeRule <string[]>] [-IncludeRule<string[]>] 
                               [-Severity <string[]>] [-Recurse] [-SuppressedOnly] 
                               [<CommonParameters>]

        To run the script analyzer against a single script file execute:
        
        PS C:\> Invoke-ScriptAnalyzer -Path myscript.ps1
        
        This will analyze your script against every built-in rule.  As you may find
        if your script is sufficiently large, that could result in a lot of warnings
        and/or errors. See the next section on recommendations for running against
        an existing script, module or DSC resource.
        
        To run the script analyzer against a whole directory, specify the folder
        containing the script, module and DSC files you want analyzed.  Specify
        the Recurse parameter if you also want sub-directories searched for files 
        to analyze.
        
        PS C:\> Invoke-ScriptAnalyzer -Path . -Recurse
        
        To see all the built-in rules execute:
        
        PS C:\> Get-ScriptAnalyzerRule
        
RUNNING SCRIPT ANALYZER ON A NEW SCRIPT, MODULE OR DSC RESOURCE

        If you have the luxury of starting a new script, module or DSC resource, it
        is in your best interest to run the script analyzer with all the rules 
        enabled.  Be sure to evaluate your script often to address rule violations as 
        soon as they occur.  
        
        Over time, you may find rules that you don't find value in or have a need to 
        explicitly violate.  Suppress those rules as necessary but try to avoid 
        "knee jerk" suppression of rules.  Analyze the diagnostic output and the part
        of your script that violates the rule to be sure you understand the reason for 
        the warning and that it is indeed OK to suppress the rule.  For information on 
        how to suppress rules see the RULE SUPPRESSION section below.
        
RUNNING SCRIPT ANALYZER ON AN EXISTING SCRIPT, MODULE OR DSC RESOURCE

        If you have existing scripts, they are not likely following all of these best 
        practices, practices that have just found their way into books, web sites, 
        blog posts and now the PSScriptAnalyer in the past few years.
        
        For these existing scripts, if you just run the script analyzer without
        limiting the set of rules executed, you may get deluged with diagnostics
        output in the form of information, warning and error messages.  You should 
        try running the script analyzer with all the rules enabled (the default) and
        see if the output is "manageable".  If it isn't, then you will want to "ease 
        into" things by starting with the most serious violations first - errors.
        
        You may be temtped to use the Invoke-ScriptAnalyzer command's Severity 
        parameter with the argument Error to do this - don't.  This will run every 
        built-in rule and then filter the results during output.  The more rules the 
        script analyzer runs, the longer it will take to analyze a file.  You can 
        easily get Invoke-ScriptAnalyzer to run just the rules that are of severity 
        Error like so:
        
        PS C:\> $errorRules = Get-ScriptAnalyzer -Severity Error
        PS C:\> Invoke-ScriptAnalyzer -Path . -IncludeRule $errorRules
        
        The output should be much shorter (hopefully) and more importantly, these rules
        typically indicate serious issues in your script that should be addressed.
        
        Once you have addressed the errors in the script, you are ready to tackle
        warnings.  This is likely what generated the most output when you ran the 
        first time with all the rules enabled.  Now not all of the warnings generated 
        by the script analyzer are of equal importance.  For the existing script 
        scenario, try running error and warning rules included but with a few rules 
        "excluded":
        
        PS C:\> $rules = Get-ScriptAnalyzerRule -Severity Error,Warning
        PS C:\> Invoke-ScriptAnalyzer -Path . -IncludeRule $rules -ExcludeRule `
                    PSAvoidUsingCmdletAliases, PSAvoidUsingPositionalParameters

        The PSAvoidUsingCmdletAliases and PSAvoidUsingPositionalParameters warnings 
        are likely to generate prodigious amounts of output.  While these rules have 
        their reason for being many existing scripts violate these rules over and 
        over again.  It would be a shame if you let a flood of warnings from these two 
        rules, keep you from addressing more potentially serious warnings.
        
        There may be other rules that generate a lot of output that you don't care 
        about - at least not yet.  As you examine the remaining diagnostics output, 
        it is often helpful to group output by rule.  You may decide that the one or 
        two rules generating 80% of the output are rules you don't care about.  You 
        can get this view of your output easily:
        
        PS C:\> $rules = Get-ScriptAnalyzerRule -Severity Error,Warning
        PS C:\> $res = Invoke-ScriptAnalyzer -Path . -IncludeRule $rules -ExcludeRule `
                          PSAvoidUsingPositionalParameters, PSAvoidUsingCmdletAliases
        PS C:\> $res | Group RuleName | Sort Count -Desc | Format-Table Count, Name
        
        This renders output like the following:
        
        Count Name
        ----- ----
           23 PSAvoidUsingInvokeExpression
            8 PSUseDeclaredVarsMoreThanAssigments
            8 PSProvideDefaultParameterValue
            6 PSAvoidUninitializedVariable
            3 PSPossibleIncorrectComparisonWithNull
            1 PSAvoidUsingComputerNameHardcoded
            
        You may decide to exclude the PSAvoidUsingInvokeExpression rule for the moment
        and focus on the rest, especially the PSUseDeclaredVarsMoreThanAssigments, 
        PSAvoidUninitializedVariable and PSPossibleIncorrectComparisonWithNull rules.
        
        As you fix rules, go back and enable more rules as you have time to address 
        the associated issues.  In some cases, you may want to suppress a rule at
        the function, script or class scope instead of globally excluding the rule.  
        See the RULE SUPPRESSION section below.
        
        While getting a completely clean run through every rule is a noble goal, it 
        may not always be feasible. You have to weigh the gain of passing the rule 
        and eliminating a "potential" issue with changing script and possibly 
        introducing a new problem.  In the end, for existing scripts, it is usually 
        best to have evaluated the rule violations that you deem the most valuable to 
        address.

RULE SUPPRESSSION

        Rule suppression allows you to turn off rule verification on a function, 
        scripts or class definition.  This allows you to exclude only specified 
        scripts or functions from verification of a rule instead of globally 
        excluding the rule.  

        There are several ways to suppress rules.  You can suppress a rule globally 
        by using the ExcludeRule parameter when invoking the script analyzer e.g.:
        
        PS C:\> Invoke-ScriptAnalyzer -Path . -ExcludeRule `
                    PSProvideDefaultParameterValue, PSAvoidUsingWMICmdlet
                   
        Note that the ExcludeRule parameter takes an array of strings i.e. rule names.
        
        Sometimes you will want to suppress a rule for part of your script but not for
        the entire script.  PSScriptAnalyzer allows you to suppress rules at the 
        script, function and class scope.  You can use the .NET Framework 
        System.Diagnoctics.CodeAnalysis.SuppressMesssageAttribute in your script 
        like so:
        
        function Commit-Change() {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", 
                                                               "", Scope="Function", 
                                                               Target="*")]
            param() 
        }
        
EXTENSIBILITY

        PSScriptAnalyzer has been designed to allow you to create your own rules via
        a custom .NET assembly or PowerShell module.  PSScriptAnalyzer also allows 
        you to plug in a custom logger (implemented as a .NET assembly).
        
CONTRIBUTE

        PSScriptAnalyzer is open source on GitHub:
        
        https://github.com/PowerShell/PSScriptAnalyzer
        
        As you run the script analyzer and find what you believe to be are bugs,
        please submit them to:
        
        https://github.com/PowerShell/PSScriptAnalyzer/issues
        
        Better yet, fix the bug and submit a pull request.
        
SEE ALSO
        Get-ScriptAnalyzerRule
        Invoke-ScriptAnalyzer
        Set-StrictMode        
        about_Pester
Advertisements
This entry was posted in PowerShell 5.0, PSScriptAnalyzer. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s