DEV Community

Cover image for Automate your GitHub Issues with PowerShell and GitHub API
Olivier Miossec
Olivier Miossec

Posted on • Edited on

Automate your GitHub Issues with PowerShell and GitHub API

I made a post on how to authenticate to the GitHub API to perform tasks on your repositories, in this article I will show how to automate actions in GitHub. I will use GitHub Issues as examples,
I will use the token authentication, the standard one without restriction, but all the scripts presented here will work using any other authentication method.
The scenario is simple. You have one repository in a GitHub organization and you want to automate some issue-related tasks.
Let's start by creating an issue for a repository in PowerShell.

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The Organisation name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # the tittle of the Issue
    [Parameter(Mandatory=$true)]
    [string]
    $issueTitle, 
    # the body of the issue
    [Parameter(Mandatory=$true)]
    [string]
    $issueBody
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))

$headers = @{
        "Authorization"         = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"          = "application/json"
        "Accept"                = "application/vnd.github+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
}

$issueCreationUri = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues"

$body = @{ "title" = $issueTitle; "body"= $issueBody} | ConvertTo-Json   

$githubIssueLabels = Invoke-RestMethod -Method post -Uri $issueCreationUri -Headers $headers -body $body

$githubIssueLabels
Enter fullscreen mode Exit fullscreen mode

As seen in the previous article, we need to convert the token (a String) in base64 and add it to the header used in the request. This header includes the content type, JSON, the accept field (recommended by GitHub), and the API version (optional).
We need to create the HTTP body of the request by using a Hashtable converted to JSON
We need to build the API URI using the Organization name and the Repository name
Then we use invoke-RestMethod to send the query using the HTTP post method
To run it:
.\createIssue.ps1 -accessToken "<GitHub Token>" -orgaName "<Orga Name>" -reposName "<Repos name>" -issueTitle "something don't work" -issueBody "I can't access the servive"

We can list issues from a repository:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 

    # The Organisation name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 

    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName 
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization" = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"  = "application/json"
        "X-GitHub-Api-Version"  = "2022-11-28"
    }

# Querry the API to list all issues in a repository
$reposAPIUri = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues" 
$githubIssues = Invoke-RestMethod -Method get -Uri $reposAPIUri -Headers $headers 

foreach ($issue in $githubIssues) { 
    $issue.title 
    $issue.Id
    $issue.created_at.dateTime
    $issue.locked
    $issue.State
    $issue.user.login
    $issue.number
}
Enter fullscreen mode Exit fullscreen mode

Here, we use almost the same header, but the Accept field is optional. We build the URI and use Invoke-WebRequest to query the API.
The result in the $gitHubIssues is an Array. You need to use a forEach to extract some information.

We can get the details of an issue by using the issue number:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The Organisation name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization"         = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"          = "application/json"
        "Accept"                = "application/vnd.github.text+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
    }

$issueIUri = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)"

$githubIssue = Invoke-RestMethod -Method get -Uri $issueIUri -Headers $headers 

$githubIssue
Enter fullscreen mode Exit fullscreen mode

Here we need to use the Accept field with "application/vnd.github.text+json" to indicate we only want the text representation of the Markdown, but you can use other options:

  • application/vnd.github.raw+json to return the markdown as code, like in VSCode
  • application/vnd.github.html+json to return the HTML representation of the issue
  • application/vnd.github.full+json to return the text representation, the markdown code and the HTML representation in Body, Body_text and Body_html

To execute:

.\getissue.ps1 - accessToken "<GitHub Token>" -orgaName "<Orga Name>" -reposName "<Repos name>" -issueNumber <issue Number>

You can also get the list of comments for an issue:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The Organisation name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization"         = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"          = "application/json"
        "Accept"                = "application/vnd.github.full+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
    }

    $issueCommentsURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/comments"

    $githubIssueComments = Invoke-RestMethod -Method get -Uri $issueCommentsURI -Headers $headers 

    foreach ($comment in $githubIssueComments){
        $comment.body
        $comment.id
        $comment.user.login
        $comment.url
        $comment.reactions.total_count
    }
Enter fullscreen mode Exit fullscreen mode

Here we have the same header as the previous example, the Accept field uses application/vnd.github.full+json to get the 3 options for the comment body.

The result is an array that can be parsed with a ForEach and it contains complex objects like user or reaction.
To create a comment, you can use the same URI with a POST HTTP method:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The Organisation name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # the body of the comment
    [Parameter(Mandatory=$true)]
    [string]
    $commentBody,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))

$headers = @{
        "Authorization"         = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"          = "application/json"
        "Accept"                = "application/vnd.github+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
}

$body = @{  "body"= $commentBody} | ConvertTo-Json   

$issueCommentsURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/comments"

$githubCreateIssueComments = Invoke-RestMethod -Method POST -Uri $issueCommentsURI -Headers $headers -body $body

$githubCreateIssueComments
Enter fullscreen mode Exit fullscreen mode

To run it:
createcomment.ps1 - accessToken "<GitHub Token>" -orgaName "<Orga Name>" -reposName "<Repos name>" -issueNumber <issue Number> -commentBody "I have the same problem"

You can manage labels on an issue, list, add, remove, and update labels.
To list label:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization" = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"  = "application/json"
        "Accept"        = "application/vnd.github+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
    }

$issueURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/labels"

$githubIssueLabels = Invoke-RestMethod -Method get -Uri $issueURI -Headers $headers

foreach ($label in $githubIssueLabels) {
    $label.name
    $label.description
    $label.default
}
Enter fullscreen mode Exit fullscreen mode

The default property, here, indicates if the label is part of the standard GitHub label (Bug, Triage, documentation for example) or a personal label.

To add labels to an issue:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber,
    # Array of label
    [Parameter(Mandatory=$true)]
    [array]
    $issueLabels
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
$headers = @{
        "Authorization"         = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"          = "application/json"
        "Accept"                = "application/vnd.github+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
}

$body = @{ "labels" = $issueLabels} | ConvertTo-Json   

$issueLabelsURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/labels"

$githubIssueLabels = Invoke-RestMethod -Method post -Uri $issueLabelsURI -Headers $headers -body $body

$githubIssueLabels
Enter fullscreen mode Exit fullscreen mode

A POST HTTP method is used to create labels. Labels are stored as an array, the array is used to create the body of the request that needs to be converted into JSON.

You can delete a label by using the DELETE HTTP method:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber,
    # The label to remove
    [Parameter(Mandatory=$true)]
    [string]
    $issueLabelName
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization" = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"  = "application/json"
        "Accept"        = "application/vnd.github+json"
        "X-GitHub-Api-Version"  = "2022-11-28"
    }

$issueLabelsURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/labels/$($issueLabelName)"

$githubIssueLabels = Invoke-RestMethod -Method DELETE -Uri $issueLabelsURI -Headers $headers -body $body

$githubIssueLabels
Enter fullscreen mode Exit fullscreen mode

Another action that can be done with the GitHub API is to lock an issue:

param(
    # The access token to the GitHub Rest API 
    [Parameter(Mandatory=$true)]
    [string]
    $accessToken, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $orgaName, 
    # The repository Name
    [Parameter(Mandatory=$true)]
    [string]
    $reposName,
    # The issue number
    [Parameter(Mandatory=$true)]
    [int]
    $issueNumber,
    # The lock reason
    [Parameter(Mandatory=$true)]
    [string]
    [ValidateSet("off-topic",
        "too heated",
        "resolved",
        "spaqm")]
    $lockReason
)

$authenticationToken = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$accessToken"))
    $headers = @{
        "Authorization" = [String]::Format("Basic {0}", $authenticationToken)
        "Content-Type"  = "application/json"
        "Accept"        = "application/vnd.github+json"
    }

$body = @{ "lock_reason" = $lockReason} | ConvertTo-Json   

$issueURI = "https://api.github.com/repos/$($orgaName)/$($reposName)/issues/$($issueNumber)/lock"

Invoke-RestMethod -Method put -Uri $issueURI -Headers $headers -body $body
Enter fullscreen mode Exit fullscreen mode

To lock an issue you need to choose a reason among off-topic, too heated, resolved, or spam, the parameter $lockReason validates this set. It is used to create the body of the request.

There are small examples of how to automate GitHub Issues with PowerShell. You can explore the API to do more
You can find the code in this GitHub repository

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.