Automate your GitHub Issues with PowerShell and GitHub API
Tags: powershell, github
Published: 2024-07-01 21:07:45

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.

```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
```
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:

```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 
)

$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
}
```
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:

```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 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
```
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: 

```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 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
    }
```

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: 

```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 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
```
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: 

```powershell
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
}
```

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: 

```powershell
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
```

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:

```powershell
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
```

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

```powershell
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
```

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](https://docs.github.com/en/rest/quickstart?apiVersion=2022-11-28) 
You can find the code in this [GitHub repository](https://github.com/omiossec/pwsh-GitHub-API/tree/main/AutomateGitHub)