Skip to content

Checkstyle Standards for Powershell Modules #63

@jborean93

Description

@jborean93

Proposal: Checkstyle Standards for Powershell Modules

Author: Jordan Borean @jborean93

Date: 2017/05/21

  • Status: New
  • Proposal type: core design
  • Targeted Release: 2.4
  • Associated PR: TBC
  • Estimated time to implement: 4 weeks

Motivation

Trying to keep the Powershell modules consistent with each other and define
a standard that is enforcable without human intervention.

Problems

Some of the current problems we have today are;

  • There is a mixture of ways Powershell modules are written which causes confusion and fragmentation
  • The Python modules use PEP for standards but no automated checked are in place for Powershell
  • There is also no official standard for how we write these modules, this proposal will both create that standard and enforce it

Solution proposal

The proposed solution is to use 2 Powershell modules called PSScriptAnalyzer
and Pester to run a check style analysis
as well as generate reports on this analysis in a CI like fashion. These tools
should run on the module affected on every pull request.

PSScriptAnalyzer is a tool that can run checkstyle analysis on powershell
scripts based on a custom ruleset. It has the ability to specify rules that you
wish to scan for as well as writting custom rules if the in built ones don't
match your requirements.

Pester is a framework for running tests that can easily integrate in a CI
system. While it has a lot of features we can use in the future like unit
testing for powershell modules and code coverage reporting this proposal is
limited to using Pester to run PSScriptAnalyzer and generate an NUnit XML
report at the end of the process. There are numerous tools out there to convert
an NUnit xml to HTML for reporting purposes.

While these modules need to run in Powershell with the ability to install
Powershell 5 on both Windows and Unix we have various options available to us
when running these tests.

The following rule settings for PSScriptAnalyzer is as follows

@{
    IncludeRules = @(
        'PSAvoidUsingCmdletAliases',
        'PSAvoidUsingEmptyCatchBlock',
        'PSAvoidUsingPositionalParameters',
        'PSAvoidUsingWMICmdlet',
        'PSAvoidUsingWriteHost',
        'PSMisleadingBacktick',
        'PSPlaceCloseBrace',
        'PSPlaceOpenBrace',
        'PSReservedCmdletChar',
        'PSReservedParams',
        'PSUseConsistentIndentation',
        'PSUseConsistentWhitespace'
    )

    Rules = @{
        PSAvoidUsingCmdletAliases = @{
            Whitelist = @('')
        }
        PSPlaceCloseBrace = @{
            Enable = $true
            NoEmptyLineBefore = $true
            IgnoreOneLineBlock = $false
            NewLineAfter = $false
        }
        PSPlaceOpenBrace = @{
            Enable = $true
            OnSameLine = $true
            IgnoreOneLineBlock = $false
            NewLineAfter = $true
        }
        PSUseConsistentIndentation = @{
            Enable = $true
            IndentationSize = 4
        }
        PSUseConsistentWhitespace = @{
            Enable = $true
            CheckOpenBrace = $true
            CheckOpenParen = $true
            CheckOperator = $true
            CheckSeparator = $true
        }
    }
}

The documentation for these rules can be found here. These rules are
open to debug they are just what I believe we should be aiming for but happy to
add/remove/modify rules based on a consensus with the community.

The second part of this proposal is to go through our existing modules and
ensure they conform to this new standard. Because of the different coding
styles in place it is hard to.

As well as implementing these checks into the existing testing process we will
also need to modify the existing modules to ensure they pass the new checks
added.

An example of the output of running this check on an existing module like
win_acl

PS C:\Users\jbore> powershell.exe -File C:\dev\ansible\test\runner\powershell-pester.ps1

Describing Testing against PSSA rules
   Context PSSA Ansible Rules

RuleName                            Severity     ScriptName Line  Message
--------                            --------     ---------- ----  -------
PSAvoidUsingCmdletAliases           Warning      win_acl.ps 67    'where' is an alias of 'Where-Object'. Alias can
                                                 1                introduce possible problems and make scripts hard to
                                                                  maintain. Please consider changing alias to its full
                                                                  content.


    [-] Should pass PSAvoidUsingCmdletAliases 1.03s
      Expected: {0}
      But was:  {1}
      13:                     $failures.Count | Should Be 0
      at <ScriptBlock>, C:\dev\ansible\test\runner\pester-tests.ps1: line 13
    [+] Should pass PSAvoidUsingEmptyCatchBlock 127ms
    [+] Should pass PSAvoidUsingPositionalParameters 19ms

RuleName                            Severity     ScriptName Line  Message
--------                            --------     ---------- ----  -------
PSAvoidUsingWMICmdlet               Warning      win_acl.ps 67    File 'win_acl.ps1' uses WMI cmdlet. For PowerShell 3.0 and
                                                 1                above, use CIM cmdlet which perform the same tasks as the
                                                                  WMI cmdlets. The CIM cmdlets comply with WS-Management
                                                                  (WSMan) standards and with the Common Information Model
                                                                  (CIM) standard, which enables the cmdlets to use the same
                                                                  techniques to manage Windows computers and those running
                                                                  other operating systems.


    [-] Should pass PSAvoidUsingWMICmdlet 30ms
      Expected: {0}
      But was:  {1}
      13:                     $failures.Count | Should Be 0
      at <ScriptBlock>, C:\dev\ansible\test\runner\pester-tests.ps1: line 13
    [+] Should pass PSAvoidUsingWriteHost 25ms
    [+] Should pass PSMisleadingBacktick 20ms

RuleName                            Severity     ScriptName Line  Message
--------                            --------     ---------- ----  -------
PSPlaceCloseBrace                   Warning      win_acl.ps 41    Close brace does not follow a non-empty line.
                                                 1


    [-] Should pass PSPlaceCloseBrace 28ms
      Expected: {0}
      But was:  {1}
      13:                     $failures.Count | Should Be 0
      at <ScriptBlock>, C:\dev\ansible\test\runner\pester-tests.ps1: line 13

RuleName                            Severity     ScriptName Line  Message
--------                            --------     ---------- ----  -------
PSPlaceOpenBrace                    Warning      win_acl.ps 29    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 37    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 39    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 43    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 48    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 54    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 59    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 65    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 69    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 74    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 78    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 82    Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 100   Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.
PSPlaceOpenBrace                    Warning      win_acl.ps 238   Open brace not on same line as preceding keyword. It should
                                                 1                be on the same line.


    [-] Should pass PSPlaceOpenBrace 75ms
      Expected: {0}
      But was:  {14}
      13:                     $failures.Count | Should Be 0
      at <ScriptBlock>, C:\dev\ansible\test\runner\pester-tests.ps1: line 13
    [+] Should pass PSReservedCmdletChar 28ms
    [+] Should pass PSReservedParams 20ms
    [+] Should pass PSUseConsistentIndentation 23ms

RuleName                            Severity     ScriptName Line  Message
--------                            --------     ---------- ----  -------
PSUseConsistentWhitespace           Warning      win_acl.ps 29    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 37    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 39    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 43    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 48    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 54    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 59    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 65    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 69    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 74    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 78    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 82    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 87    Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 100   Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 238   Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 282   Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 297   Use space before open brace.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 282   Use space before open parenthesis.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 297   Use space before open parenthesis.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 198   Use space before and after binary and assignment operators.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 199   Use space before and after binary and assignment operators.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 200   Use space before and after binary and assignment operators.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 264   Use space before and after binary and assignment operators.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 267   Use space before and after binary and assignment operators.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 107   Use space after a comma.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 225   Use space after a comma.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 226   Use space after a comma.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 229   Use space after a comma.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 229   Use space after a comma.
                                                 1
PSUseConsistentWhitespace           Warning      win_acl.ps 295   Use space after a comma.
                                                 1


    [-] Should pass PSUseConsistentWhitespace 142ms
      Expected: {0}
      But was:  {30}
      13:                     $failures.Count | Should Be 0
      at <ScriptBlock>, C:\dev\ansible\test\runner\pester-tests.ps1: line 13
Tests completed in 1.58s
Passed: 7, Failed: 5, Skipped: 0, Pending: 0, Inconclusive: 0

PS C:\Users\jbore> $LASTEXITCODE
5

There are some very basic scripts I created for the output above. You can see
them here.

Dependencies

  • Powershell 5.0
  • PSScriptAnalyzer
  • Pester

Testing

These tests only need to run on the module that is affected in the PR, there is
no reason that we need to run it on all modules but that is possible if we wish
to go down that path.

Documentation

New docs page created that will show the rules and examples that we are
enforcing as well as some guidelines on how to run these tests locally
before raising a PR.

Anything else?

While this is not part of the scope for this proposal I believe using a library
like Pester brings us a step forward to implementing unit tests on our
powershell modules and bringing more in line with our processes around Python
modules.

This is my first proposal so let me know if I have missed anything or whether I
have done something wrong.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions