Monday, September 28, 2015

PSScriptAnalyzer - Automate code checking for your PowerShell projects as part of TFS build

With WMF 5.0 we can make use of the PowerShell static code checker utility PSScriptAnalyzer to perform code analysis on PowerShell script files and 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.

You can download the entire module from PowerShell gallery using the Install-Module cmdlet or create a pull request on the project at Github and build it yourself. Once you have the module available on the workstation you can perform code checking on PowerShell files by using the Invoke-ScriptAnalyzer cmdlet.

By making use of this cmdlets and the post-build script options from TFS build, you can now perform code checking on the PowerShell projects from the build server and choose to fail the build based on the results. Let’s see how this works.

First we need a function to do code checking using the Invoke-ScriptAnalyzer cmdlet and parse the results. The below content describes the process

function Get-DiagnosticIssue
{
       param
       (
              [Parameter(Mandatory=$true)]
              [ValidateNotNullOrEmpty()]
              [ValidateScript({Test-Path $_})]
              [string] $Path,

              [Parameter(Mandatory=$true)]
              [ValidateNotNullOrEmpty()]
              [string] $Severity,

              [bool] $FailOnIssues = $false
       )

       $logDirectory = Join-Path $Env:TF_BUILD_DROPLOCATION "Logs"
       if(-not(Test-Path $logDirectory -ErrorAction SilentlyContinue))
       {
              New-Item -ItemType Directory -Force -Path $logDirectory
       }
       $logFile = Join-Path $logDirectory "PSScriptAnalyzerLogs.log"

       Invoke-ScriptAnalyzer -Path $Path -Recurse |? {$_.Severity -ge $Severity} |fl | Out-File $logFile

       if($FailOnIssues)
       {
              if((Get-Content $logFile) -ne $null)
              {
                     $Host.UI.WriteErrorLine("PSScriptAnalyzer check failed. Please refer to the file $($logFile) for more details.")
              }
       }
}

Export-ModuleMember *

The Get-DiagnosticIssue function accepts a Severity parameter to filter the issues logged and a Path variable to perform analysis on all the scripts files and modules under that location. The FailOnIssues parameter can be used to decide whether the build should be failed or not. This module is called from a script file that is used as the post-build script path in the TFS build definition.

param
(
       [string] $Severity = "Information",
       [string] $Filter,
       [string] $FailOnIssues = "False"
)

$exitCode = 0
trap
{
    $Host.UI.WriteErrorLine($error[0].Exception.Message)
    $Host.UI.WriteErrorLine($error[0].Exception.StackTrace)
    if ($exitCode -eq 0)
    {
        $exitCode = 1
    }
}

$scriptName = $MyInvocation.MyCommand.Name
$scriptPath = Split-Path -Parent (Get-Variable MyInvocation -Scope Script).Value.MyCommand.Path

Push-Location $scriptPath
Import-Module .\PSScriptAnalyzerExtensions.psm1

$source = $env:TF_BUILD_BUILDDIRECTORY

if(-not ([string]::IsNullOrWhiteSpace($Filter)))
{
       $source = Get-ChildItem -Directory $env:TF_BUILD_BUILDDIRECTORY -Filter $Filter -Recurse |? {$_.FullName.ToUpper().Contains("Solution\$Filter".ToUpper())} | select -expand FullName
}

$failBuild = $FailOnIssues -eq "True"

Get-DiagnosticIssue -Path $source -Severity $Severity -FailOnIssues:$failBuild

Pop-Location

exit $exitCode

This script file is used in the process template as post-build script path with arguments as given below.



Next time the build is queued, it will trigger a code check using PSScriptAnalyzer and you can see the results in the log file generated.