Microsoft PowerShell Support for Pipeline

    I am pleased to announce Microsoft PowerShell support for Jenkins Pipeline! As of Durable Task 1.14 and Pipeline Nodes and Processes Plugin 2.12, you will now be able to run Microsoft PowerShell scripts directly in your Jenkins Pipeline projects. This blog post covers the basics of getting started with Microsoft PowerShell in Pipeline and provides some basic examples.

    Introduction to Microsoft PowerShell

    PowerShell is Microsoft’s open source and cross platform command line shell, as well as an automation and configuration tool/framework which has a broad user base. PowerShell can be used to perform common system administration tasks in Windows, macOS, and Linux environments. It can also be used as a general purpose scripting language. Now that Jenkins Pipeline supports PowerShell, you can enjoy the rich set of features in PowerShell for your daily DevOps work.

    Before diving into using PowerShell in your Pipeline, I recommend reading the Windows PowerShell Reference as well as the PowerShell Team Blog for an introduction to PowerShell features, utilities, and as a quick look into the PowerShell language. Microsoft also has an active PowerShell community on GitHub, which I highly recommend visiting to submit feature requests and bug reports as you see fit. Jenkins Pipeline currently supports Microsoft PowerShell 3.0 or higher, so also be sure to check which version of PowerShell is installed on your system in order to take advantage of PowerShell in your Pipeline. Please note that we recommend that you upgrade to the latest stable version of PowerShell available, which as of this writing is version 5.1.14393.

    The powershell step

    node {
        powershell 'Write-Output "Hello, World!"'
    }

    Using Microsoft PowerShell in Pipeline

    Writing PowerShell code as part of your pipeline is incredibly simple. The step that you will use is simply powershell, and it includes the same optional parameters as the Windows Batch (bat) step, including:

    • returnStdout: Returns the standard output stream with a default encoding of UTF-8 (alternative encoding is optional)

    • returnStatus: Returns the exit status (integer) of the PowerShell script

    Examples

    Capture exit status of a PowerShell script

    node {
        def status = powershell(returnStatus: true, script: 'ipconfig')
        if (status == 0) {
            // Success!
        }
    }

    Capture and print the output of a PowerShell script

    node {
        def msg = powershell(returnStdout: true, script: 'Write-Output "PowerShell is mighty!"')
        println msg
    }

    Which streams get returned when I use returnStdout?

    Until the release of PowerShell 5, there were five distinct output streams. PowerShell 5 introduced a sixth stream for pushing "informational" content, with the added benefit of being able to capture messages sent to Write-Host. Each row of the following table describes a PowerShell stream along with the corresponding Cmdlet used for writing to the stream for that particular row. Please keep in mind that stream 6 and associated cmdlets either do not exist or exhibit alternate behavior in versions of PowerShell earlier than version 5.

    Stream Description Cmdlet

    1

    Output stream (e.g. stdOut)

    Write-Output

    2

    Error stream (e.g. stdErr)

    Write-Error

    3

    Warning stream

    Write-Warning

    4

    Verbose stream

    Write-Verbose

    5

    Debug stream

    Write-Debug

    6

    Information stream

    Write-Information (or Write-Host with caveats)

    If you are using the returnStdout option of the powershell Pipeline step then only stream 1 will be returned, while streams 2-6 will be redirected to the console output. For example:

    Write to all available streams and return the standard output

    node {
        def stdout = powershell(returnStdout: true, script: '''
            # Enable streams 3-6
            $WarningPreference = 'Continue'
            $VerbosePreference = 'Continue'
            $DebugPreference = 'Continue'
            $InformationPreference = 'Continue'
    
            Write-Output 'Hello, World!'
            Write-Error 'Something terrible has happened!'
            Write-Warning 'Warning! There is nothing wrong with your television set'
            Write-Verbose 'Do not attempt to adjust the picture'
            Write-Debug 'We will control the horizontal.  We will control the vertical'
            Write-Information 'We can change the focus to a soft blur or sharpen it to crystal clarity.'
        ''')
        println stdout
    }
    Console output:
    [Pipeline] {
    [Pipeline] powershell
    [TestStreams] Running PowerShell script
    <Jenkins Home>\workspace\TestStreams@tmp\durable-4d924c2d\powershellScript.ps1 : Something terrible has
    happened!
    At <Jenkins Home>\workspace\TestStreams@tmp\durable-4d924c2d\powershellMain.ps1:2 char:1
    + & '<Jenkins Home>\workspace\TestStreams@tmp\durable-4d924c ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
        + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,powershellScript.ps1
    
    Warning! There is nothing wrong with your television set
    Do not attempt to adjust the picture
    We will control the horizontal.  We will control the vertical
    We can change the focus to a soft blur or sharpen it to crystal clarity.
    Hello, World!
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] End of Pipeline
    ERROR: script returned exit code 1
    Finished: FAILURE

    Note that "Hello, World!" gets printed last even though it is the first output statement in my script. Another interesting aspect of this example is that the powershell step failed, which ultimately caused the job to fail. The failure in this example is due to the PowerShell error stream being non-empty, which therefore caused the step to result in a non-zero exit status. However, as you will soon discover, there are a variety of causes for a failing powershell step.

    What causes a failing exit status?

    When you execute a powershell step, it may produce a non-zero exit code and fail your pipeline build. This is very similar to other shell steps with some interesting caveats. Your powershell step may produce a failing exit status in the following instances:

    1. Something in your PowerShell script has thrown an exception

    2. Your PowerShell script explicitly calls exit with a non-zero exit code

    3. Your PowerShell script calls a native application that produces a non-zero $LastExitCode

    4. Your PowerShell script results in a non-empty error stream (with or without throwing an exception)

    Overriding the exit status behavior of your powershell step can be achieved by explicitly exiting from your script as long as the failure was not caused by an unhandled exception. For example:

    Unavoidable failure caused by an unhandled exception

    node {
        powershell '''
            throw 'Error! Problem Exists Between Keyboard And Chair'
            exit 0  # Unreachable code
        '''
    }

    Failed step caused by a non-empty error stream

    node {
        powershell '''
            Write-Error 'Error! Problem Exists Between Keyboard And Chair'
        '''
    }

    Failure prevented by an explicit exit

    node {
        powershell '''
            Write-Error 'Error! Problem Exists Between Keyboard And Chair'
            exit 0
        '''
    }

    Scripts vs. Cmdlets

    A Cmdlet is a small lightweight utility written in either C#, and compiled, or written in PowerShell directly. Depending on what your goal is in your pipeline you can make use of Cmdlets directly in your pipeline code, call a self contained PowerShell script, or some mixture of the two. If your strategy is to keep each powershell step as short and succinct as possible then it may make sense for you to write a library of Cmdlets, but if you have monolithic scripts then it may make sense for you to call those scripts directly from your pipeline. The choice is entirely up to you, as both scenarios are supported.

    Thanks for reading, and have fun!

    I sincerely hope that this post has encouraged you to try using PowerShell in your Jenkins Pipeline. Please do not hesitate to file an issue against the durable-task plugin on JIRA if you have discovered any problem that you suspect is related to the powershell step. For general PowerShell related issues or inquiries please route your questions to the PowerShell community.

    About the Author
    Gabriel Loewen
    Gabriel Loewen

    This author has no biography defined. See social media links referenced below.