Getty Images/iStockphoto

Automate workflows by running PowerShell in GitHub Actions

By running PowerShell scripts in GitHub Actions workflows, admins can automate common DevOps and IT management tasks. Get started with these hands-on examples.

GitHub might seem like just a fancy user interface over Git repositories, but with features such as GitHub Actions, it can be so much more. And when you add PowerShell to the mix, you gain the ability to automate any of your current workflows.

PowerShell can be used for typical DevOps workflows, such as code building and deployment, as well as automation on platforms such as Microsoft Azure and Exchange. By running PowerShell scripts in GitHub Actions, DevOps and IT teams can automate CI/CD pipeline tasks, deploy and manage cloud infrastructure, and perform traditional PowerShell tasks such as Exchange management.

What is GitHub Actions?

GitHub Actions is a DevOps pipeline tool that orchestrates actions centered around a code repository. This includes CI/CD-related tasks -- such as code testing, compilation and deployment -- that can be triggered during a normal development cycle through pull requests and code pushes.

Because GitHub is primarily a code repository, GitHub Actions are defined in YAML and stored as just another file in your repository. The actions used in this tutorial are pre-built; although this article doesn't cover how to write an entire GitHub Actions workflow, you can also use community-built or custom actions inside your workflow.

Using PowerShell in GitHub Actions

The easiest way to run PowerShell inside GitHub Actions is to call a script or run inline code using the default action and specifying the PowerShell shell.

For example, to run PowerShell code included in your YAML file, use the following syntax.

- name: Build Module
  shell: pwsh
  run: Invoke-Build -Task ModuleBuild

Although this example uses the Invoke-Build module to build a PowerShell module, you can also specify arbitrary code.

Notice that the shell value is set to pwsh, referencing PowerShell 7. If you're running your workflow on a Windows host, you also have access to the powershell shell, which refers to Windows PowerShell.

Specify the OS for the job by setting the runs-on property, as shown below.

jobs:
  PowerShellDemo:
    name: "PowerShell Demo"
    runs-on: ubuntu-latest

    steps:
      - name: Build Module
        shell: pwsh
        run: Invoke-Build -Task ModuleBuild

This example uses the latest version of Ubuntu, so assume that all other code in this tutorial is written to execute on ubuntu-latest.

To write out a multiline script, use the pipe character on the run: line, then indent the subsequent lines of your script as shown below.

- name: Get contents of files
  shell: pwsh
  run: |
    foreach ($file in (Get-ChildItem ./path/to/files/*.txt)) {
      Write-Host "File: $($file.Name)"
      Get-Content $file.FullName
    }

Setting workflow variables

In a GitHub Actions workflow, you can also set a workflow variable by writing output using the following format, replacing varName with the name of the variable to set and varValue with the variable's value.

::set-output name=varName::varValue

To do this in PowerShell, use the following syntax.

- name: Set pipeline variable
  id: set_pipeline_variable
  shell: pwsh
  run: |
    $content = Get-Content ./path/to/file.json
    Write-Output ::set-output name=jsonData::$content

The above script reads the content of a JSON file and sets the value of the jsonData variable to that content. The data in that variable can then be accessed further down the pipeline by referencing ${{steps.set_pipeline_variable.outputs.jsonData}}.

Calling script files

Executing PowerShell in a GitHub Actions workflow, as shown in the above example, has a downside: The code is inside the YAML file. While this doesn't prevent the file from running or directly cause problems, longer scripts can make the YAML file difficult to read.

An alternative approach is to call script files, which you can do using the syntax below. This is the recommended way to run complicated scripts inside a pipeline.

- name: Build Module
  shell: pwsh
  run: ./build.ps1 -Task ModuleBuild

This example involves running a PowerShell ModuleBuild script and passing a parameter to it. This executes the script as expected, with the Task parameter set to ModuleBuild.

Using GitHub Actions for common PowerShell management tasks

PowerShell is often used in GitHub Actions to work with traditionally PowerShell-managed platforms, such as Microsoft Azure and Exchange.

Example: Microsoft Azure

Microsoft provides Azure PowerShell-specific actions, which makes it easy to run PowerShell commands against Azure.

The first step is to create an Azure service principal with the correct permissions and then collect the following information in JSON format.

{
   "clientId": "<GUID>",
   "clientSecret": "<STRING>",
   "subscriptionId": "<GUID>",
   "tenantId": "<GUID>"
}

Store the collected information in the repository as a secret -- in this example, AZURE_CREDENTIALS. Then, in the workflow, use the azure/login action as shown below.

- name: Login via Az module
  uses: azure/login@v1
  with:
    creds: ${{secrets.AZURE_CREDENTIALS}}
    enable-AzPSSession: true

You can then use the azure/powershell action without worrying about authentication.

- name: Create an RG and SA
  uses: azure/powershell@v1
  with:
    inlineScript: |
      $rg = New-AzResourceGroup -Name 'New Resource Group' -Location 'West US 2'
      New-AzStorageAccount -ResourceGroupName $rg.Name -Location $rg.Location -Name 'superawesomedemosa' -SkuName Standard_LRS
    azPSVersion: "latest"

Example: Microsoft Exchange Online

There are no Exchange-specific GitHub Actions, so authenticating to Exchange Online with a GitHub Actions workflow that calls a PowerShell script can be a hassle. Fortunately, Microsoft provides a thorough overview of how to do so.

The following example uses certificate authentication. Once you have an application in Azure Active Directory with the right roles and a certificate, convert the certificate in PFX format to Base64 and store it in a GitHub secret.

Use the following script to convert the certificate file to Base64 using your preferred PowerShell version.

# PowerShell 6+
[System.Convert]::ToBase64String((Get-Content '<certpath>' -AsByteStream -Raw))

# Windows PowerShell 5.1
[System.Convert]::ToBase64String((Get-Content '<certpath>' -Encoding Bytes))

Copy the output into a repository secret -- in this example, EXCHANGE_CERTIFICATE. Store the certificate's password as CERTIFICATE_PASSWORD and the ID of the application you created as EXCHANGE_APPLICATION_ID.

Then, in GitHub Actions, use the following script to read the certificate and authenticate to Exchange Online, where you can then perform your Exchange tasks.

- name: Work with Exchange
  shell: pwsh
  env:
    exchangeCert: ${{ secrets.EXCHANGE_CERTIFICATE }}
    certPass: ${{ secrets.CERTIFICATE_PASSWORD }}
    appId: ${{ secrets.EXCHANGE_APPLICATION_ID }}
  run: |
    [System.IO.File]::WriteAllBytes('.\cert.pfx',[System.Convert]::FromBase64String($env:exchangeCert))
    $splat = @{
      CertificateFilePath = '.\cert.pfx'
      CertificatePassword = (ConvertTo-SecureString $env:certPass -AsPlainText -Force)
      AppID = $env:appId
      Organization = 'myorg.onmicrosoft.com'
    }
    Connect-ExchangeOnline @splat
    Get-ExoMailbox [email protected]

This example uses the env: parameter to pass the secrets as environment variables to the PowerShell script. However, you could also store your script in a PS1 file and call that file instead of storing your code inline. This is highly recommended if your script is more than a few lines.

Next Steps

Compare Azure DevOps vs. GitHub for CI/CD pipelines

Deploy Azure landing zones using Terraform

Dig Deeper on Systems automation and orchestration