Skip to content

Instantly share code, notes, and snippets.

@rleap-m
Last active May 6, 2025 15:42
Show Gist options
  • Save rleap-m/27ab8174cb479ec55d328fd9351657f3 to your computer and use it in GitHub Desktop.
Save rleap-m/27ab8174cb479ec55d328fd9351657f3 to your computer and use it in GitHub Desktop.
Script to assign team's permissions to organizational repositories in MSR registry.
<#
.SYNOPSIS
Script to assign team's permissions to organizational repositories in MSR registry.
.PARAMETER MkeUrl
The URL of the MKE server.
.PARAMETER MkrCred
The credentials for the MKE server.
.PARAMETER MsrRegistryHost
The MSR Registry hostname.
.PARAMETER MsrAccessToken
The access token for the MSR registry.
.PARAMETER MkeMgmtModuleName
The name of the MKE management module to import.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $MkeUrl,
[Parameter(Mandatory=$true,ParameterSetName='Cred')]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$MkeCred,
[Parameter(Mandatory = $true)]
[string] $MsrRegistryHost,
[Parameter(Mandatory = $false)]
[string] $MsrUsername = 'admin',
[Parameter(Mandatory = $true)]
[string] $MsrAccessToken,
[Parameter(Mandatory=$false)]
[string] $MkeMgmtModuleName = 'mke.mgmt'
)
function Invoke-WithTimeout {
param (
[Parameter(Mandatory)]
[ScriptBlock]$ScriptBlock,
[int]$TimeoutSeconds = 30,
[int]$MaxRetries = 3,
[object[]]$ArgumentList = @()
)
for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) {
Write-Verbose "Attempt [$attempt]: Starting background job with timeout $TimeoutSeconds seconds..."
$job = Start-Job -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList
if (Wait-Job -Job $job -Timeout $TimeoutSeconds) {
try {
$result = Receive-Job -Job $job -ErrorAction Stop
Remove-Job $job -Force
return $result
}
catch {
Write-Warning "Attempt [$attempt]: Job completed but returned an error: $_"
Remove-Job $job -Force
}
} else {
Write-Warning "Attempt [$attempt] timed out after $TimeoutSeconds seconds. Retrying..."
Stop-Job $job -Force
Remove-Job $job -Force
}
}
throw "Operation failed after [$MaxRetries] attempts due to timeouts or errors."
}
function Get-MsrRepo {
<#
.SYNOPSIS
Get repositories in a namespace
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $RegistryHost,
[Parameter(Mandatory = $false)]
[string] $Namespace,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $UserName = 'admin',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $AccessToken
)
begin {
}
process {
}
end {
$uri = "https://$($RegistryHost)/api/v0/repositories/$($Namespace)".TrimEnd('/') + "?pageSize=1000&count=false"
$headers = @{
"accept" = "application/json"
"Content-Type" = "application/json"
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))"
}
$splat = @{
'Uri' = $uri
'Headers' = $headers
'Method' = 'GET'
'UseBasicParsing' = $true
}
if ($PSEdition -eq 'Core') {
$splat.Add('SkipCertificateCheck', $true)
}
Try {
Write-Verbose "Obtaining MSR repos for namespace [$Namespace]..."
$response = Invoke-WebRequest @splat -ErrorAction Stop
if ($response.StatusCode -eq 200) {
if ($response.Headers.'Content-Type' -eq 'application/json') {
($response.content | ConvertFrom-Json).repositories
}
else {
$response.Content
}
}
else {
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]"
}
Write-Verbose "Obtaining MSR repos for namespace [$Namespace] complete."
}
Catch {
Write-Warning "Obtaining MSR repos failed."
Write-Warning "Reason: $($_)"
}
}
}
function Get-MsrRepoTeamAccess {
<#
.SYNOPSIS
Get teams granted access to an organization-owned repository.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $RegistryHost,
[Parameter(Mandatory = $true)]
[string] $Namespace,
[Parameter(Mandatory = $false)]
[string] $RepoName,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $UserName = 'admin',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $AccessToken
)
begin {
}
process {
}
end {
$uri = "https://$($RegistryHost)/api/v0/repositories/$($Namespace)/$($RepoName)/teamAccess".TrimEnd('/') + "?pageSize=1000&count=false"
$headers = @{
"accept" = "application/json"
"Content-Type" = "application/json"
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))"
}
$splat = @{
'Uri' = $uri
'Headers' = $headers
'Method' = 'GET'
'UseBasicParsing' = $true
}
if ($PSEdition -eq 'Core') {
$splat.Add('SkipCertificateCheck', $true)
}
Try {
Write-Verbose "Obtaining MSR team access for repo [$RepoName] for namespace [$Namespace]..."
$response = Invoke-WebRequest @splat -ErrorAction Stop
if ($response.StatusCode -eq 200) {
if ($response.Headers.'Content-Type' -eq 'application/json') {
($response.content | ConvertFrom-Json).teamAccessList
}
else {
$response.Content
}
}
else {
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]"
}
Write-Verbose "Obtaining MSR team access for repo [$RepoName] for namespace [$Namespace] complete."
}
Catch {
Write-Warning "Obtaining MSR repo team access failed."
Write-Warning "Reason: $($_)"
}
}
}
function Get-MsrNamespaceTeamAccess {
<#
.SYNOPSIS
Get teams granted access to an organization-owned namespace of repositories.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $RegistryHost,
[Parameter(Mandatory = $true)]
[string] $Namespace,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $UserName = 'admin',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $AccessToken
)
begin {
}
process {
}
end {
$uri = "https://$($RegistryHost)/api/v0/repositoryNamespaces/$($Namespace)/teamAccess".TrimEnd('/') + "?pageSize=1000&count=false"
$headers = @{
"accept" = "application/json"
"Content-Type" = "application/json"
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))"
}
$splat = @{
'Uri' = $uri
'Headers' = $headers
'Method' = 'GET'
'UseBasicParsing' = $true
}
if ($PSEdition -eq 'Core') {
$splat.Add('SkipCertificateCheck', $true)
}
Try {
Write-Verbose "Obtaining MSR team access for namespace [$Namespace]..."
$response = Invoke-WebRequest @splat -ErrorAction Stop
if ($response.StatusCode -eq 200) {
if ($response.Headers.'Content-Type' -eq 'application/json') {
($response.content | ConvertFrom-Json).teamAccessList
}
else {
$response.Content
}
}
else {
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]"
}
Write-Verbose "Obtaining MSR team access for namespace [$Namespace] complete."
}
Catch {
Write-Warning "Obtaining MSR repo team access failed."
Write-Warning "Reason: $($_)"
}
}
}
function Set-MsrNamespaceTeamAccess {
<#
.SYNOPSIS
Set team access to an organization-owned namespace of repositories.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $RegistryHost,
[Parameter(Mandatory = $true)]
[string] $Namespace,
[Parameter(Mandatory = $true)]
[string] $TeamName,
[Parameter(Mandatory = $false)]
[ValidateSet('read-only','read-write','admin')]
[string] $AccessLevel = 'read-only',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $UserName = 'admin',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $AccessToken
)
begin {
}
process {
}
end {
$uri = "https://$($RegistryHost)/api/v0/repositoryNamespaces/$($Namespace)/teamAccess/$($TeamName)".TrimEnd('/')
$body = @{'accessLevel' = $AccessLevel} | ConvertTo-Json -Compress
$headers = @{
"accept" = "application/json"
"Content-Type" = "application/json"
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))"
}
$splat = @{
'Uri' = $uri
'Headers' = $headers
'Method' = 'PUT'
'Body' = $body
'UseBasicParsing' = $true
}
if ($PSEdition -eq 'Core') {
$splat.Add('SkipCertificateCheck', $true)
}
if ($PSVersionTable.PSVersion -ge [Version]"7.4") {
$splat.Add('ConnectionTimeoutSeconds', 60)
$splat.Add('OperationTimeoutSeconds', 120)
$splat.Add('HttpVersion', '1.1')
$splat.Add('DisableKeepAlive', $true)
$splat.Add('MaximumRetryCount', 5)
$splat.Add('RetryIntervalSec', 5)
}
Try {
Write-Verbose "Assigning MSR team [$TeamName] [$AccessLevel] access to repos in namespace [$Namespace]..."
$response = Invoke-WebRequest @splat -ErrorAction Stop
if ($response.StatusCode -eq 200) {
if ($response.Headers.'Content-Type' -eq 'application/json') {
$response.content | ConvertFrom-Json
}
else {
$response.Content
}
}
else {
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]"
}
Write-Verbose "Assigning MSR team [$TeamName] [$AccessLevel] access to repos in namespace [$Namespace] complete."
}
Catch {
Write-Warning "Assigning MSR team access failed."
Write-Warning "Reason: $($_)"
}
}
}
Try {
Import-Module -Name $MkeMgmtModuleName -ErrorAction Stop
}
Catch {
Write-Warning "Unable to import the MKE management module. Please ensure it is available on the system."
return
}
Write-Verbose "Establishing MKE session..."
New-MkeSession -url $MkeUrl -Credential $MkeCred -AllowInsecureTransfer
if ($null -eq (Get-MkeSession -Active)) {
Write-Warning "Unable to create an MKE session. Please check your credentials and URL."
return
}
Write-Verbose "Establishing MKE session complete."
Write-Verbose "Obtaining MKE organization list..."
$mkeOrgs = Get-MkeOrg
# $mkeOrgs = @(Get-MkeOrg -Name '000001')
# $mkeOrgs += Get-MkeOrg -Name '000002'
# $mkeOrgs += Get-MkeOrg -Name '000003'
if ($null -eq $mkeOrgs) {
Write-Warning "Unable to obtain MKE organization list."
return
}
Write-Verbose "Obtaining MKE organization list complete. Total Orgs: [$($mkeOrgs.Count)]"
[string] $setMsrNamespaceTeamAccessDef = (Get-Command Set-MsrNamespaceTeamAccess).ScriptBlock.ToString()
# Looking for the Orgs with a numeric name as that is the convention we used for scale testing
$mkeOrgs = $mkeOrgs | Where-Object { $_.name -match '^\d+$' }
foreach ($mkeOrg in $mkeOrgs) {
Write-Verbose "Getting teams for MKE org [$($mkeOrg.name)]..."
$teams = @(
Invoke-WithTimeout -TimeoutSeconds 30 -MaxRetries 3 -ScriptBlock {
param (
[string] $MkeMgmtModuleName,
[object] $MkeSession,
[string] $MkeOrgName
)
$VerbosePreference = $using:VerbosePreference
Try {
Import-Module -Name $MkeMgmtModuleName -Verbose:$false -ErrorAction Stop
}
Catch {
Write-Warning "Unable to import the MKE management module. Please ensure it is available on the system."
return
}
Set-MkeSession -Session $MkeSession
Get-MkeTeam -OrgName $MkeOrgName
} -ArgumentList $MkeMgmtModuleName, (Get-MkeSession -Active), $mkeOrg.name
)
Write-Verbose "Getting teams for MKE org [$($mkeOrg.name)] complete."
if ($teams.Count -gt 0) {
$teams | ForEach-Object -Parallel {
$VerbosePreference = $using:VerbosePreference
$accessLevel = switch (($_.name).Split('-')[1]) {
'testers' { 'read-only' }
'developers' { 'read-write' }
'operators' { 'admin' }
default { 'read-only' }
}
# Bring the script-scope defined function into the runspace scope
$SetMsrNamespaceTeamAccessFunc = [scriptblock]::Create($using:setMsrNamespaceTeamAccessDef)
$splat = @{
'RegistryHost' = $using:MsrRegistryHost
'Namespace' = $using:mkeOrg.name
'TeamName' = $_.name
'AccessLevel' = $accessLevel
'UserName' = $using:MsrUsername
'AccessToken' = $using:MsrAccessToken
}
& $SetMsrNamespaceTeamAccessFunc @splat
} -ThrottleLimit $teams.count -TimeoutSeconds 120 -Verbose:($VerbosePreference -eq 'Continue')
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment