PowerShell Genie

PowerShell in CI/CD

PowerShell in a pipeline needs three things the interactive console gives you for free: a non-zero exit code on failure, no interactive prompts, and no dependency on a profile.

Lesson 2 min read Windows PowerShell 5.1 and PowerShell 7

Documentation: Microsoft Learn

Overview

Exit codes are the one that catches people. A PowerShell error does not set the exit code by itself, so a script that logs a failure and ends normally reports success to the build. Set $ErrorActionPreference = 'Stop' at the top and call exit 1 in your catch block, or the pipeline will go green while nothing worked.

Run with -NoProfile and -NonInteractive. A profile that works on your machine is not present on the runner, and any cmdlet that prompts will hang the job until it times out.

Pin your module versions with -RequiredVersion. A pipeline that installs the latest version of a module is a pipeline that can break without anyone changing anything, and the failure will look unrelated to the real cause.

Secrets come from the CI system's secret store as environment variables. Never in the script, never in the repository. Also be careful what you echo, because build logs are usually readable by more people than the repository is.

Invoke-Pester -CI sets the exit code and emits test results in a format the CI system can display.

Inspect this example’s structure

Read the command and its supplied arguments. This example requires real PowerShell or its module; this diagram does not execute it.

$stage = @( [pscustomobject]@{ Step = 1;Name = 'Analyse';Command = 'Invoke-ScriptAnalyzer -Path . -Severity Error' } [pscustomobject]@{ Step = 2;Name = 'Test';Command = 'Invoke-Pester -CI' } [pscustomobject]@{ Step = 3;Name = 'Publish';Command = 'Publish-Module -Path . -NuGetApiKey $env:KEY' }) $stage | Format-Table Step, Name, Command
  1. Stage 1$stagePositional values or no named parameters
  2. Stage 2Format-TablePositional values or no named parameters

    An example to inspect

    Preview the example. Sign in for the full lesson and activities.

    $stage = @(
    [pscustomobject]@{ Step = 1;Name = 'Analyse';Command = 'Invoke-ScriptAnalyzer -Path . -Severity Error' }
    [pscustomobject]@{ Step = 2;Name = 'Test';Command = 'Invoke-Pester -CI' }
    [pscustomobject]@{ Step = 3;Name = 'Publish';Command = 'Publish-Module -Path . -NuGetApiKey $env:KEY' })
    $stage | Format-Table Step, Name, Command

    What happens

    This lays out the three stages in order. Analyse fails the build on error-severity findings before tests even run, which is the cheapest gate. Test uses -CI so the exit code and result file are handled. Publish reads its key from the environment rather than holding it, which is the only acceptable place for it.

    Worth remembering

    • Set $ErrorActionPreference = 'Stop' and exit 1 on failure, or the build stays green.
    • Use -NoProfile and -NonInteractive on the runner.
    • Pin module versions with -RequiredVersion.
    • Secrets come from the CI secret store, and build logs are widely readable.

    Primary documentation

    PowerShell errors and debugging documentation

    Try it

    Illustrative output · values vary by environment

    1PS> $stage = @(
    2PS> [pscustomobject]@{ Step = 1;Name = 'Analyse';Command = 'Invoke-ScriptAnalyzer -Path . -Severity Error' }
    3PS> [pscustomobject]@{ Step = 2;Name = 'Test';Command = 'Invoke-Pester -CI' }
    4PS> [pscustomobject]@{ Step = 3;Name = 'Publish';Command = 'Publish-Module -Path . -NuGetApiKey $env:KEY' })
    5PS> $stage | Format-Table Step, Name, Command

    Example reader

    Inspect this page's example

    Fictional browser data when supported

    Run supported commands against the practice data, or inspect the example without executing it.

    Ready to inspect.

      Lays out the three CI stages with the flags that make each behave.

      Show reference output

      Illustrative output · values vary by environment

      Step  Name     Command
      ----  ----     -------
      1     Analyse  Invoke-ScriptAnalyzer -Path . -Severity Error
      2     Test     Invoke-Pester -CI
      3     Publish  Publish-Module -Path . -NuGetApiKey $env:KEY

      Keep going

      Next: PowerShell Script governance
      Genie guidePowerShell in CI/CD

      PowerShell in a pipeline needs three things the interactive console gives you for free: a non-zero exit code on failure, no interactive prompts, and no dependency on a profile.