Automation VSTS REST API to create new project - azure-devops

I'm trying to learn how to use REST API, but I'm at square one. The part of not knowing what tool to use to interact with VSTS REST API or how to configure it.
Any help on like "you should use this tool" and "this is how you connect to it".
Small goal is to be able to just get a list of projects in a VSTS account I own and I can build from there.

You can use PowerShell to use REST API of TFS/VSTS easily.
Here is a sample code to list all projects
param(
[Parameter(Mandatory=$true)]
[string] $tfsUri,
[Parameter(Mandatory=$true)]
[string] $token
<#[Parameter(Mandatory=$true)]
[string] $User,
[Parameter(Mandatory=$true)]
[string] $Password#>
)
<# Base64-encodes the Personal Access Token (PAT) appropriately #>
$User=''
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$token)));
$header = #{Authorization=("Basic {0}" -f $base64AuthInfo)};
<#---------------------------------------------------------------------- #>
<#
$securePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($User, $securePassword)
#>
$reportName = 'TeamProjectList.html'
$teamProjectsReport = '<!DOCTYPE html><html><head>
<!--mce:0-->
</head><body>'
$teamProjectsReport = $teamProjectsReport + '<h2><u><center>' + 'Team Project List' + '</center></u></h2>'
$teamProjectsReport | Out-File -Force $reportName
$teamProjectsReport = '';
$Uri = $tfsUri + '/_apis/projectCollections?api-version=1.0'
$tfsCollections = Invoke-RestMethod -Method Get -ContentType application/json -Uri $Uri -Headers $header #-Credential $credential
foreach($tfsCollection in $tfsCollections.value)
{
$top=100;
$skip=0;
$collectionName = $tfsCollection.name;
if ($tfsUri.Contains('visualstudio.com'))
{
$collectionName = 'DefaultCollection'
}
write-host '==============================='
write-host $tfsCollection.Name
$teamProjectsReport = '<ul><h4>'+ $collectionName + '</h4>';
write-host '-------------------------------'
$collectionProjectList = #();
while($true)
{
$Uri = $tfsUri + '/' + $collectionName + '/_apis/projects?$top='+ $top + '&$skip='+ $skip + '&api-version=1.0'
$tfsProjects = Invoke-RestMethod -Method Get -ContentType application/json -Uri $Uri -Headers $header #-Credential $credential
$skip+=$top;
if($tfsProjects.count -le 0)
{
$orderedProjects = $collectionProjectList| Sort-Object -Property name
foreach($tfsProject in $orderedProjects)
{
write-host $tfsProject.Name
$teamProjectsReport = $teamProjectsReport + '<li> <a target="_blank" href="' + $tfsUri + '/' + $collectionName + '/'+ $tfsProject.Name + '" >' + $tfsProject.Name + '</a></li>';
}
$teamProjectsReport = $teamProjectsReport + '</ul>'
$teamProjectsReport | Out-File -Append -Force $reportName
$teamProjectsReport = '';
break;
}
$collectionProjectList += $tfsProjects.value
}
write-host '-------------------------------'
}
$teamProjectsReport = $teamProjectsReport + '</ul></body></html>'
$teamProjectsReport | Out-File -Append -Force $reportName
To execute this PowerShell in VSTS or TFS 2017 or 2018 you can create a Persona Access Token (PAT) with all scopes (all scopes required here to enable access to collection level information). You can call the script with you VSTS account as shown below.
.\GetAllProjects.ps1 -tfsUri 'https://youraccount.visualstudio.com' -token 'xxxxxxxxPATxxxxxxxxxxxxxx'
To call the script for TFS on premise 2017, 2018 you can use either of below depending on your TFS is setup with SSL.
.\GetAllProjects.ps1 -tfsUri 'https://yourtfs/tfs' -token 'xxxxxxxxPATxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
or
.\GetAllProjects.ps1 -tfsUri 'http://yourtfs:8080/tfs' -token 'xxxxxxxxPATxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Project are listed as below
More information on script available here

Related

Custom Azure Log from Azure Resource Graph query

I have Runbook under Azure automation account that should collect results from Resource graph query and pass it to Log analytics as custom log. I have managed to create a script that works fine.
$customerId = "xxxxxxxxx"
$SharedKey = "xxxxxxxxxxxxxxxx"
$LogType = "MyRecord"
$TimeStampField = ""
#function block
Function Connect-ToAzure {
$connectionName = "AzureRunAsConnection"
$automationAccountName = Get-AutomationVariable -Name 'automationAccountName'
Write-Output "Azure Automation Account Name - $automationAccountName"
$connectionName = "AzureRunAsConnection"
Write-Output "Connection Name - $connectionName"
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
Write-Output "Logging in to Azure..."
Connect-AzAccount -ServicePrincipal -Tenant $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
-Subscription $servicePrincipalConnection.SubscriptionId
}
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
$xHeaders = "x-ms-date:" + $date
$stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
$keyBytes = [Convert]::FromBase64String($sharedKey)
$sha256 = New-Object System.Security.Cryptography.HMACSHA256
$sha256.Key = $keyBytes
$calculatedHash = $sha256.ComputeHash($bytesToHash)
$encodedHash = [Convert]::ToBase64String($calculatedHash)
$authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
return $authorization
}
# Create the function to create and post the request
Function Post-LogAnalyticsData($customerId, $sharedKey, $body, $LogType)
{
$method = "POST"
$contentType = "application/json"
$resource = "/api/logs"
$rfc1123date = [DateTime]::UtcNow.ToString("r")
$contentLength = $body.Length
$signature = Build-Signature `
-customerId $customerId `
-sharedKey $sharedKey `
-date $rfc1123date `
-contentLength $contentLength `
-method $method `
-contentType $contentType `
-resource $resource
$uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
$headers = #{
"Authorization" = $signature;
"Log-Type" = $LogType;
"x-ms-date" = $rfc1123date;
"time-generated-field" = $TimeStampField;
}
Write-Output "Sending a request"
$response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
return $response.StatusCode
Write-Output "Request has been sent"
}
try {
Write-Output "Starting runbook"
$customerId = "xxxxxxxxx"
$SharedKey = "xxxxxxxxxxxxxxxx"
$LogType = "MyRecord"
$Query = #'
resources
| where type == "microsoft.compute/disks"
| extend diskState = properties.diskState
| extend diskSizeGB = properties.diskSizeGB
| where properties['encryptionSettingsCollection'] != "enabled"
| where diskState == "Attached" or diskState == "Reserved"
| extend diskCreationTime = properties.timeCreated
| extend hostVM = split(managedBy,"/")[-1]
| project diskCreationTime, name, resourceGroup, hostVM, diskState
'#
Connect-ToAzure
$Result = (Search-AzGraph -Query $Query -First 1000)
$jsonResult = $Result | ConvertTo-Json
Post-LogAnalyticsData -customerId $customerId -sharedKey $sharedKey -body $jsonResult -logType $LogType
Write-Output "Runbook has been finished"
}
catch {
Write-Error -Message $_
break
}
However, I have an issue with collection of logs. I gather logs only from one subscription.
Can someone please help with code adjustment? How can I gather results from all subscriptions not just from one? I assume it should be foreach ($sub in $subs), but not sure how to do it combined with this graph query.
As the run as account will retire by Sep'23 so I would recommend using managed identities approach in your runbook i.e., configure role assignment for managed identity across multiple subscriptions as explained here and update your Connect-ToAzure function block something like shown below. For more context with regards to it, refer this Azure document.
$automationAccountName = Get-AutomationVariable -Name 'automationAccountName'
Write-Output "Azure Automation Account Name - $automationAccountName"
Disable-AzContextAutosave -Scope Process | Out-Null
Write-Output "Logging in to Azure..."
$AzureContext = (Connect-AzAccount -Identity).context
Also, you may use UseTenantScope parameter in your Search-AzGraph cmdlet to run the query across all available subscriptions in the current tenant.

synchronize an onprem fileshare to a sharepoint online site collection using powershell and Microsof RestAPI

I am trying to work out a powershell script that:
retrieves an accesstoken (MSAL) to access (read/write) a sharepoint online site with subsites and documents. Preferably the Azure APP-registration ServicePrincipal can be granted access to just that site and access the sharepoint site/files without giving consent to the whole sharepoint environment. I don't know if that is possible currently as I can only grant application permission to files.readwrite.all and sites.readwrite.all. I do not see anything like files.readwrite.shared to grant access only to sites/collections that the serviceprincipal has access to. Anyone done this? I currently use the MSAL.PS powershell module to get a token using an AppRegistration with the admin-consented readwrite.all access but would like to limit that. The code for this is now:
Import-Module MSAL.PS;
$clientid = "my-appreg-client-id";
$tenantID = 'my-tenant-id';
$thumbPrint = 'certificate-thumbprint';
$ClientCertificate = Get-Item "Cert:\CurrentUser\My\$thumbPrint";
$myAccessToken = Get-MsalToken -ClientId $clientID -TenantId $tenantID -ClientCertificate
$ClientCertificate;
The script will read all files and folders from an UNC-share and build a file-collection of the onprem files. That part of the code is in place using a Get-ChildItem call to the UNC filetree.
Then, after getting the token, I need to get the current available files in the sharepoint online site document library structure and store that in a variable/hashtable which I can use to perform lookups between the onprem filecollection and the presence of those files and (sub)folders in the sharepoint site. If a folder does not yet exist I need to create that sharepoint folder and if a file is not yet present or the onprem version is newer I need to upload that file into sharepoint.
I have a script that does this using the old sharepoint.client.dll libraries but those support only basic authentication which will be unavailable any time soon for accessing the MS Online environment. So now I am searching for code to do this using the Microsoft Graph Api or other Rest API call. I am already struggling to get the contents of a site file collection so I hope that this generic problem description is enough to get some hints and tips/resources to get going.
Many thanks,
Eric
This is what I use. I'm using powershell in Linux.
## Get the Token
$clientId = "Application (Client) ID"
$clientSecret = "Client secret"
$tenantName = "TenantName.onmicrosoft.com"
$tokenBody = #{
Grant_Type = 'client_credentials'
Scope = 'https://graph.microsoft.com/.default'
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $tokenBody -ErrorAction Stop
$headers = #{
"Authorization" = "Bearer $($tokenResponse.access_token)"
"Content-Type" = "application/json"
}
## Use the SharePoint groups ObjectID. From this we'll get the drive ID.
$site_objectid = "Groups ObjectID"
## Create all the folders on the SharePoint site first. I've set microsoft.graph.conflictBehavior below to fail because I never want to rename or replace folders.
# Set the base directory.
$baseDirectory = "/test"
$directories = get-childItem -path $baseDirectory -recurse -directory
foreach ($directory in $directories) {
$URL = "https://graph.microsoft.com/v1.0/groups/$site_objectid/sites/root"
$subsite_ID = (Invoke-RestMethod -Headers $headers -Uri $URL -Method Get).ID
$URL = "https://graph.microsoft.com/v1.0/sites/$subsite_ID/drives"
$Drives = Invoke-RestMethod -Headers $headers -Uri $URL -Method Get
$Document_drive_ID = ($Drives.value | Where-Object { $_.name -eq 'Documents' }).id
$createFolderURL = "https://graph.microsoft.com/v1.0/drives/$Document_drive_ID/items/root:{0}:/children" -f $directory.parent.FullName
$file = $directory.Name
$uploadFolderRequestBody = #{
name= "$file"
folder = #{}
"#microsoft.graph.conflictBehavior"= "fail"
} | ConvertTo-Json
invoke-restMethod -headers $headers -method Post -body $uploadFolderRequestBody -contentType "application/json" -uri $createFolderURL
}
## Upload the files. I'm only adding files that are 4 days old or less because I run the script every 3 days for backup.
## These are set in the $sharefiles variable. To upload all files just remove everything after the pipe.
$sharefiles = get-childItem $baseDirectory -recurse | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-4)}
foreach ($sharefile in $sharefiles) {
$Filepath = $sharefile.FullName
$URL = "https://graph.microsoft.com/v1.0/groups/$site_objectid/sites/root"
$subsite_ID = (Invoke-RestMethod -Headers $headers -Uri $URL -Method Get).ID
$URL = "https://graph.microsoft.com/v1.0/sites/$subsite_ID/drives"
$Drives = Invoke-RestMethod -Headers $headers -Uri $URL -Method Get
$Document_drive_ID = ($Drives.value | Where-Object { $_.name -eq 'Documents' }).id
$Filename = $sharefile.Name
$upload_session = "https://graph.microsoft.com/v1.0/drives/$Document_drive_ID/root:{0}/$($Filename):/createUploadSession" -f $sharefile.directory.FullName
$upload_session_url = (Invoke-RestMethod -Uri $upload_session -Headers $headers -Method Post).uploadUrl
## We'll upload files in chunks.
$ChunkSize = 62259200
$file = New-Object System.IO.FileInfo($Filepath)
$reader = [System.IO.File]::OpenRead($Filepath)
$buffer = New-Object -TypeName Byte[] -ArgumentList $ChunkSize
$position = 0
$counter = 0
Write-Host "ChunkSize: $ChunkSize" -ForegroundColor Cyan
Write-Host "BufferSize: $($buffer.Length)" -ForegroundColor Cyan
$moreData = $true
While ($moreData) {
#Read a chunk
$bytesRead = $reader.Read($buffer, 0, $buffer.Length)
$output = $buffer
If ($bytesRead -ne $buffer.Length) {
#no more data to be read
$moreData = $false
#shrink the output array to the number of bytes
$output = New-Object -TypeName Byte[] -ArgumentList $bytesRead
[Array]::Copy($buffer, $output, $bytesRead)
Write-Host "no more data" -ForegroundColor Yellow
}
#Upload the chunk
$Header = #{
'Content-Range' = "bytes $position-$($position + $output.Length - 1)/$($file.Length)"
}
Write-Host "Content-Range = bytes $position-$($position + $output.Length - 1)/$($file.Length)" -ForegroundColor Cyan
#$position = $position + $output.Length - 1
$position = $position + $output.Length
Invoke-RestMethod -Method Put -Uri $upload_session_url -Body $output -Headers $Header -SkipHeaderValidation
#Increment counter
$counter++
}
$reader.Close()
}

Reading and uploading zip files from Powershell

My below code successfully read a folder and uploades the subfolder one by one as a zip file in SharePoint. But if any of my file is of Zip extension, it gives me an error. I would like to know how can I fix my existing code below to handle the zip files as well so that it loads both the zip folder and non-zip files and folders in the SharePoint. I tried multiple tings like removing .zip extension in the Create-Archive and file but it does not work.
error received:
2020-12-09T15:49:05.7979920Z Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
2020-12-09T15:49:05.7981233Z At D:\a\r1\a\_DevOpsScripts\ReleaseNoteScripts\UploadInSharePoint.ps1:55 char:17
2020-12-09T15:49:05.7982104Z + ... $response = Invoke-RestMethod $uploadURLObject.uploadUrl -Method 'PUT ...
2020-12-09T15:49:05.7982819Z + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020-12-09T15:49:05.7983312Z + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
2020-12-09T15:49:05.7983979Z eption
2020-12-09T15:49:05.7984369Z + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
param($ClientID,$ClientSecret,$WId,$RName,$RReqFor,$SPid,$LibId)
$tempDirectory = "temp"
# arguments defined from predefined variables in argument section of powershell stage of pipeline
$workingDirectory = "$WId"
$CLIENT_ID = "$ClientID"
$CLIENT_SECRET = "$ClientSecret"
$SharePointSiteId = "$SPid"
$LibraryId = "$LibId"
$azx = "$RName" +"_"+"$RReqFor"
$adx = ($azx.Split("[")[0])
$artifactname = $adx.Substring(0,$adx.Length-1)
# Get access token
Function GET-TOKEN
{
param($clientId, $clientSecret)
$tokenHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$tokenHeaders.Add("Content-Type", "application/x-www-form-urlencoded")
$tokenBody = "client_id="+ $clientId + "&scope=https%3A//graph.microsoft.com/.default&client_secret=" + $clientSecret + "&grant_type=client_credentials"
$tokenBody
$tokenResponse = Invoke-RestMethod 'https://login.microsoftonline.com/test.onmicrosoft.com/oauth2/v2.0/token' -Method 'POST' -Headers $tokenHeaders -Body $tokenBody
return $tokenResponse
}
# upload the file
Function UPLOAD-FILE
{
param($workingDir, $tempDir, $clientId, $clientSecret, $artifactname)
Get-ChildItem $workingDir | ForEach-Object {
$name = $_.FullName.Split("\")[-1]
CREATE-ARCHIVE -workingDir $_.FullName -tempDir $tempDir -name $name
$file = $_.FullName + "\" +$tempDir + "\" + "$($name).zip"
$fileSize = (Get-Item $file).length
$uploadURLObject = GET-UPLOADLINK -clientId $clientId -clientSecret $clientSecret -artifactname $name
$tokenObject = GET-TOKEN -clientId $clientId -clientSecret $clientSecret
$uploadHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$uploadHeaders.Add("Content-Type", "application/json")
$uploadHeaders.Add("Content-Range", "bytes " + 0 +"-" + ($fileSize-1) + "/" + $fileSize)
$uploadHeaders.Add("Content-Length", $fileSize)
$uploadHeaders.Add("Authorization", "Bearer "+ $tokenObject.access_token)
$uploadBody = [System.IO.File]::ReadAllBytes($file)
$response = Invoke-RestMethod $uploadURLObject.uploadUrl -Method 'PUT' -Headers $uploadHeaders -Body $uploadBody
$response | ConvertTo-Json
REMOVE-TEMPDIR -workingDir $_.FullName -tempDir $tempDirectory
}
}
Function GET-UPLOADLINK
{
param($clientId, $clientSecret, $artifactname)
$tokenObject = GET-TOKEN -clientId $clientId -clientSecret $clientSecret
$uploadLinkRequestHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$uploadLinkRequestHeaders.Add("Content-Type", "application/json")
$uploadLinkRequestHeaders.Add("Authorization", "Bearer "+ $tokenObject.access_token)
$uploadLinkRequestBody = ""
#update the actual link
$fullname = "https://graph.microsoft.com/v1.0/sites/$($SPid)/drives/$($LibId)/root/children/"
$comname = $fullname + $artifactname
$uploadLinkResponse = Invoke-RestMethod "$($comname).zip/createUploadSession" -Method 'POST' -Headers $uploadLinkRequestHeaders -Body $uploadLinkRequestBody
$uploadLinkResponse | ConvertTo-Json
return $uploadLinkResponse
}
Function CREATE-ARCHIVE
{
param($workingDir, $tempDir, $name )
write-host $workingDir
cd $workingDir
md $tempDir
#Compress-Archive -Path $workingDir -DestinationPath $workingDir\$tempDir\$artifactname
Compress-Archive -Path $workingDir -DestinationPath $workingDir\$tempDir\$name
}
Function REMOVE-TEMPDIR
{
param($workingDir, $tempDir)
#This will be the last step
rm $workingDir\$tempDir -Recurse
}
UPLOAD-FILE -workingDir $workingDirectory -tempDir $tempDirectory -clientId $CLIENT_ID -clientSecret $CLIENT_SECRET -artifactname $artifactname
If it's only failing on files that are .zip prior to the CREATE-ARCHIVE function being run.
What happens if CREATE-ARCHIVE ignores .zip files?
if (-not($_.name -like "*.zip")){
CREATE-ARCHIVE....
} else {
COPY-ITEM $workingDir\temp\$($_.name)
}
Seems like archiving an archive is unnecessary work, and potentially the source of an issue.

How to create a Microsoft Teams team using a PowerShell Azure function and the Graph API?

My ultimate goal is to create a MS Teams team with channels and tabs of applications.
But first, I need to properly format my request. I dont know what I'm doing wrong.
Obviously I found this topic (https://learn.microsoft.com/en-us/graph/api/team-post?view=graph-rest-1.0) Example n°3 that looks promising but I dont know how to use it. I started with the code bellow:
$password = "stackexchange"
$login = "stackexchange#stackexchange.onmicrosoft.com"
$ownerEmail = "stackexchange#stackexchange.onmicrosoft.com"
$url = "https://graph.microsoft.com/v1.0/teams"
$securedPassword = convertto-securestring -String $password -AsPlainText -Force
$creds = new-object -typename System.Management.Automation.PSCredential -argumentlist $login, $securedPassword
$GraphAppId = "stackexchange-guid"
$GraphAppSecret = "stackexchange"
$AADDomain = "stackexchange.onmicrosoft.com"
Connect-AzureAD -Credential $creds
$userId = (Get-AzureADUser -ObjectId $ownerEmail).ObjectId
write-output $userId # Here the userId is actually displayed
Connect-PnPOnline -ClientId $GraphAppId -ClientSecret $GraphAppSecret -AADDomain $AADDomain
$accessToken = Get-PnPGraphAccessToken
$header = #{
"Content-Type" = "application/json"
Authorization = "Bearer $accessToken"
}
$body = #{
displayName = "Test"
"owners#odata.bind" = "https://graph.microsoft.com/v1.0/users('$userId')"
"template#odata.bind" = "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"
memberSettings = #{
allowCreateUpdateChannels = $true
}
messagingSettings = #{
allowUserEditMessages = $true
allowUserDeleteMessages = $true
}
funSettings = #{
allowGiphy = $true
giphyContentRating = "strict"
}
}
$Body = ConvertTo-Json -InputObject $body
Invoke-RestMethod -Uri $url -Body $Body -Method 'Post' -Headers $header -UseBasicParsing -Credential $creds
I get the following message in my PowerShell terminal :
Invoke-RestMethod : {
"error": {
"code": "BadRequest",
"message": "Invalid bind property name owners in request.",
"innerError": {
"date": "2020-09-03T15:40:53",
"request-id": "fef8bd7e-3143-4ea9-bcf6-a87702a488b8"
}
}
}
At character Line:36 : 5
+ Invoke-RestMethod -Uri $url -Body $Body -Method 'Post' -Headers $ ...
+ CategoryInfo : InvalidOperation : (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Instead of doing this all "by hand", I'd suggest looking at the Graph SDK for PowerShell. It's still 'officially' in beta, but that's this PowerShell SDK, not the Graph itself of course.
you could also use the PS nuget "MicrosoftTeams"
for example:
# ===========================================
# this Script creates a new project environment containing:
# - a new TEAMs channel
# ===========================================
Install-Module MicrosoftTeams -Force # -AllowClobber
## parameters
$TeamDisplayName='contoso'
$ProjectName='Contoso-Reporting'
$TEAMS_ChannelName=$ProjectName
## connect to TEAMS
Connect-MicrosoftTeams
## Get the Opslogix TEAM
$team = Get-Team | foreach {if ( $_.DisplayName -eq $TeamDisplayName ) { $_ }}
## create a new project channel
$team | new-TeamChannel -DisplayName $TEAMS_ChannelName
#$team | Get-TeamChannel
## disconnect TEAMS
Disconnect-MicrosoftTeams
Try changing:
"owners#odata.bind" = "https://graph.microsoft.com/v1.0/users('$userId')"
to:
members = #(
#{
'#odata.type' = "#microsoft.graph.aadUserConversationMember"
roles = #(
'owner'
)
'user#odata.bind' = "https://graph.microsoft.com/v1.0/users('$userId')"
}
)

Azure DocumentDB Rest API PowerShell delete collection 401 Unathorized

Need to delete collection in my automation process.
Trying to execute script below. Get operations working fine, but Delete operation failed with "(401) Unathorized" error. It is strange cause Delete collection do not need additional headers. Can someone give a hint, what is wrong?
$accountName = 'someaccountname'
$connectionKey = 'masterkey'
$collectionName = 'mycollection'
$databaseName = 'mydatabase'
function GetKey([System.String]$Verb = '',[System.String]$ResourceId = '',
[System.String]$ResourceType = '',[System.String]$Date = '',[System.String]$masterKey = '') {
$keyBytes = [System.Convert]::FromBase64String($masterKey)
$text = #($Verb.ToLowerInvariant() + "`n" + $ResourceType.ToLowerInvariant() + "`n" + $ResourceId + "`n" + $Date.ToLowerInvariant() + "`n" + "`n")
$body =[Text.Encoding]::UTF8.GetBytes($text)
$hmacsha = new-object -TypeName System.Security.Cryptography.HMACSHA256 -ArgumentList (,$keyBytes)
$hash = $hmacsha.ComputeHash($body)
$signature = [System.Convert]::ToBase64String($hash)
[System.Web.HttpUtility]::UrlEncode($('type=master&ver=1.0&sig=' + $signature))
}
function BuildHeaders([string]$action = "get",[string]$resType, [string]$resourceId){
$authz = GetKey -Verb $action -ResourceType $resType -ResourceId $resourceId -Date $apiDate -masterKey $connectionKey
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", $authz)
$headers.Add("x-ms-version", '2015-12-16')
$headers.Add("x-ms-date", $apiDate)
$headers
}
function GetUTDate() {
$date = get-date
$date = $date.ToUniversalTime();
return $date.ToString("r", [System.Globalization.CultureInfo]::InvariantCulture);
}
function GetDatabases() {
$uri = $rootUri + "/dbs"
$hdr = BuildHeaders -resType dbs
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $hdr
$response.Databases
Write-Host ("Found " + $Response.Databases.Count + " Database(s)")
}
function GetCollections([string]$dbname){
$uri = $rootUri + "/" + $dbname + "/colls"
$hdr = BuildHeaders -resType colls -resourceId $dbname
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $hdr
$response.DocumentCollections
Write-Host ("Found " + $Response.DocumentCollections.Count + " DocumentCollection(s)")
}
function DeleteCollection([string]$dbname){
$uri = $rootUri + "/" + $dbname + "/colls" + "/" + $collectionName
$hdrs = BuildHeaders -action DELETE -resType colls -resourceId $collectionName
$response = Invoke-RestMethod -Uri $uri -Method Delete -Headers $hdrs
Write-Host "DELETE $uri"
}
$rootUri = "https://" + $accountName + ".documents.azure.com"
write-host ("Root URI is " + $rootUri)
#validate arguments
$apiDate = GetUTDate
$db = GetDatabases | where { $_.id -eq $databaseName }
if ($db -eq $null) {
write-error "Could not find database in account"
return
}
$dbname = "dbs/" + $databaseName
$collection = GetCollections -dbname $dbname | where { $_.id -eq $collectionName }
if($collection -eq $null){
write-error "Could not find collection in database"
return
}
Write-Host
$Delete = DeleteCollection -dbname $dbname | where { $_.id -eq $collectionName }
Normally, either the Authorization or x-ms-date header is not set (or the Authorization header with an invalid authorization token), 401 unauthorized error will be returned. I use fiddler to capture the request and check the response, and I find both Authorization and x-ms-date header are set, so it seems that the Authorization header is set to an invalid authorization token. Based on your code, I do some changes, and the function could work fine on my side.
function DeleteCollection([string]$dbname){
$uri = $rootUri + "/" + $dbname + "/colls" + "/" + $collectionName
$collectionName = $dbname + "/colls" + "/" + $collectionName
$hdrs = BuildHeaders -action DELETE -resType colls -resourceId $collectionName
#Write-Host "resourceId $collectionName"
$response = Invoke-RestMethod -Uri $uri -Method Delete -Headers $hdrs
Write-Host "resource $collectionName"
Write-Host "DELETE $uri"
}
The $collectionName should be dbs/{yourdbname}/colls/{yourcollectionname}
This can happen if there are pending operations. Either retry until it succeeds or add a delay before deleting. For us, a half second is enough to assure we never see this again.