Решение состоит в том, чтобы использовать команду az rest
и использовать ее для прямого вызова Graph API. Вот сценарий PowerShell, который я использую в конвейере yaml Azure DevOps.
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[String]$servicePrincipalId,
[Parameter(Mandatory)]
[String]$servicePrincipalPassword,
[Parameter(Mandatory)]
[String]$servicePrincipalTenantId,
[Parameter(Mandatory)]
[String]$newReplyUrl,
[Parameter(Mandatory)]
[String]$appRegIdentifier
)
Write-Host "Logging in as B2C Tenant Service Principal"
$null = az login --service-principal -u $servicePrincipalId -p $servicePrincipalPassword -t $servicePrincipalTenantId --allow-no-subscriptions
# Load some JSON/Text file helpers...
Import-Module $PSScriptRoot\Json-Helpers.psm1
Write-Host "Adding $newReplyUrl if required..."
$appRegObjectId = az ad app show --id $appRegIdentifier --query 'objectId'
$graphAppRegUri = "https://graph.microsoft.com/v1.0/applications/$appRegObjectId"
$appJson = az rest --method GET --uri $graphAppRegUri
$app = $appJson | ConvertFrom-Json
if ($app.web.redirectUris.Contains($newReplyUrl)) {
# ReplyUrl exists, no-op
Write-Host "Reply Url already exists, no action required..."
Exit 0
}
$patchApp = @{ web = @{ redirectUris = @() } }
ForEach ($url in $app.web.redirectUris) {
$patchApp["web"].redirectUris += $url
}
$patchApp["web"].redirectUris += $newReplyUrl
$tempFile = ".\patchAppTemp-$([System.Guid]::NewGuid()).json"
try {
$patchApp | ConvertTo-Json -Depth 5 | Format-Json -Minify | Set-Content -Path $tempFile -Encoding UTF8 -NoNewline
# Update `redirectUris` for `web` property
Write-Host "Calling Graph API to update Reply Urls"
az rest --method patch --uri $graphAppRegUri --headers "Content-Type=application/json" --body=@$tempFile
Write-Host "Successfully added $newReplyUrl to App Registration $appRegIdentifier"
}
finally {
if (Test-Path $tempFile) {
Remove-Item -Path $tempFile
}
}
И для полноты, вот модуль Json -Helpers.psm1 PowerShell:
function Get-Encoding
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[string]
$Path
)
process
{
$bom = New-Object -TypeName System.Byte[](4)
$file = New-Object System.IO.FileStream($Path, 'Open', 'Read')
$null = $file.Read($bom,0,4)
$file.Close()
$file.Dispose()
$enc = 'Ascii'
if ($bom[0] -eq 0x2b -and $bom[1] -eq 0x2f -and $bom[2] -eq 0x76)
{ $enc = 'Utf7' }
if ($bom[0] -eq 0xff -and $bom[1] -eq 0xfe)
{ $enc = 'Unicode' }
if ($bom[0] -eq 0xfe -and $bom[1] -eq 0xff)
{ $enc = 'Bigendianunicode' }
if ($bom[0] -eq 0x00 -and $bom[1] -eq 0x00 -and $bom[2] -eq 0xfe -and $bom[3] -eq 0xff)
{ $enc = 'Utf32'}
if ($bom[0] -eq 0xef -and $bom[1] -eq 0xbb -and $bom[2] -eq 0xbf)
{ $enc = 'Utf8'}
[PSCustomObject]@{
Encoding = $enc
Path = $Path
}
}
}
function Format-Json {
<#
.SYNOPSIS
Prettifies JSON output.
.DESCRIPTION
Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
.PARAMETER Json
Required: [string] The JSON text to prettify.
.PARAMETER Minify
Optional: Returns the json string compressed.
.PARAMETER Indentation
Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
.PARAMETER AsArray
Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
.EXAMPLE
$json | ConvertTo-Json | Format-Json -Indentation 2
#>
[CmdletBinding(DefaultParameterSetName = 'Prettify')]
Param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[string]$Json,
[Parameter(ParameterSetName = 'Minify')]
[switch]$Minify,
[Parameter(ParameterSetName = 'Prettify')]
[ValidateRange(1, 1024)]
[int]$Indentation = 4,
[Parameter(ParameterSetName = 'Prettify')]
[switch]$AsArray
)
if ($PSCmdlet.ParameterSetName -eq 'Minify') {
return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
}
# If the input JSON text has been created with ConvertTo-Json -Compress
# then we first need to reconvert it without compression
if ($Json -notmatch '\r?\n') {
$Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
}
$indent = 0
$regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
$result = $Json -split '\r?\n' |
ForEach-Object {
# If the line contains a ] or } character,
# we need to decrement the indentation level unless it is inside quotes.
if ($_ -match "[}\]]$regexUnlessQuoted") {
$indent = [Math]::Max($indent - $Indentation, 0)
}
# Replace all colon-space combinations by ": " unless it is inside quotes.
$line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
# If the line contains a [ or { character,
# we need to increment the indentation level unless it is inside quotes.
if ($_ -match "[\{\[]$regexUnlessQuoted") {
$indent += $Indentation
}
$line
}
if ($AsArray) { return $result }
return $result -Join [Environment]::NewLine
}
Ключевая строка:
az rest --method patch --uri $graphAppRegUri --headers "Content-Type=application/json" --body=@$tempFile
, которая вызывает Graph API, передавая Json содержимое вашего динамически созданного временного файла. Одним из небольших недостатков этого способа является то, что вы берете текущие URL-адреса, добавляете новый и исправляете весь список, а не просто добавляете тот, который вам нужен. По мере роста списка содержание патча также может увеличиваться.
Но на сегодняшний день это лучший способ обойти обнаруженную мной проблему.