send multiple parameter to Azure-Devops pipeline job via Powershell - azure-devops

I am able to kick off an Azure Devops Build job and send a text parameter but I only seem to be able to send a single parameter and not more.
I need to send a token parameter Ok that works but I also want to send a text param that contains a json payload to be processed by a Powershell script in the Build job.
So I have a Hellow World definition setup with two variables in Pipeline variables.
First one is tokentext the second one is jsonInput.
Both have the checkbox "Settable at queue time" checked.
I have a PowerShell Inline script in the job definition with this:
Write-Host "Hello World"
Write-Host "tokentext: $(tokentext) `n"
Write-Host "Json Input"
Write-Host "---------------------------------------------------"
$(jsonInput)
My Body that I am sending to the Invoke-RestMethod is:
$body = #{
definition = #{
id = $buildDefID
}
parameters = "{`"tokentext`" :$mytoken}
{`"jsonInput`" :$j}
"
}
$b = $body | ConvertTo-Json
This works but the above does not:
$body = #{
definition = #{
id = $buildDefID
}
parameters = "{`"tokentext`" :$mytoken}
"
}
$b = $body | ConvertTo-Json
I have tried it with a comma separating the parameters etc.. All kids of things I have tried. I could sure use some assistance if anyone is sening multiple parameters to variables in a build Definition from a script.
Thanks

Below request body works for me. Please check it out.
$body = #{
definition= #{id = $buildDefID};
parameters ="{`"jsonInput`":`"$jsonInput`", `"tokentext`":`"$tokentext`"}"
}
$b = $body | ConvertTo-Json
You can also run your pipeline via Runs - Run Pipeline rest api which is less complicated.
POST https://dev.azure.com/{organization}/{project}/_apis/pipelines/{pipelineId}/runs?api-version=6.1-preview.1
pipelineId is the $buildDefID
You can put the parameters in your request body as below:
$body=#{
variables = #{
jsonInput= #{value = $jsonInput};
tokentext= #{value = $tokentext}
}
}
$b = $body | ConvertTo-Json

Please try this:
$body = #{
definition = #{
id = $buildDefID
}
parameters = "{`"tokentext`" :$mytoken, `"jsonInput`" :$j}"
}
$b = $body | ConvertTo-Json

Related

How to output array on new lines using PowerShell and Microsoft Teams webhook

Attempting to post using Teams webhook the count of users in array and each user in array on new line
The user count works as expected, but the user name list does not
I have tried
`n`n
prints first value in array then nothing more
Below print the values and plus \n\n for example all on one line
\n\n
\r\r
\r\n
<br><br>
$URI = " "
$AD = "abc","bcd","cdb","bva","dfdf","4gfd09","sdf435"
$Date = get-date
$AddUsersList = ($AD -join "\r\r")
write-host "Build Teams message"
$JSONBody = [PSCustomObject][Ordered]#{
"#type" = "MessageCard"
"#context" = "<http://schema.org/extensions>"
"themeColor" = '25bc15'
"title" = "TEST - $Date"
"text" = "
$($AD.count) users were added
$($AddUsersList)
"
}
$TeamMessageBody = ConvertTo-Json $JSONBody
$parameters = #{
"URI" = $URI
"Method" = 'POST'
"Body" = $TeamMessageBody
"ContentType" = 'application/json'
}
Invoke-RestMethod #parameters
If you want to add new line to array using PowerShell, you have to use the pipe operator to pipe the array variable to Out-String.
Let’s consider an example of an Employee array having employee names stored in an array variable as below
$EmployeeArrayList = 'Tom Smith','Adam Strauss','Tim Smith','Gary willy'
$empArray = $EmployeeArrayList | Out-String
Write-Host $empArray
The output of the above script in PowerShell to add new line in Array as below

Renaming devices in intune via Powershell

I am trying to write a PowerShell script that allows me to update all the names of our devices in Intune [430ish devices] to reflect our asset tags. When they were imported into our tenant, they were given the serialNumber of the device as their deviceName. All permissions for the API have been applied:
API Permissions:
Device Read
Device Read all
DeviceManagementApps.ReadAll
DeviceManagementApps.ReadWriteAll
DeviceManagementConfiguration.ReadAll
DeviceManagementConfiguration.ReadWriteAll
DeviceManagementManagedDevices.PrivilegedOperations.All
DeviceManagementManagedDevices.ReadAll
DeviceManagementManagedDevices.ReadWriteAll
DeviceManagementRBAC.ReadAll
DeviceManagementRBAC.ReadWriteALL
DeviceManagementServiceConfig.ReadAll
DeviceManagementServiceConfig.ReadWriteAll
User Read
This is the code as far as I can get it, but I am still getting the following error [I apologise for ugly or poorly formatted code, I have had no formal training, all learnt using google-fu!]:
# Setting variables for connecting to the MS API
$ApplicationID = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
$TenantDomainName = "contoso.com"
$AccessSecret = Read-Host "Enter Secret"
# Connect to MSGraph command to run
Connect-MSGraph
# Setting the body of the json
$Body = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $ApplicationID
Client_Secret = $AccessSecret
}
# Authenticating the connection to MSGraph
$ConnectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantDomainName/oauth2/v2.0/token" `
-Method POST -Body $Body
$token = $ConnectGraph.access_token
# Importing the CSV of device information
$csvfile = "C:\<Path to file>"
Import-Csv $csvfile | ForEach-Object {
$serialNumber = $_.serialNumber;
$tag = $_.tag;
$deviceId = $serialNumber
Write-Host "Renaming machine from: $deviceID to: $tag" -ForegroundColor Cyan
# Getting the Device from the CSV and then putting it into MSGraph compatible Json
$DeviceToRename = Get-IntuneManagedDevice -Filter ("serialNumber eq '$serialNumber'")
Foreach ($Device in $DeviceToRename) {
$Resource = "deviceManagement/managedDevices('$DeviceId')/setDeviceName"
$graphApiVersion = "Beta"
$uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices/executeAction"
#This JSON format doesnt work
# $JSONPayload = #"
# { <NEW>
# "body": <NEW>
# {
# action: "setDeviceName",
# actionName: "setDeviceName",
# deviceName: "$tag",
# realaction: "setDeviceName",
# restartNow: false
# }
# } <NEW>
#"#
#Don't know if this works properly either?
$JSONPayload = #"
{
"#odata.type": "#microsoft.graph.managedDevice",
"actionName": "setDeviceName",
"deviceName": "$tag"
}
"#
# Writing out to check if this is working correctly
Write-Host $JSONPayload
# Converting $JSONPayload to an actual workable JSON
$convertedJSON = ConvertTo-Json $JSONPayload
try {
Invoke-MSGraphRequest -Url $uri -HttpMethod PATCH -Body $JSONPayload -ContentType "application/Json" -Verbose
} catch {
# Dig into the exception to get the Response details.
Write-Host "StatusCode:" "$_.Exception.Response.StatusCode.value__"
Write-Host "StatusDescription:" "$_.Exception.Response.StatusDescription"
Write-Host "StatusCode2:" "$_.ErrorDetails.Message"
}
}
}
Error response:
StatusCode: A parameter cannot be found that matches parameter name 'Body'..Exception.Response.StatusCode.value__
StatusDescription: A parameter cannot be found that matches parameter name 'Body'..Exception.Response.StatusDescription
StatusCode2: A parameter cannot be found that matches parameter name 'Body'..ErrorDetails.Message
Thanks
Tom
I had similar problems some months ago manipulating intune devices from an powershell runbook over graph. In my case the json body was the problem. I had to define the body first as hashtable and then convert it to json. Try something like this:
# JSONPayload as hashtable instead of string
$JSONPayload = #{
"#odata.type" = "#microsoft.graph.managedDevice"
"actionName" = "setDeviceName"
"deviceName" = "$tag"
}
# Writing out to check if this is working correctly
$JSONPayload
# Converting $JSONPayload to an actual workable JSON
$convertedJSON = $JSONPayload | ConvertTo-Json
And then pass the $convertedJSON to your graph call as body:
Invoke-MSGraphRequest -Url $uri -HttpMethod POST -Content $convertedJSON -Verbose
EDIT:
You are calling the endpoint /deviceManagement/managedDevices/executeAction with the http method PATCH. According to this ms docs article you have to call the endpoint with the http method POST.

How to get the workitems from AzureDevOps with RestApi in Powershell

Param(
[string]$collectionurl = "https://dev.azure.com",
[string]$project = "projectname",
[string]$token = "PAT"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo =
[Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}" -f
$token)))
$baseUrl =
"$collectionurl/$project/_apis/wit/reporting/workitemrevisions?
includeLatestOnly=true&api-version=5.0-preview.2"
$response = (Invoke-RestMethod -Uri $baseUrl -Method Get -
UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f
$base64AuthInfo)}).values
$wits = $response | where({$_.fields.'System.WorkItemType' -eq
'Task'}) # Only retrieve Tasks
$witrevisions = #()
foreach($wit in $wits){
$customObject = new-object PSObject -property #{
"WitID" = $wit.fields.'System.Id'
"rev" = $wit.fields.'System.Rev'
"Title" = $wit.fields.'System.Title'
"AssignedTo" = $wit.fields.'System.AssignedTo'
"ChangedDate" = $wit.fields.'System.ChangedDate'
"ChangedBy" = $wit.fields.'System.ChangedBy'
"WorkItemType" = $wit.fields.'System.WorkItemType'
}
$witrevisions += $customObject
}
$witrevisions | Select-Object `
WitID,
rev,
Title,
AssignedTo,
ChangedDate,
ChangedBy,
WorkItemType #|export-csv -Path E:\ashwin\devdata.csv -
NoTypeInformation
Write-Output $witrevisions
I want to display the workitems in my project to be displayed using powershell with the following Rest Api using my PAT.
https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{id}?api-version=5.1
How to get the workitems from AzureDevOps with RestApi in Powershell
The result will display in the output, you will find like following:
If you do not find above output, make sure you have workitem with Task type, because you have set the condition 'System.WorkItemType' -eq 'Task' in the powershell scripts.
On the other hand, you could export the work item list to a *.csv file, this part of the code is commented in powershell:
WorkItemType #| export-csv -Path G:\temp\WIT.csv -NoTypeInformation
If you want to create a *.csv file, you need to remove the # in that line, it should be :
WorkItemType | export-csv -Path G:\temp\WIT.csv -NoTypeInformation
Now, we could get that file in our local folder:
Note: The path is G:\temp is a local path, you should use the private agent, if you are using the hosted agent, you should copy that file from the hosted agent, and publish it to pipeline artifact.
Hope this helps.

Adding Azure Table Entity with Powershell with REST API

I've been struggling with what appears to be a common problem: formatting my authorization header for the Azure Table Service REST API. I have been unable to find an example using PowerShell and SharedKey, and am worried that I am making some dumb mistake in working backwards from other examples.
The specific (though unspecific) error is: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
I have been referencing these articles, as well as other examples:
https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/Authentication-for-the-Azure-Storage-Services?redirectedfrom=MSDN
https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/addressing-table-service-resources
I've confirmed that my key is correct, the table exists, has at least one row, and a number of other suggestions without luck.
If there is another approach I should take to accomplish the same goal, I'm all ears. I would like to use the REST API to allow for maximum compatibility on the clients.
$tableEndpoint = 'https://STORAGEACCOUNTNAME.table.core.windows.net/'
$tableName = 'TABLENAME'
$StorageAccountName = 'STORAGEACCOUNTNAME'
$Key = "STORAGEACCOUNTKEY"
Function New-AuthorizationHeader
{
param ($canonicalizedString)
[byte[]]$Bytes = [system.convert]::FromBase64String($Key)
$HMACSHA256 = [System.Security.Cryptography.HMACSHA256]::new($Bytes)
$dataToHmac = [System.Text.Encoding]::UTF8.GetBytes($canonicalizedString)
$Signature = [System.Convert]::ToBase64String($HMACSHA256.ComputeHash($dataToHmac))
[string]$AuthorizationHeader = "{0} {1}:{2}" -f "SharedKey",$StorageAccountName,$Signature
$AuthorizationHeader
}
Function New-Entity
{
param ($jsonContent)
$requestMethod = "POST"
$contentMD5 = [string]::Empty
$storageServiceVersion = '2016-05-31'
$reqDate = (Get-Date -Format r)
$contentType = "application/json"
$canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,($tableEndpoint + $tableName)
$stringToSign = "{0}`n{1}`n{2}`n{3}`n{4}" -f $requestMethod,$contentMD5,$contentType,$reqDate,$canonicalizedResource
$authorizationHeader = New-AuthorizationHeader -canonicalizedString $stringToSign
$content = [System.Text.Encoding]::UTF8.GetBytes($jsonContent)
$fullURI = New-Object -TypeName System.Uri -ArgumentList ($tableEndpoint + $tableName)
$httpWebRequest = [System.Net.WebRequest]::Create($fullURI)
$httpWebRequest.Accept = 'application/json;odata=fullmetadata'
$httpWebRequest.ContentLength = $content.length
$httpWebRequest.ContentType = $contentType
$httpWebRequest.Method = $requestMethod
$httpWebRequest.Headers.Add("x-ms-date", $reqDate)
$httpWebRequest.Headers.Add("x-ms-version", $storageServiceVersion)
$httpWebRequest.Headers.Add("Authorization", $authorizationHeader)
$httpWebRequest.Headers.Add("Accept-Charset", "UTF-8")
$httpWebRequest.Headers.Add("DataServiceVersion", "3.0;NetFx")
$httpWebRequest.Headers.Add("MaxDataServiceVersion", "3.0;NetFx")
$requestStream = $httpWebRequest.GetRequestStream()
$requestStream.Write($content, 0, $content.length)
$requestStream.Close()
$response = $httpWebRequest.GetResponse()
$dataStream = $response.GetResponseStream()
$reader = New-Object -TypeName System.IO.StreamReader($dataStream)
$responseFromServer = $reader.ReadToEnd()
}
$jsonContent = #"
{
"ExecutionStatus"="smapledata",
"PartitionKey"="$ENV:Username",
"RowKey"="PrinterScript"
}
"#
New-Entity -jsonContent $jsonContent
Please make 2 changes above:
Canonical resource should not have table endpoint. So it should be:
$canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,$tableName
JSON body should be properly formatted. So it should be:
$jsonContent = #"
{
"ExecutionStatus":"smapledata",
"PartitionKey":"$ENV:Username",
"RowKey":"PrinterScript"
}
"#
Once you make these changes, the code should work just fine.
Thank you again for your responses, Gaurav.
I verified that my clock is not skewed. In the end, I switched to using a Shared Access Signature, which is probably a better practice anyway.
Using a SAS obviates the need for an Authorization header (and getting that correctly formatted).
Here's the relevant updated PowerShell:
$tableEndpoint = 'https://STORAGEACCOUNT.table.core.windows.net/'
$tableName = 'TABLENAME'
$SAS = "?sv=2016-05-31&ss=t&srt=o&sp=wa&se=2017-09-01T04:08:11Z&st=2017-03-14T20:08:11Z&spr=https&sig=SIGNATURE"
$URI = $tableEndpoint + $tableName + $SAS
If (-NOT $script:RunLogKeyTime)
{
$script:RunLogKeyTime = (Get-Date -Format 'yyyyMMdd-HHmmss')
}
$RequestBody = ConvertTo-Json -InputObject #{
"TagetName"= $TargetName;
"Message"= $Message;
"ComputerName"= $ENV:ComputerName;
"Username"= $ENV:Username;
"EntryType"= $EntryType;
"PartitionKey"= "$Username`_$ScriptIdentifier";
"RowKey"= "$EntryType"}
$EncodedRequestBody = [System.Text.Encoding]::UTF8.GetBytes($RequestBody)
$RequestHeaders = #{
"x-ms-date"=(Get-Date -Format r);
"x-ms-version"="2016-05-31";
"Accept-Charset"="UTF-8";
"DataServiceVersion"="3.0;NetFx";
"MaxDataServiceVersion"="3.0;NetFx";
"Accept"="application/json;odata=nometadata";
"ContentLength"=$EncodedRequestBody.Length}
Invoke-WebRequest -Method POST -Uri $URI -Headers $RequestHeaders -Body $EncodedRequestBody -ContentType "application/json"

Best practice for emailing a body of appended strings?

I have a Powershell script that automates a process and emails the report of what happened.
Send-MailMessage -To $toAddress -From "no-reply#domain.org" -subject "Automation status" -body $bodystr -SmtpServer SERVER1 -EA Stop
So $bodystr is essentially an appended string throughout the script to report what happened and has multiple lines. Things like:
$bodystr = $bodystr + "Line found: 305`n"
$bodystr = $bodystr + "Moving line 305 to 574`n"
The Send-MailMessage command is at the bottom of the script outside any function. But most other code is in various different functions.
The issue is $bodystr does not seem accessible inside functions, and so the email is lacking a lot of information.
I believe I could use Set-Variable or passing arguments, but there are so many arguments it seems farther away from best practice to add a new argument for each function just to keep the string updated.
What's the best practice to handle this?
As a general rule, don't write data back to variables outside the scope of your function.
If you are compiling an email by gathering data from multiple sources, abstract it away in multiple functions that does one thing each and have them return a multiline string with the relevant output.
At the end of your script, collect the different message body parts and join them to a single string before sending.
In this example, we have a script that takes a path to a log file, defines a function to extract errors from a log file, and send an email with the errors in the body:
param(
[ValidateScript({Test-Path $_ -PathType Leaf })]
[string]$LogPath = 'C:\Path\To\File.log',
[string]$From = 'noreply#company.example',
[string]$To = #('ceo#company.example','finance#company.example'),
[string]$Subject = 'Super Important Weekly Report',
[string]$SmtpServer = $PSEmailServer,
[string]$Credential
)
# Define functions with a straight forward purpose
# e.g. Searching a logfile for errors
function Parse-Logfile {
param($LogPath)
[string[]]$LogErrors = #()
Get-Content $LogPath |ForEach-Object{
if($_ -contains $Error){
$LogErrors += $_
}
}
# Create and return a custom object has the error details as properties
New-Object psobject -Property #{
ErrorCount = $LogErrors.Count
Errors = $LogErrors
}
}
# Create a email template that's easy to maintain
# You could store this in a file and add a $TemplateFile parameter to the script ;-)
$EmailTemplate = #'
Hi there!
Found {0} errors in log file: {1}
{2}
Regards
Zeno
'#
# Use your function(s) to create and gather the details you need
$ErrorReport = Parse-Logfile -LogPath $LogPath
# If necessary, concatenate strings with -join
$ErrorString = $ErrorReport.Errors -join "`n"
# Use the format operator to the final Body string
$Body = $EmailTemplate -f $ErrorReport.ErrorCount, $LogPath, $ErrorString
# Set up a splatting table (Get-Help about_Splatting)
$MailParams = #{
To = $To
From = $From
Subject = $Subject
Body = $Body
SmtpServer = $SmtpServer
}
if($PSBoundParameters.ContainsKey('Credential')){
$MailParams['Credential'] = $Credential
}
# Send mail
Send-MailMessage #MailParams