Is the object empty? - powershell

I'm using an Azure API > https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull%20request%20query/get?view=azure-devops-rest-5.1
I'm using this code:
$body = #"
{
"queries": [{
"items": [
"59c1c31397b266116ff6d735e5638ef5d1b598a0"
],
"type": "commit"
}]
}
"#
$x = Invoke-RestMethod -Uri $someLink -Headers #{Authorization = $pat } -Body $body -Method Post -ContentType 'application/json'
Write-Host $x
Write-Host $x | ConvertFrom-Json
Write-Host $x.results | ConvertFrom-Json
I've removed the link for security reasons.
When I run the code I get the following in my console:
2020-01-20T16:17:55.8600905Z #{queries=System.Object[]; results=System.Object[]}
2020-01-20T16:17:55.8637026Z #{queries=System.Object[]; results=System.Object[]}
2020-01-20T16:17:55.8674193Z
I'm not sure if the queries and results objects are empty or that I need some other way of reading them.

Write-Host writes to the host (which is typically the console / terminal), bypassing PowerShell's success output stream, which is why a command such as Write-Host $x | ConvertFrom-Json is pointless, because ConvertFrom-Json will receive no input.
Write-Host writes simple .ToString() stringifications of non-string input objects to the host, which means that you don't get PowerShell's rich, human-friendly output formatting that implicit output or - rarely needed - explicit Write-Output calls result in when ultimately printing to the host by default, due to the output neither getting captured in a variable or getting redirected with >).
Invoke-RestMethod automatically parses the JSON output it receives into PowerShell custom objects ([pscustomobject] instances, so there is also also no conceptual reason to pipe its property values to ConvertFrom-Json - unless the API truly returns nested JSON text as string values in the JSON it returns.
If you want to visually inspect the results, simply output them directly:
$x # output visualization of the whole object
$x.results # just the .results property
The above outputs to the success output stream, which means that an outside caller of your code would receive these values as part of the code's output.
If the resulting table- or list-like formatting (depending on the number of properties) doesn't tell you enough, you can convert the objects back to JSON, by piping to ConvertTo-Json.
Note that the latter's default serialization depth is limited to 2, so you may have to pass a higher -Depth value to see the object in full - see this post.
$x | ConvertTo-Json # use -Depth <n>, if needed.
$x.results | ConvertTo-Json
If you really want to just print the values as information for the user, without becoming part of the output, use Out-Host rather than Write-Host, because Out-Host does apply the rich formatting:
$x | Out-Host # print visualization of the whole object directly to the host
$x.results | Out-Host # just the .results property
# Ditto via ConvertTo-Json
$x | ConvertTo-Json | Out-Host
...

Related

How to change a powershell Invoke-RestMethod body from its original format into something else

I have a param section of a Invoke-RestMethod that i have to keep as is, but also add parameters:
$param = #{
...
Body = '{"entry":[{"#name":SOMETEXT,"static":{"member":[ANOTHERTEXT]}}]}'
...
}
Since the entire Body is in single quotes, any $parameter added will be treated like a string instead of a normal PowerShell parameter
In this case SOMETEXT and ANOTHERTEXT will be $HubAddressObject and $ArrayList, respectivly.
How do i make that Body entry work with parameters, and keeping the same structure (this is part of a Panorama box)?
What i would need would be:
Body = '{"entry":[{"#name":$HubAddressObject,"static":{"member":[$ArrayList]}}]}'
Thanks.
I'd recommend using ConvertFrom-Json / ConvertTo-Json for that kind of thing.
To keep it one 1 line, you can use the -Compress switch.
$params = #{
Body = '{"entry":[{"#name":"SOMETEXT","static":{"member":"[ANOTHERTEXT]"}}]}'
}
# Create a PSObject representation of your JSON
$jsonObj = $Params.body | ConvertFrom-Json
#Modify whatever you want
$jsonObj.entry[0].'#name' = 'NewText'
# Convert the Object back to Json.
$Params.Body = $JsonObj | Convertto-Json -Compress -Depth 4
Json comparison
# Starting off Json
{"entry":[{"#name":"SOMETEXT","static":{"member":"[ANOTHERTEXT]"}}]}
# Modified JSON
{"entry":[{"#name":"NewText","static":{"member":"[ANOTHERTEXT]"}}]}

How do I access PowerShell object values? [duplicate]

This question already has answers here:
How can you use an object's property in a double-quoted string?
(5 answers)
Closed 1 year ago.
I currently have an Azure DevOps pipeline that makes a REST API call which returns an object of parameters:
#{location=eastus2; envName=sandbox; ...}
My PowerShell script looks like:
steps:
- powershell: |
$uri = "https://dev.azure.com/{org}/{proj}/_apis/build/latest/{buildId}?branchName={name}&api-version=6.0-preview.1"
$token = [System.Text.Encoding]::UTF8.GetBytes("$(System.AccessToken)" + ":")
$base64 = [System.Convert]::ToBase64String($token)
$basicAuth = [string]::Format("Basic {0}", $base64)
$headers = #{ Authorization = $basicAuth }
$result = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ContentType application/json
$params = $result.templateParameters
Write-Host "##vso[task.setvariable variable=paramObj]$params"
- powershell: |
Write-Host $(paramObj)
In this first PS task I can use $params.location which will return eastus2. However, in the second PS task I get an error saying:
Line |
2 | Write-Host #{location=eastus2; envName=Sandbox; sqlDBAutoPauseDatabas …
| ~~~~~~~
| The term 'eastus2' is not recognized as a name of a cmdlet,
| function, script file, or executable program. Check the
| spelling of the name, or if a path was included, verify that
| the path is correct and try again.
Since my API call has an object of parameters, I'm trying to pass that parameter object to another task where I can then use those values. In the second task seen above I can't log the result, so of course trying to use dot notation, or Select-Object all results in the same error.
I was also attempting to iterate over the parameters object so I could do something like:
steps:
- powershell: |
...
$params = $result.templateParameters
foreach ($param in $params) {
Write-Host "##vso[task.setvariable variable=test]$param.Value"
}
- powershell: |
Write-Host $(location)
But I haven't been able to figure this out either as I keep getting the same error listed above. How do I access the parameter values in a second script without hardcoding it?
You've encountered the magic of expandable string parsing in PowerShell.
When you place a variable expression, like $param, inside an expandable string (a string literal bounded with " double quotes), PowerShell will attempt to resolve it's value for you - but nothing more than that - it's not going to attempt to evaluate member invocation, like $param.Value, for example.
So a string literal like this:
"$param.Value"
Will expand to:
"#{location=eastus2;...}.Value"
Notice that $params was evaluated and it's value substituted in the string, and the .Value-part is completely ignored.
To force PowerShell to evaluate expressions more complicated than simple variable references, use the $() subexpression operator:
Write-Host "##vso[task.setvariable variable=test]$($param.Value)"

Disable conversion to UTC timezone after deserialization of a response from Invoke-Restmethod

I'm using the Invoke-RestMethod to get the data from REST API. One of the attributes in response is the date. When using Postman or other tools to get the data the date is returned correctly but when I'm using PowerShell (version 5.1.19041.906) and its Invoke-RestMethod like this:
$response = Invoke-RestMethod -Method Get -Uri $url -Headers $requestHeaders
All values from the date attribute are automatically converted to UTC. Is there any way how to disable this shift? I need the original values returned from the API.
Invoke-RestMethod, when given a JSON response, automatically parses it into a [pscustomobject] graph; in a manner of speaking, it has ConvertFrom-Json built in.
When ConvertFrom-Json does recognize what are invariably string representation of dates in the input JSON, it converts them to [datetime] instances.
In Windows PowerShell (v5.1, the latest and final version) and as of PowerShell (Core) 7.2, you get NO control over what kind of [datetime] instances are constructed, as reflected in their .Kind property:
In Windows PowerShell, which requires a custom date-string format (e.g. "\/Date(1633984531266)\/"), you invariably get Utc instances.
In PowerShell (Core) 7+, which additionally recognizes string values that are (variations of) ISO 8601 date-time strings (e.g. "2021-10-11T13:27:12.3318432-04:00"), the .Kind value depends on the specifics of the string value:
If the string ends in Z, denoting UTC, you get a Utc instance.
If the string ends in a UTC offset, e.g. -04:00 you get a Local instance (even if the offset value is 00:00)
Note that this means that the timestamp is translated to the caller's local time zone, so the original offset information is lost (unless the caller's time zone's offset happens to match).
Otherwise you get an Unspecified instance.
While Windows PowerShell will see no new features, there is a hope for PowerShell (Core): GitHub issue #13598 proposes adding a -DateTimeKind parameter to ConvertFrom-Json, so as to allow explicitly requesting the kind of interest, and to alternatively construct [datetimeoffset] instances, which are preferable.
Workaround:
Note: In the event that you need access to the raw string values, exactly as defined, the solution below wont' work. You'll have to retrieve the raw JSON text and perform your own parsing, using Invoke-WebRequest and the response's .Content property, as Mathias R. Jessen notes.
The following snippet walks a [pscustomobject] graph, as returned from Invoke-RestMethod and explicitly converts any [datetime] instances encountered to Local instances in place (Unspecified instances are treated as Local):
# Call Invoke-RestMethod to retrieve and parse a web service's JSON response.
$fromJson = Invoke-RestMethod ...
# Convert any [datetime] instances in the object graph that aren't already
# local dates (whose .Kind value isn't already 'Local') to local ones.
& {
# Helper script block that walks the object graph.
$sb = {
foreach ($el in $args[0]) { # iterate over elements (if an array)
foreach ($prop in $el.psobject.Properties) {
# iterate over properties
if ($dt = $prop.Value -as [datetime]) {
switch ($dt.Kind) {
'Utc' { $prop.Value = $dt.ToLocalTime() }
# Note: calling .ToLocalTime() is not an option, because it interprets
# an 'Unspecified' [datetime] as UTC.
'Unspecified' { $prop.Value = [datetime]::new($dt.Ticks, 'Local') }
}
}
elseif ($prop.Value -is [Array] -or $prop.Value -is [System.Management.Automation.PSCustomObject]) {
& $sb $prop.Value # recurse
}
}
}
}
# Start walking.
& $sb $args[0]
} $fromJson
# Output the transformed-in-place object graph
# that now contains only Local [datetime] instances.
$fromJson
$response = Invoke-RestMethod -Method Get -Uri $url -Headers $requestHeaders
$changeddate = $response.fields.'System.ChangedDate'
$datetime = ([DateTime]$changeddate).ToLocalTime()

Need to sort value from web request response

I need to sort specific value of BuildName: from web request response.
How can I store a particular response in a variable?
Trying with following command
Invoke-WebRequest -Uri https://s3.amazonaws.com/$url/env.js | select-object Content
I am getting the following response. I need to sort the values from there
Response will be like this
window.env = {
// Hardcode environment variables in here but tokenize customer specific ones with #{}#
BuildName: 'AppClient-develop-0716.4'
GRAPH_QL_HOST: 'https://xyz.google.com/graphql'
};
From her need to get value of BuildName:
You can convert the content to individual strings and parse those to get the individual values.
Example
# $Content = (Invoke-WebRequest -Uri https://s3.amazonaws.com/$url/env.js).Content
$Content = #'
window.env = {
// Hardcode environment variables in here but tokenize customer specific ones with #{}#
BuildName: 'AppClient-develop-0716.4'
GRAPH_QL_HOST: 'xyz.google.com/graphql'
};
'#
$Content -split "`r`n" | Select-String "\w+:" | % { ($_ -split ": ")[1]}

How do i add an unknown number of variables to a command

I have a script which I'm trying to modify, which adds objects to a firewall via powershell, using Invoke-RestMethod
The current script has the following code;
#Import CSV and set variables
$csv = import-csv C:\Powershell\groups.csv
# RESTful API Call
$csv | ForEach-Object {
$Name = $_.name
$Member1 =$_.member1
$Member2 =$_.member2
Invoke-RestMethod -Uri https://172.16.16.16:4444/webconsole/APIController?reqxml=<Request><Login><Username>admin</Username><Password>password</Password></Login><Set%20operation=%27add%27><IPHostGroup><Name>$Name</Name><IPFamily>IPv4</IPFamily><HostList><Host>$Member1</Host><Host>$Member2</Host></IPHostGroup></Set></Request>
}
I am wanting to import the hostgroups via groups.csv which (in my test) has 3 columns as follows;
Name,Member1,Member2
TestGroup,TestHost1,TestHost2
TestGroup2,TestHost3,TestHost4
etc.
My problem is that in the real data, there are varying amount of hosts in each group, some have hundreds. I'm not sure how to get these into the command without defining a variable for each possible member. Even then, say I created $Member(s) all the way to 200 (be gentle, I'm not a real coder!) and then imported them in manually one by one in the Invoke-Restmethod command (Might as well do it by hand at that point!) I'm not sure the command would handle the blank inputs in the cases where there were only a few hosts in the group.
(In other words if my csv had the following entries;)
Name,Member1,Member2,Member3,Member4
TestGroup,TestHost1,TestHost2,TestHost3,TestHost4
TestGroup2,TestHost5,TestHost6
TestGroup3,TestHost7
And I did;
# RESTful API Call
$csv | ForEach-Object {
$Name = $_.name
$Member1 =$_.member1
$Member2 =$_.member2
$Member3 =$_.member3
$Member4 =$_.member4
The Rest call for the third group would end up running as;
Invoke-RestMethod -Uri https://172.16.16.16:4444/webconsole/APIController?reqxml=<Request><Login><Username>admin</Username><Password>password</Password></Login><Set%20operation=%27add%27><IPHostGroup><Name>TestGroup3</Name><IPFamily>IPv4</IPFamily><HostList><Host>TestHost7</Host><Host></Host><Host></Host><Host></Host></IPHostGroup></Set></Request>
Can anyone point me in the direction of a better way of doing this?
You can get all member names using .PSObject.Properties.Name
Example:
$Csv = Import-Csv -Path 'C:\Powershell\groups.csv'
# Request XML template
$RequestTpl = #'
<Request>
<Login>
<Username>admin</Username>
<Password>password</Password>
</Login>
<Set%20operation=%27add%27>
<IPHostGroup>
<Name>{0}</Name>
<IPFamily>IPv4</IPFamily>
<HostList>
{1}
</HostList>
</IPHostGroup>
</Set>
</Request>
'#
# Host list XML template
$RequestHostListTpl = '<Host>{0}</Host>'
$Csv | ForEach-Object {
<#
Get names of all the properties in the current object
Leave only those that don't match '^Name$' regex.
-match, when operates on collections, returns matched items
You can use
$_.PSObject.Properties.Name | Where-Object {$_ -ne 'Name'}
but it's a bit slower.
#>
$Members = #($_.PSObject.Properties.Name) -notmatch '^Name$'
# Build list of hosts
$RequestHostList = foreach ($item in $Members) {
# Only add item if it's not empty
if ($_.$item) {
$RequestHostListTpl -f $_.$item
}
}
# Build request XML
$Request = $RequestTpl -f $_.Name, -join $RequestHostList
# Remove newlines to make it one long string
$Request = $Request -replace '\r|\n'
# Show resulting url
"Invoke-RestMethod -Uri https://172.16.16.16:4444/webconsole/APIController?reqxml=$Request"
# Uncomment to actually invoke API call
#Invoke-RestMethod -Uri "https://172.16.16.16:4444/webconsole/APIController?reqxml=$Request"
}