0

I am new to PowerShell, learning it on the fly coming from Python background.

I am pulling data from another tool where the data is retrieved through a REST call.

METERS variable is stored in this format in the source.

{
    "500 HR": 500,
    "1000 HR": 1000,
    "2000 HR": 2000
}

PowerShell code

#REST call to source
$meter=@{}
Foreach ($item in $origresult.Items)  {
$result = (Invoke-RestMethod -Uri $url  -Headers $headers -Method GET -ContentType application/json -ErrorVariable RespErr)
$meter.Add($item.Name,$result.Value)
}
Write-Host ($meter | Out-String) -ForegroundColor Red

This is the Output

Name                           Value
----                           -----
LastUploaded                   2020-12-29T06:38:02
IsEnabled                      1       
METERS                         {...
ORGID                          WHS

How do I retrieve METERS and traverse through the dictionary? I tried this so far. Python spoiled me with its simple data structures, PowerShell is not so straightforward unless there is a simpler way to do this.

$mymeters = $meter.METERS | ConvertFrom-Json
Write-Host ($mymeters | Out-String) -ForegroundColor Yellow

Output

500 HR   : 500
1000 HR  : 1000
2000 HR  : 2000

Here are somethings I have tried so far -

$mymeters = [ordered]@{}
" Here is the item $mymeters.Get_Item(500 HR)" #my silly attempt!
# Looping is a no go either - it says specialized ordered dictionary
foreach ($ind in $mymeters) {
" --> $ind"
}

Output

 Here is the item System.Collections.Specialized.OrderedDictionary.Get_Item(500 HR)
 --> System.Collections.Specialized.OrderedDictionary

I might be missing something real basic but I am unable to figure it out on my own! Any help is really appreciated. All I want is to traverse on the METERS hashtable/dictionary and call a function.

2 Answers 2

3

Before getting into the meat of it, let's review a few PowerShell syntax basics and see if we can re-use some of your Pythonic intuition :)

Member access

Just like in Python, you can reference the properties of an object by name using the . member access operator - for non-contiguous names, use quotes:

$mymeters = $meter.METERS | ConvertFrom-Json
$mymeters.'500 HR'  # evaluates to `500`

String expressions

String literals in PowerShell comes in two distinct flavours:

  • Single-quoted strings ('Hello World!')
    • These are verbatim string literals, and the only supported escape sequence is '' (literal single-quote)
  • Double-quoted strings ("Hello World!")
    • These are expandable string literals - automatic interpolation of $variable tokens and $() subexpressions will occur unless explicitly escaped - ` is the escape character, and common sequences you'd expect in most C-like languages (`n, `t, `r, etc.) are natively recognized.

Arbitrary expressions (like $dictionary.get_Item('some key')) will not be evaluated as-is, however.

To get around that, we can either use the -f string format operator:

$mymeters = [ordered]@{}
"Here is item '500 HR': {0}" -f $mymeters['500 HR']

-f should feel familiar if your used to Python3's f strings, but with a caveat - PowerShell's -f operator is a thin wrapper around String.Format(), and String.Format() only supports 0-based placeholders - '{0} {1}' -f 1,2 is valid but '{} {}' -f 1,2 is not.

Another option is to wrap the expression in a $() sub-expression operator inside a double-quoted string literal:

$mymeters = [ordered]@{}
"Here is item '500 HR': $($mymeters['500 HR'])"

Take note that dictionaries in PowerShell supports keyed index access with [] - just like Python :)


Not unlike Python, PowerShell (and .NET in general) has powerful introspection capabilities as well.

Dynamically discovering and iterating the properties of any object is as simple as referencing a special member named psobject:

foreach($propertyMetadataEntry in $someObject.psobject.Properties){
    "Property: {0,-20} = {1}" -f $propertyMetadataEntry.Name,$propertyMetadataEntry.Value
}

Or in your case:

$mymeters = $meter.METERS | ConvertFrom-Json
foreach($meterReading in $mymeters.psobject.Properties){
    "Meter: {0,-20} = {1}" -f $meterReading.Name,$meterReading.Value

    # do whatever else you please with $meterReading here :)
}

This will work for any scalar object (like the object returned by ConvertFrom-Json or Invoke-RestMethod).

For iterating over the entries in a dictionary, you need to explicitly call .GetEnumerator():

$dictionary = [ordered]@{ A = 1; B = 2; C =3 }
foreach($keyValuePair in $dictionary.GetEnumerator()){
    "Dictionary Entry: {0} = {1}" -f $keyValuePair.Key,$keyValuePair.Value
}
Sign up to request clarification or add additional context in comments.

6 Comments

Thank you so much for the basics and the 101, really helped understand the nuances when compared to Python. I still got this when I tried your approach - Meter: Count = 0 Meter: IsReadOnly = False Meter: Keys = System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection Meter: Values = System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection Meter: IsFixedSize = False Meter: SyncRoot = System.Object Meter: IsSynchronized = False
Those are the properties of an ordered dictionary object (like the one produced by [ordered]@{}). If you want the entries in a dictionary, you'll need to obtain an enumerator: foreach($kvp in $dictionary.GetEnumerator()){ $kvp.Key,$kvp.Value -join '='}
$mymeters = $meter.METERS | ConvertFrom-Json foreach($kvp in $mymeters.GetEnumerator()) { " --> I got here <-- " $kvp.Key,$kvp.Value -join '=' } I got a null-valued expression error, You cannot call a method on a null-valued expression.
The other way around - ConvertFrom-Json returns a single [PSCustomObject], against which you'll want to iterate over .psobject.Properties. [ordered]@{}, on the other hand, returns an ordered dictionary - and for dictionaries, you need GetEnumerator()
Ah, that did the trick, thank you so much! I used this foreach($meterReading in $mymeters.psobject.Properties) like you suggested.
|
1

Please check my answer below:

1.) I tested the stuff on powershell 5.x. Newer or older Versions of powershell (particulary older) might work slightly different

2.) Invoke-Restmethod does automatically convert the json response into a powershell object, so no further processing needed. Get rid of all the assignment into an hashtable.

$responseJson = '{
    "500 HR": 500,
    "1000 HR": 1000,
    "2000 HR": 2000
}'

$response = $responseJson | ConvertFrom-Json

$nodes = (Get-Member -inputobject $response  -MemberType NoteProperty).Name

$nodes | ForEach-Object {
    echo ("Element: $_ Result: " +$response.$_)
    
}

echo "Another one"

#alternative
foreach ($node in $nodes) {
    echo ("Element: $node Result: " +$response.$node)
}

3.) I think the response is bad formated, so if you have control over the restservice I recommend something like this:

$responseJson = '{
    "hr": [
     500,
    1000,
    2000
    ]
}'

$response = $responseJson | ConvertFrom-Json

$response.hr | ForEach-Object {
    echo ("Element: $_ Result: " +$response.$_)
    
}

3 Comments

I do have some control over the source, the way METERS has a JSON string can be changed. I need key,value pairs, some edge cases like 12KHR: 12000 and the like. I am able to traverse through the meters with your nodes logic. I got this as output for both options you provided - Ex. Element: 500 HR Result: , can I get key,value output instead? $response.$node or $response.$_ is blank.
Hi, the Key is in $node and the value is in $response.node or what am I missing?
My apologies, variable mess up on my part. This worked too. Thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.