Cannot update AAD App Registration in B2C Tenant after first user logs in - azure-ad-b2c

I am posting this question in the style of question and answer.
I recently ran into an issue where I could not add Reply Urls to an Azure AD App Registration, which was registered in a B2C Tenant.
I had been happily updating the Reply Urls using automation using the az ad app update --id $appIdentifier --add replyUrls "https://<a-valid-dns-zone-record> command.
However, once you use your App Registration from a B2C tenant to perform the first login, the command above stops working. You CAN carry on using the portal to add new Reply Urls, but that isnt any good for automation!
I had googled and searched stackoverflow for answers but found none. I raised a Premier Support ticket with Microsoft, and a Github issue on the Azure CLI tool.
I got a workaournd for the problem from my Github Issue
Which I will detail in my answer

The solution is to use the az rest command and use it to call Graph API directly. Here is a PowerShell script I use in an Azure DevOps yaml pipeline.
[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
}
}
And for completeness, here is the Json-Helpers.psm1 PowerShell module:
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
}
The key line is
az rest --method patch --uri $graphAppRegUri --headers "Content-Type=application/json" --body=#$tempFile
Which calls the Graph API, passing the Json content of your dynamically created temporary file. One small downside to doing it this way is that you take the current URLs, add your new one, and patch the whole list, rather than just adding the one you wanted. As the list grows, the content of the patch could get large too.
But as things stand today, this is the best way to get around the problem that I found.

While Ian had a very good answer and would not have been able to get started without it, it also seems bit too complex. With patch command you do not need to write all the json gotten from the GET call, but only the part you want to update.
$myTestServiceUri = "https://my-test-url.com"
$myObjectId = "<OBJECT_ID_GUID_HERE>"
$graphAppRegUri = "https://graph.microsoft.com/v1.0/applications/$myObjectId"
az rest --method GET --uri $graphAppRegUri
$newUriArr = ((az rest --method GET --uri $graphAppRegUri | ConvertFrom-Json).spa.redirectUris + $myTestServiceUri)
(#{spa=#{redirectUris=$newUriArr}} | ConvertTo-Json) | Set-Content -Encoding UTF8 -NoNewlin -Path tmp-redirectUris.json
az rest --method PATCH --uri $graphAppRegUri --headers "Content-Type=application/json" --body=#tmp-redirectUris.json
Also good to do login before running this to test locally (had to use no subscription as Azure AD B2C tenant has got no subscription at least in my case):
az login --tenant <TENANT_NAME_HERE>.onmicrosoft.com --allow-no-subscriptions
When logging in with automated setup, one needs to use Azure B2C application configured with correct access rights and client secret (see instructions from Azure B2C docs):
az login --service-principal --username APP_ID --password SP1_PASSWORD --tenant TENANT_ID
From the Microsoft Graph REST API docs one can also see the required permissions for the command and can configure those to B2C application. So you will need Application.ReadWrite.All if you are going to manage all your Applications with the same "service principal". If only this one application, use Application.ReadWrite.OwnedBy.

Related

PowerShell: Output provider results from multiple Azure subscriptions into one .txt file - via arrayList

I am trying to get the registered providers for multiple subscriptions and output everything into one file.
For that, i am getting the subscriptions from a folder full of *.yaml files that contain information about the subscriptions, including their name.
What i cannot achieve is get the result for each subscriptions into an array and output that array into a text file. The script also allows the use of only one item from the folder in case that is the case.
Here is a sample code of a subscription and the code for it:
subscription1.yaml
name: subscription1
emailContact: email.address#domain.com
tags:
- key: "key1"
value: "Value1"
subscription2.yaml
name: subscription2
emailContact: email.address#domain.com
tags:
- key: "key1"
value: "Value1"
Folder structure where the yaml files is: ./landingZones/landingzone1/settings/dev/*.yaml
script:
param (
[Parameter(Mandatory = $false)]
[string]$Environment = 'dev',
[Parameter(Mandatory = $false)]
[string]$LandingZoneType = 'landingzone1',
[Parameter(Mandatory = $false)]
[string]$SingleSubscription
)
$scriptPath = Split-Path -parent $PSCommandPath
$subscriptionsEnvironmentDirectory = Get-ChildItem -Directory $scriptPath -Recurse -Filter "*$Environment*" | Where-Object { $_.parent.parent.Name -eq $LandingZoneType }
$subscriptions = Get-ChildItem -Path $($subscriptionsEnvironmentDirectory.FullName)
foreach ($subscription in ($subscriptions | Where-Object { ([System.String]::IsNullOrEmpty($SingleSubscription)) -or ($_.Name -replace "\.[^\.]+$", '') -eq $SingleSubscription })) {
$landingZone = Get-Content -Path $subscription.FullName | ConvertFrom-Yaml
# Set subscriptionName variable
$subscriptionName = $landingZone.name
$providers = az provider list --subscription $subscriptionName | ConvertFrom-Json
$defaultRegisteredProviders = 'Microsoft.ADHybridHealthService|Microsoft.Authorization|Microsoft.Billing|Microsoft.ClassicSubscription|Microsoft.Commerce|Microsoft.Consumption|Microsoft.CostManagement|Microsoft.Features|Microsoft.MarketplaceOrdering'
$registeredProviders = $providers | Where-Object { ($_.registrationState -eq 'Registered') -and ($_.namespace -notmatch $defaultRegisteredProviders) }
# Outputting result into txt file in the same directory where the command was executed
Write-Host ('{1}# Registered providers for subscription [{0}]' -f $subscriptionName, "`n")
$list = New-Object -TypeName 'System.Collections.ArrayList'
$sortedObjects = $registeredProviders | Sort-Object namespace | `
Format-Table `
#{l = 'Namespace'; e = { $_.namespace } }, `
#{l = "Subscription Id [$subscriptionName]"; e = { $_.id } }, `
#{l = 'Registration State'; e = { $_.registrationState } }, `
#{l = 'Registration Policy'; e = { $_.registrationPolicy } }
foreach ($i in $sortedObjects) {
$list.Add($i) | Out-Null
}
# Alternative to add into array:
# #($sortedObjects).foreach({$list.Add($_)}) | Out-Null
}
$list.Count
$list | Out-File .\registered_providers.txt -Force
The result is a file called 'registered_providers.txt' that contains only the registered providers for the first subscription in the foreach loop. I cannot get the contents of the second, third and so on in the same file, just a replaced text from the $sortedObjects
How do i create the array to contain all the info from all the subscriptions called?
Thanks
$list | Out-File Should be inside the for loop so that all the data you are fetching will be stored in the specified file.
Out-File cmdlet has -append parameter that appends the output to the existing file for every for loop happens. Otherwise, it will clean up the information/value stored in that output file.
Thanks to #jdweng for pointing the user to the right solution.
Refer to this MS Doc on Out-File -Append Parameter usage.

PowerShell Script getting JSON output to use in API

I am running a PowerShell script on a server to check on other machines on the network.
I want the result of the check to be outputted in JSON format so I can send this JSON data via an api request to a web dashboard build with angular.
My set up:
Get-Request from Angular front end -> Express server -> run PowerShell script with Node-Powershell
Now I want to return the result in proper format to be used in the front end. What is the best way to accomplish this? I want to use the data to fill a material data table.
PowerShell Script status.ps1:
$Computers = #('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
foreach ($computer in $Computers) {
gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status | ConvertTo-Json
}
Api from express server.js (using Node-Powershell):
app.get('/api/psjson', (request, response) => {
ps.addCommand('./status.ps1');
ps.invoke().then(output => {
console.log(output);
response.send(JSON.parse(output));
}).catch(err => {
console.log(err);
response.send(err);
ps.dispose();
});
});
I tried using | ConvertTo-Json inside the loop but it is causing in error in node:
SyntaxError: Unexpected token { in JSON at position 55
at JSON.parse ()
Please try the following:
$Computers = #('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
$Results = #()
foreach ($computer in $Computers) {
$Result = $null
$Result = gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status
If ($Result -ne $null){
$Results += $Result
}
}
$Results | ConvertTo-Json
This builds an array of the results and then converts the array to JSON.
I think the issue you are experiencing is due to converting inside a loop and therefore the structure is incorrect.
CraftyB's answer diagnoses the problem correctly, but there's a simpler, more PowerShell-idiomatic solution that uses a single pipeline:
$computers = 'ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04'
gsv -cn $computers -Name Example -ErrorAction SilentlyContinue |
| Select-Object machinename, status |
ConvertTo-Json
The crucial aspect is that all input objects are passed to a single ConvertTo-Json call - see the bottom section for an explanation.
In Windows PowerShell, Get-Service (gsv) the -ComputerName (-cn) parameter directly accepts an array of computer names.
Note: In PowerShell (Core) 7+, this form of remoting is no longer supported, so there is no -ComputerName parameter; there, assuming that the target computers are set up for PowerShell remoting, you could use:
Invoke-Command -ComputerName $computers { Get-Service -Name Example -ErrorAction SilentlyContinue }
As for what you tried:
If you call ConvertTo-Json inside a loop, per input object, you will implicitly output multiple, independent JSON strings instead of a single JSON string representing the input objects as a JSON array:
Given the following sample input objects:
$objects = [pscustomobject] #{ foo=1 }, [pscustomobject] #{ foo=2 }
It is the difference between:
# !! BROKEN: *multiple* ConvertTo-Json calls.
# !! When the result is stringified, you get a space-separated list of the
# !! individual JSON strings, which is NOT valid JSON.
PS> #"
$(
foreach ($object in $objects) { $object | ConvertTo-Json -Compress }
)
"#
{"foo":1} {"foo":2} # NOT valid JSON.
and:
# OK: *Single* ConvertTo-Json call, which outputs a valid JSON array.
PS> $objects | ConvertTo-Json -Compress
[{"foo":1},{"foo":2}] # OK: valid JSON array.

UserLastLogon -Export

Hi I'm trying to export a list of AD users based on "Last Logon"
I've scripted using base powershell however I'd be interested if anyone can find a solution using "AzureAD to Powershell" commands.
I've gotten as far as getting the list however I cannot export it to any file type because of how it generates through the loop.
End result I'm looking for is to be able to organize the data to see which users have been inactive?
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName) {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$time = 0
foreach($dc in $dcs) {
$hostname = $dc.HostName
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time) {
$time = $user.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
Write-Host $username "last logged on at:" $dt
}
$unames = Get-ADUser -Filter 'ObjectClass -eq "User"' | Select -Expand SamAccountName
foreach ($uname in $unames) { Get-ADUserLastLogon($uname); }
In Azure AD, we can get all user Sign-ins records on Azure Portal or using Azure AD PowerShell.
If you are looking for a way by PowerShell to export Azure AD users last login list with user account status (enabled or not), just try the code below:
Connect-AzureAD
$AllUsers = Get-AzureADUser -All $true
$AllSiginLogs = Get-AzureADAuditSignInLogs -All $true
$results = #()
foreach($user in $AllUsers){
$LoginRecord = $AllSiginLogs | Where-Object{ $_.UserId -eq $user.ObjectId } | Sort-Object CreatedDateTime -Descending
if($LoginRecord.Count -gt 0){
$lastLogin = $LoginRecord[0].CreatedDateTime
}else{
$lastLogin = 'no login record'
}
$item = #{
userUPN=$user.UserPrincipalName
userDisplayName = $user.DisplayName
lastLogin = $lastLogin
accountEnabled = $user.AccountEnabled
}
$results += New-Object PSObject -Property $item
}
$results | export-csv -Path d:\result.csv -NoTypeInformation
export to .csv file Result:
There is one thing that you should know, for different Azure AD service tier, the time that Azure AD keep these data is different, details see here.

Get Key Value Pairs from Azure App Configuration with PowerShell

There aren't any cmdlets in the AZ modules to get key-value pairs from Azure app configuration. Are there any other options besides AZ CLI? Is there a way to interact with service from PowerShell?
There is no PowerShell support to get key-values from App Configuration at this point. Calling Azure CLI in PowerShell is the way to go. The Az.AppConfiguration module only supports management operations (eg., creating an App Configuration store, etc).
The request is tracked in GitHub https://github.com/Azure/AppConfiguration/issues/267.
Hey I'm a bit late to the party but I was searching for the same thing and I ended up created my own solution because the given answer only covers management tasks and not data retrieval.
check out this repo
It can fetch key values and resolve secrets.
Also it can parse referenced keys in the values.
it uses az cli in the background, so you need that installed first.
Azure CLI has a command az appconfig kv list that can be used to list all key-values from App Configuration. More details on the usage and examples can be found here.
Here's my solution using App Configuration connection string for authentication. You're interested in .items property from the returned object. In case of paging, there'll be a next link there as well. For details, see the docs at https://learn.microsoft.com/azure/azure-app-configuration/rest-api-key-value
Keys can contain slash characters, so they need to be URL-encoded using [System.Net.WebUtility]::UrlEncode($Key) when building a $RequestUri.
function Invoke-AppConfigRequest {
param(
[Parameter(Mandatory = $true)] [string] $ConnectionString, # 'Endpoint=...;Id=...;Secret=...'
[Parameter(Mandatory = $true)] [string] $RequestUri, # '/kv?api-version=1.0&key=some-url-encoded-key&label=*'
[Parameter(Mandatory = $false)] [string] $Method = 'GET', # 'GET', 'POST'
[Parameter(Mandatory = $false)] [object] $Body = $null # Accepts [object] to avoid implicit conversion of $null to empty string
)
$ConnectionStringValues = $ConnectionString -split ';' | ForEach-Object { $Tokens = $_ -split '=',2; #{ Key = $Tokens[0]; Value = $Tokens[1] } }
$Endpoint = ($ConnectionStringValues | Where-Object { $_.Key -eq 'Endpoint' }).Value
$Credential = ($ConnectionStringValues | Where-Object { $_.Key -eq 'Id' }).Value
$Secret = ($ConnectionStringValues | Where-Object { $_.Key -eq 'Secret' }).Value
if ([string]::IsNullOrWhitespace($Endpoint) -or [string]::IsNullOrWhitespace($Credential) -or [string]::IsNullOrWhitespace($Secret)) {
throw "Invalid App Configuration connection string"
}
$UtcNow = (Get-Date).ToUniversalTime().ToString('ddd, d MMM yyyy HH:mm:ss \G\M\T')
$EndpointHost = $Endpoint -replace '^https?://(.*)$','$1'
$ContentHash = [Convert]::ToBase64String(
[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($(if ($Body -ne $null) { "$Body" } else { '' }))
)
)
$StringToSign = "$Method`n$RequestUri`n$UtcNow;$EndpointHost;$ContentHash"
$HmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
$HmacSha256.Key = [Convert]::FromBase64String($Secret)
$Signature = [Convert]::ToBase64String(
$HmacSha256.ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($StringToSign)
)
)
$Headers = #{
'Host' = $EndpointHost;
'x-ms-date' = $UtcNow;
'x-ms-content-sha256' = $ContentHash;
'Accept' = 'application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json';
'Authorization' = "HMAC-SHA256 Credential=$Credential&SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=$Signature";
}
$Uri = "$Endpoint$RequestUri"
$Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -Body $Body
if ($Response.StatusCode -eq 200) {
[System.Text.Encoding]::UTF8.GetString($Response.Content) | ConvertFrom-Json
}
}
Example invocation:
function Get-AppConfigKeyValue {
param(
[Parameter(Mandatory = $true)] [string] $ConnectionString,
[Parameter(Mandatory = $true)] [string] $Key,
[Parameter(Mandatory = $false)] [string] $Label = ''
)
$UrlEncodedKey = [System.Net.WebUtility]::UrlEncode($Key)
$UrlEncodedLabel = [System.Net.WebUtility]::UrlEncode($Label)
# https://learn.microsoft.com/azure/azure-app-configuration/rest-api-key-value
$Method = 'GET'
$ApiVersion = '1.0'
$RequestUri = '/kv'
#if (![string]::IsNullOrWhitespace($UrlEncodedKey)) {
# $RequestUri += "/$UrlEncodedKey" # Strict key/label matching, no support for wildcards like *.
#}
$RequestUri += "?api-version=$ApiVersion"
if (![string]::IsNullOrWhitespace($UrlEncodedKey)) {
$RequestUri += "&key=$UrlEncodedKey" # Key filter, accepts "*" to match all keys.
}
if (![string]::IsNullOrWhitespace($UrlEncodedLabel)) {
$RequestUri += "&label=$UrlEncodedLabel" # Label filter, accepts "*" to match all labels.
} else {
$RequestUri += "&label=%00" # Matches KV without a label.
}
(Invoke-AppConfigRequest -ConnectionString $ConnectionString -RequestUri $RequestUri).items
}
Take a look at the Az.AppConfiguration module. I can't vouch for the quality of it or how complete it is, but it's out there. The Az module doesn't look like it is included as a dependency.
You can download is from the PowerShell Gallery with:
Install-Module -Name Az.AppConfiguration -Repository PSGallery -Scope CurrentUser

How to automate the pipelining in azure devops using rest api

I would look to automate process of creating build, releases in azure devops. I do understand that rest api's exist. But will they help to automate the process and can this be done in node.js?
yes. Azure Devops do have API. I do not have experiences in node.js.
Docu for Azure DevOps REST API here:
https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-5.0
You may find node.js libraries here:
https://github.com/microsoft/azure-devops-node-api
Can you give a few more details about your desired automation process?
If you mean also the creation of definitions, then I would also have a look into YAML. In the future, there will be few more update especially for the Release Definitions:
https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema
I implemented a Powershell (but not with Node.js) wrapper for the Azure DevOps Api . Here are some code snippets to create a new release:
SET UP CONNECTION
<#
.SYNOPSIS
Sets the environment parameter for the current session so that the commandlets can access Azure DevOps.
#>
function Set-AzureDevOpsEnvironment {
Param(
<# The account name of the Azure DevOps tenant to access. If not set, this is set to "vanstelematics" #>
[Parameter(Mandatory=$true)]
$AzureDevOpsAccountName,
<# The project name of Azure DevOps to work with. If not set, this is set to "ScaledPilotPlatform" #>
[Parameter(Mandatory=$true)]
$AzureDevOpsProjectName,
<# The PAT to access the Azure DevOps REST API. If not set, the function tries to read the PAT from a
textfile called "AzureDevOpsPat.user" either in the current working directory or in the profile
directory of the current user. The textfile must contain only that PAT as plain string. #>
[Parameter(Mandatory=$false)]
$AzureDevOpsPat
)
if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) {
$Script:Verbose = $true
} else {
$Script:Verbose = $false
}
if (!$AzureDevOpsPat) {
$paths = #("AzureDevOpsPat.user", (JOin-Path $env:USERPROFILE "AzureDevOpsPat.user"))
foreach ($path in $paths) {
if (Test-Path $path -ErrorAction SilentlyContinue) {
$AzureDevOpsPat = Get-Content $path
break
}
}
if (!$AzureDevOpsPat) {
Write-Host "AzureDevOpsPat is empty and file AzureDevOpsPat.user not found." -ForegroundColor Red
return
}
}
Write-Host "The Azure DevOps project '$($AzureDevOpsProjectName)' inside the Azure DevOps account '$($AzureDevOpsAccountName)' will be used."
$Script:AzureDevOpsAccountName = $AzureDevOpsAccountName
$Script:AzureDevOpsProjectName = $AzureDevOpsProjectName
$Script:AzureDevOpsPat = $AzureDevOpsPat
}
REST CALLER
function Call-AzureDevOpsApi($Url, $JsonBody, [ValidateSet("GET", "DELETE", "POST", "PUT", "PATCH")]$Method, $ContentType) {
if ($Script:Verbose) {
Write-Host "Calling $($Method) $($Url)"
}
if (!$ContentType) {
$ContentType = "application/json"
}
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(("{0}:{1}" -f "", $Script:AzureDevOpsPat)))
$parameters = #{
Headers = #{Authorization = ("Basic {0}" -f $base64AuthInfo)};
Method = $Method;
Uri = $Url;
}
if ($Method -in #("POST", "PUT", "PATCH")) {
if (!$JsonBody) {
Write-Error "A JsonBody is required for method $($Method)."
return
}
$JsonBodyUtf8 = [Text.Encoding]::UTF8.GetBytes($JsonBody)
$parameters["Body"] = $JsonBodyUtf8
$parameters["ContentType"] = $ContentType
}
$result = Invoke-RestMethod #parameters
return $result
}
function Call-AzureDevOpsApiPost($Url, $JsonBody, [Parameter(Mandatory=$False)][ValidateSet("application/json", "application/json-patch+json")]$ContentType) {
return Call-AzureDevOpsApi -Url $Url -JsonBody $JsonBody -ContentType $ContentType -Method POST
}
function Call-AzureDevOpsApiPut($Url, $JsonBody, [Parameter(Mandatory=$False)][ValidateSet("application/json", "application/json-patch+json")]$ContentType) {
return Call-AzureDevOpsApi -Url $Url -JsonBody $JsonBody -Method PUT
}
function Call-AzureDevOpsApiPatch($Url, $JsonBody, [Parameter(Mandatory=$False)][ValidateSet("application/json", "application/json-patch+json")]$ContentType) {
return Call-AzureDevOpsApi -Url $Url -JsonBody $JsonBody -Method PATCH
}
function Call-AzureDevOpsApiGet($Url, [Parameter(Mandatory=$False)][ValidateSet("application/json", "application/json-patch+json")]$ContentType) {
return Call-AzureDevOpsApi -Url $Url -Method GET
}
function Call-AzureDevOpsApiDelete($Url, [ValidateSet("application/json", "application/json-patch+json")]$ContentType) {
return Call-AzureDevOpsApi -Url $Url -Method DELETE
}
NEW RELEASE
<#
.SYNOPSIS
Creates a new release for a given Release Definition and artifact (p.e. build)
#>
function New-Release {
Param(
<# The id of the release definition to create the release for. #>
[Parameter(Mandatory=$true)]
$ReleaseDefinition,
<# The alias of the artifact of the release definition to create the release for #>
[Parameter(Mandatory=$true)]
$ArtifactAlias,
<# The version of the artifact (p.e. the id of the build)#>
[Parameter(Mandatory=$true)]
$ArtifactVersion,
<# The description/name of the release #>
[Parameter(Mandatory=$true)]
$Description
)
$url = "https://vsrm.dev.azure.com/$($Script:AzureDevOpsAccountName)/$($Script:AzureDevOpsProjectName)/_apis/release/releases?api-version=4.1-preview.6"
$releaseData = #{
"definitionId" = $ReleaseDefinition.id;
"description" = $Description;
"artifacts" = #(
#{
"alias" = $ArtifactAlias;
"instanceReference" = $ArtifactVersion
}
);
"isDraft" = $false;
"reason" = "none";
"manualEnvironments" = $ReleaseDefinition.environments | select -ExpandProperty name
}
$result = Call-AzureDevOpsApiPost -Url $url -JsonBody ($releaseData | ConvertTo-Json -Depth 100)
return $result
}
Hope this gives an idea how to use it.

Resources