Hi there! πŸ‘‹

Recently I discovered Git Hooks. Git Hooks provide a way of running custom scripts when a certain git action occurs. In this post, I want to share a pre-commit Git Hook I’ve written to lint PowerShell code using the PSScriptAnalyzer module.

What is a Linter? πŸ•΅οΈβ€β™‚οΈ

A linter analyses code to identify common errors, bugs and stylistic issues. Their aim is to improve code quality. Linters perform static analysis meaning they check code without executing it. Some well known linters for other languages include ESLint and Pylint.

What is PSScriptAnalyzer?

PSScriptAnalyzer is a linter for PowerShell modules and scripts. It runs a set of rules against PowerShell code. These rules are based on best practices identified by the PowerShell team at Microsoft and the community. The full set of rules can be found here.

What is a pre-commit Git Hook?

A pre-commit Git Hook executes when running git commit. The contents of the pre-commit file located at .git/hooks/pre-commit is executed and if the script has an exit code of 1 (job failed), then the commit is aborted. Pre-commit hooks provide an excellent use case for linting code changes before pushing to a remote repository.

Linting PowerShell code using the pre-commit Git Hook πŸ”Ž

To use this pre-commit Git Hook you must have PowerShell 7, Git and the PSScriptAnalyzer module installed.

Install PSScriptAnalyzer:

Install-Module -Name "PSScriptAnalyzer" -Verbose

Pre-commit Git Hook

#!/usr/bin/env pwsh
Import-Module -Name "PSScriptAnalyzer"

$DiffNames = git --no-pager diff --name-only --staged --line-prefix="$(git rev-parse --show-toplevel)/"
$Results = @()

foreach ($DiffName in $DiffNames) {
    Write-Output -InputObject "Analysing ""$($DiffName)"""
    $Output = Invoke-ScriptAnalyzer -Path $DiffName
    $Results += $Output
}

if ($Results.Count -gt 0) {
    Write-Warning -Message "PSScriptAnalyzer identified one or more files with linting errors. Commit aborted. Fix them before committing or use 'git commit --no-verify' to bypass this check."
    foreach ($Result in $Results) {
        Write-Error -Message "$($Result.ScriptName) - Line $($Result.Line) - $($Result.Message)"
    }
    exit 1
}

Installation

Linux

  1. Create .git/hooks/pre-commit:

    touch .git/hooks/pre-commit
    
  2. Paste the PowerShell snippet above into .git/hooks/pre-commit

  3. Make .git/hooks/pre-commit executable:

    chmod +x .git/hooks/pre-commit
    

Windows

  1. Create .git/hooks/pre-commit.ps1 and .git/hooks/pre-commit:

    New-Item -Path ".git\hooks\pre-commit.ps1", ".git\hooks\pre-commit" -ItemType "File"
    
  2. Paste the PowerShell snippet above into .git/hooks/pre-commit.ps1

  3. Paste the snippet below into .git/hooks/pre-commit:

    #!/bin/sh
    pwsh -File "$(git rev-parse --show-toplevel)\.git\hooks\pre-commit.ps1"
    

Usage

Now lets see if it’s working! 😄

  1. Create a directory and initialise a new repository:

    mkdir ~/git-hook-pwsh
    cd ~/git-hook-pwsh
    git init
    
  2. Follow the installation steps above depending on your environment.

  3. Create a file called git-hook-pwsh.ps1 which uses Write-Host:

    echo 'Write-Host -Object "Oh no! Not the dreaded Write-Host!"' > git-hook-pwsh.ps1
    
  4. Stage git-hook-pwsh.ps1 and attempt to commit it:

    git add git-hook-pwsh.ps1; git commit -m "Is my pre-commit git hook working?"
    

If all goes well you should see an output similar to below in your terminal:

➜  git-hook-pwsh git:(master) βœ— git commit -m "Is my pre-commit git hook working?"
Analysing "/home/dab/git-hook-pwsh/git-hook-pwsh.ps1"
WARNING: PSScriptAnalyzer identified one or more files with linting errors. Commit aborted. Fix them before committing or use 'git commit --no-verify' to bypass this check.
Write-Error: git-hook-pwsh.ps1 - Line 1 - File 'git-hook-pwsh.ps1' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.

Awesome! It worked! ⭐

I’ve also posted this pre-commit Git Hook as a GitHub gist so others can find it! 😃

Enjoy! ✨ Until next time! 😄