DEV Community

uyriq
uyriq

Posted on

1

PowerShell Automation: Execute Batch GitHub CLI Commands

Automating Routine Tasks with a PowerShell Script

Working with repositories on GitHub, especially when there are many of them, can be tiresome and time-consuming. When I needed to change the visibility of multiple repositories and manage access rights, I decided to create a cross-platform PowerShell script, batchrepoaction.ps1, which automates routine tasks and saves time and effort.

The Idea Behind the Script

The idea of writing the script came from the need to perform repetitive actions for multiple repositories. For example, changing the repository's visibility from public to private and vice versa, or adding a collaborator. These operations, if done manually through the GitHub interface, require several clicks, reading banners, and scrolling through pages. When there are dozens or even hundreds of such repositories, the task becomes tedious and time-consuming.

In real life, people like HR managers are always interested in our public information and check our repositories. Our team leads request to add or remove access to repositories for our future or former developer colleagues. These situations also motivated me to create this script.

What the Script Does

batchrepoaction.ps1 is written in PowerShell and can work both in PowerShell 5 on Windows "out of the box" and in PowerShell Core, which is available not only on Windows but also on Linux and macOS. The main idea of the script is to get a list of repositories and process them one by one by calling the GitHub CLI utility (gh). This allows executing any GitHub CLI command in the context of working with a repository.

The user must be authorized in GitHub CLI and have the gh tool installed.

The most important parameter of the script is -action. Here’s what I wrote in the help section:

.PARAMETER action
The regular `gh` CLI command to execute e.g., "gh repo edit --visibility private", "gh repo delete"
Enter fullscreen mode Exit fullscreen mode

So, the idea is that the command for gh is passed in action, which will be executed for each repository from the list.

Some gh commands can be quite long and depend on several parameters. Such commands cannot be passed in the current version of the script, so, for example, for getting a list of collaborators and adding a collaborator, I made my own shorthands instead of writing a syntax parser:

list-collaborators

add-collaborator

delete-collaborator

which can be passed in action instead of their long equivalents.

The other gh commands that support one argument are passed in their syntax, so my script is still a wrapper! Moreover, if the syntax of the commands changes a bit, as promised with the upcoming release of the gh repo edit command, it will not affect the functionality of the script, just a slightly different command will be executed for the list of repositories.

It looks like I need to conclude the explanation of the script by providing usage examples step by step:

Changing Repository Visibility

First, get a list of your repositories and save it to a text file:

.\batchrepoaction.ps1 -action "gh repo list" -outputfile repofile.txt
Enter fullscreen mode Exit fullscreen mode

You can now edit repofile.txt, leaving only the repositories needed for the operation.

Run the command to change the visibility of all listed repositories:

.\batchrepoaction.ps1 -repofile repofile.txt -action "gh repo edit --visibility private"
Enter fullscreen mode Exit fullscreen mode

If the number of repositories to be processed is not so large, you can use the -repolist parameter, listing the required repositories in quotes, separated by commas:

powershell

.\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "gh repo edit --visibility private"
Enter fullscreen mode Exit fullscreen mode

Adding a Collaborator to Several Repositories
To add a collaborator to all repositories, use the command:

.\batchrepoaction.ps1 `-repolist` "user/repo1,user/repo2" -collab_permission "push" -collaborator "anygithubusername"
Enter fullscreen mode Exit fullscreen mode

As they say in advertisements, emphasizing the meaning of the word "can", the batchrepoaction.ps1 script can simplify the management of multiple repositories on GitHub by automating routine tasks and allowing you to focus on more important aspects of development. I hope this script will be useful, saving your time and nerves.

Readme.md
tests/**
tests
view raw .gitignore hosted with ❤ by GitHub
param (
[string]$repofile,
[string]$repolist,
[string]$action,
[string]$token,
[string]$owner,
[string]$outputfile,
[string]$collaborator,
[string]$collab_permission,
[switch]$help,
[int]$maxrepos = 1000
)
function Show-Help {
@"
This script allows you to pass a GitHub CLI command as an argument and a list of repositories or a file containing repository names to execute the 'gh' CLI command in batch.
Supported Actions:
- gh repo edit --visibility private: Edit the visibility of repositories.
- gh repo delete: Delete repositories.
- add-collaborator: Add a collaborator to repositories.
- list-collaborators: List the collaborators for repositories and show their permissions you can check yours own too.
- gh secret set <name> -b <value>: Set a secret for repositories.
- gh secret list: List secrets for repositories.
Parameters:
- repofile: Path to a file containing a list of repositories (one per line).
- repolist: A comma-separated list of repositories.
- action: The 'gh' CLI command to execute.
- token: (Optional) GitHub token for authentication.
- owner: The GitHub username whose repositories will be listed.
- outputfile: The file to output the list of repositories. If not specified, the repository list will be output to the console.
- collaborator: The GitHub username of the collaborator to add to the repositories.
- collab_permission: The permission level to assign to the collaborator (e.g., 'read', 'triage', 'write', 'maintain', 'admin').
Examples:
- .\batchrepoaction.ps1 -repofile repofile.txt -action "gh repo view"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "gh repo view"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "gh repo view" -token "yourtoken"
- .\batchrepoaction.ps1 -owner "your_github_username" -action "gh repo list" -outputfile repofile.txt
- .\batchrepoaction.ps1 -owner "your_github_username" -action "gh repo list"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "add-collaborator" -collaborator "collaborator_username" -collab_permission "write"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "list-collaborators"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "gh secret set mysecret -b 'valueofsecret'"
- .\batchrepoaction.ps1 -repolist "user/repo1,user/repo2" -action "gh secret list"
"@
}
function Get-Repos {
param (
[string]$repofile,
[string]$repolist
)
$repos = @()
if ($repofile) {
if (Test-Path $repofile) {
$repos += Get-Content $repofile
}
else {
Write-Error "File not found: $repofile"
exit 1
}
}
if ($repolist) {
$repos += $repolist -split ","
}
return $repos
}
function Invoke-Action {
param (
[string]$repo,
[string]$action,
[string]$token,
[string]$collaborator,
[string]$collab_permission
)
if ($token) {
gh auth login --with-token $token
}
if ($action -eq "add-collaborator") {
if (-not $collaborator) {
Write-Error "Collaborator parameter is required for adding a collaborator."
exit 1
}
if (-not $collab_permission) {
Write-Error "Collaborator permission parameter is required for adding a collaborator."
exit 1
}
$command = @"
gh api --method PUT -H "Accept: application/vnd.github+json" /repos/$repo/collaborators/$collaborator -f permission=$collab_permission
"@
}
elseif ($action -eq "delete-collaborator") {
if (-not $collaborator) {
Write-Error "Collaborator parameter is required for deleting a collaborator."
exit 1
}
$command = @"
gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/$repo/collaborators/$collaborator
"@
}
elseif ($action -eq "list-collaborators") {
$command = "gh api /repos/$repo/collaborators --jq '.[] | {login: .login, permission: .permissions}'"
}
elseif ($action -match "gh secret set (\S+) -b '?([^']+)'?") {
$secret_name = $matches[1]
$secret_value = $matches[2]
Write-Output "secret, value: $secret_name, $secret_value" | ft -AutoSize
$command = "gh secret set $secret_name -b '$secret_value' -R $repo"
}
elseif ($action -eq "gh secret list") {
$command = "gh secret list -R $repo"
}
else {
$command = "$action $repo"
}
Write-Host "Executing: $command"
Write-Output "action, repo, token, collaborator, collab_permission: $action, $repo, $token, $collaborator, $collab_permission" | ft -AutoSize
$result = Invoke-Expression "$command 2>&1"
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to execute command: $command"
Write-Error "Error: $result"
return
}
elseif ($action -eq "gh secret list" -and (-not $result -or $result.Trim() -eq "")) {
Write-Host "No secrets found for repository: $repo"
}
else {
Write-Host "Command executed successfully: $command"
Write-Host $result
return
}
}
function Get-ReposList {
param (
[string]$owner,
[string]$outputfile
)
$command = "gh repo list $owner -L $maxrepos --json owner,name,visibility,description"
Write-Host "Executing: $command"
$result = Invoke-Expression $command | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to execute command: $command"
}
else {
Write-Host "Command executed successfully: $command"
if ($outputfile) {
$repoList = $result | ForEach-Object { "$($_.owner.login)/$($_.name)" }
$repoList | Out-File -FilePath $outputfile -Encoding utf8
Write-Host "Repository list saved to $outputfile"
}
else {
Write-Host "Repository list:"
$table = @()
foreach ($repo in $result) {
$table += [PSCustomObject]@{
Name = $repo.name
Visibility = $repo.visibility
Description = $repo.description
}
}
$table | Format-Table -AutoSize
}
}
}
# Main script
# Set output encoding to UTF-8 as repo names/desc could contain emojis
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
if ($help) {
Show-Help
exit 0
}
# Check GitHub CLI authentication status
$authStatus = Invoke-Expression "gh auth status --active 2>&1"
$loggedInUser = $authStatus | Select-String -Pattern "Logged in to github.com account (\w+)" | ForEach-Object {
if ($_ -match "Logged in to github.com account (\w+)") {
$matches[1]
}
}
if ($loggedInUser) {
Write-Host "GitHub CLI authentication successful. Logged in as: $loggedInUser"
if (-not $owner) {
$owner = $loggedInUser
}
}
else {
Write-Host "GitHub CLI authentication failed. Please log in using 'gh auth login'."
return
}
if (-not $action) {
Write-Error "Action parameter is required."
exit 1
}
if ($action -eq "gh repo list" -and $owner) {
Get-ReposList -owner $owner -outputfile $outputfile
exit 0
}
$repos = Get-Repos -repofile $repofile -repolist $repolist
if ($repos.Count -eq 0) {
Write-Error "No repositories specified."
exit 1
}
foreach ($repo in $repos) {
Invoke-Action -repo $repo -action $action -token $token -collaborator $collaborator -collab_permission $collab_permission
}
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
Author: Uyriq
view raw unlicence.txt hosted with ❤ by GitHub

Top comments (0)

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay