Powershell Custom Object toString value is used as a Note Property - powershell

I'm working with the JIRA API with the Issue changelog. The API call returns JSON key/value pairs. One of the keys is toString which is represented in the PS Custom Object as a NoteProperty. (Other keys are "to" "from" "fromString", etc.) When I reference the toString NoteProperty, PS thinks I'm calling the ToString() string method and it gives an OverLoadDefinition error.
$response is the variable I use to store the API call output.
$response.changelog.histories.items.to # this works fine
$response.changelog.histories.items.fromString # this works fine
$response.changelog.histories.items.toString # This fails.
PS thinks I want to call the toString() method.
Is there a way to force PS to use the NoteProperty value stored in the toString key?

Try to put it into single brakets like this:
$response.changelog.histories.items.'toString'

By trying some options I found something that works:
$JiraIssue = "https://jira/rest/api/2/issue/DAA-2662?fields=key,id,reporter&expand=changelog"
$response = Invoke-RestMethod -Method GET -Uri $JiraIssue -Headers #{"Authorization"="Basic $myCreds"} -ContentType application/json
$response.changelog.histories.items.tostring # FAILS
$response.changelog.histories.items[0].tostring # WORKS
foreach ($i in $response.changelog.histories.items ) {$i.tostring } # WORKS
When I reference items.toString it looks like PS is confused between the toString method and the NoteProperty with the same name. But when I reference a single array value within the items array items[0].toString, PS knows that I want the NoteProperty.
Any further comments or insight on this is most welcome!

Related

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()

Variable in Invoke-WebRequest -Uri path

I am trying to pass a MAC Address and code (1,2,3,4) to Invoke-WebRequest.
Manually the command is working fine, but I am not able to do it via command.
The manual command that works is:
Invoke-WebRequest -Uri https://mywebsite.org/pcconfig/setstate.php?mac=F832E3A2503B"&"state=4
Now when I break this up into variable to use with the mac from the machine I do the following.
$LiveMAC = Get-NetAdapter -Physical |
where Status -eq "Up" |
Select-Object -ExpandProperty PermanentAddress
$Str1 = "https://mywebsite.org/pcconfig/setstate.php?mac=$LiveMAC"
$Str2 = $Str1 + '"&"' + 'state=4'
Invoke-WebRequest -Uri $Str2
When I run the above code I do not see errors in red, it seems to process but does not work.
Looking at $Str2 I see the below output, which seems correct, but when passing it as above it fails to work.
https://mywebsite.org/pcconfig/setstate.php?mac=F832E3A2503B"&"state=4
The double quotes in a statement like
Invoke-WebRequest -Uri https://example.org/some/sit.php?foo=x"&"bar=y
mask the ampersand for PowerShell, because otherwise otherwise PowerShell would throw an error that a bare & is reserved for future use. A more canonical way of avoiding this is to put the entire URI in quotes:
Invoke-WebRequest -Uri "https://example.org/some/sit.php?foo=x&bar=y"
Either way the actual URI that is passed to Invoke-WebRequest is
https://example.org/some/sit.php?foo=x&bar=y
without the quotes.
However, in a code snippet like this:
$Str1 = "https://example.org/some/site.php?foo=$foo"
$Str2 = $Str1 + '"&"' + 'state=4'
you're including literal double quotes as part of the URI, so the actual URI passed to the cmdlet would be
https://example.org/some/sit.php?foo=x"&"bar=y
which is invalid.
With that said, you don't need string concatenation for building your URI anyway. Simply put your variable in the double-quoted string:
$uri = "https://example.org/some/sit.php?foo=${foo}&bar=y"
If you need to insert the value of an object property or array element use either a subexpression
$uri = "https://example.org/some/sit.php?foo=$($foo[2])&bar=y"
or the format operator
$uri = 'https://example.org/some/sit.php?foo={0}&bar=y' -f $foo[2]

Script return multiple values

We have a powershell script that download data from an url, verify the data using some generic and simple rules and return success or failed (0 or 1). Here is an example
Get-data -uri "http://www.google.com/some_path"
This script is used successfully in many situations. However, in some cases the simple rules implemented in the Get-data script is not enough to verify the data. We do not want to add a lot of domain specific rules into the Get-data. It would be much better if the parent script performed the additional verification but then it needs access to the raw data. How can we return both a boolean return value of success \ failed and a data object?
How about returning an object instead of a bool:
$props = #{
Success = $result
Data = $theData
}
$object = new-object psobject -Property $props
return $object
You can get the object like so:
$result = Get-data -uri "http://www.google.com/some_path"
if($result.success) {
# Do all the stuff you want with $result.data
}
Read more about creating objects here.

How do I write a PowerShell cmdlet to take either a HashTable or a PODO for input?

I have a powershell module that wraps around some web services. The web services take complex Plain Old Dot Net Objects (PODOs) and I have been using HashTables as in cmdlet parameters and New-Object MyPODO -Property $MyHashTable to transform the hashtable into the request object like so
function Get-Stuff ([HashTable]$WhatStuff) {
$service = New-ServiceProxy . . . .
$request = New-Object GetStuffRequest -Property $WhatStuff;
return $service.GetStuff($request);
$response;
}
However, sometimes I have a cmdlet whose response object can directly become a request object like so:
function Find-Stuff ([HashTable]$KindaStuff) {
$service = New-ServiceProxy . . . .
$request = New-Object GetStuffRequest -Property $KindaStuff;
return $service.SearchStuff($request);
}
Is there some sort of way to decorate the $WhatStuff parameter to accept either a HashTable or a PODO of a particular type?
James Tryand gave me this answer in a tweet.
The answer is to use Parameter Sets.
In one paramater set you accept a parameter of type HashTable, and in the other one, you accept the PODO type.
Maybe like below, depending on how you want to use it:
function Get-Stuff ($WhatStuff) {
if(($WhatStuff -isnot [HashTable]) -or ($WhatStuff -isnot [PODOType])){ throw "expect it to be Hashtable or object of type"}
...
}