Why does the pipeline not work on converted json objects? - powershell

Consider the following code:
$data = '[
{
"Name": "banana",
"Color": "yellow"
},
{
"Name": "kiwi",
"Color": "green"
},
{
"Name": "apple",
"Color": "red"
}
]'
# Returns 3 objects while only 1 was expected
$data | ConvertFrom-Json | Where-Object { $_.Name -eq 'banana' }
# Workaround, returns 1 object as expected:
($data | ConvertFrom-Json) | Where-Object { $_.Name -eq 'banana' }
Why is it not possible to use the first option? It seems like the Where-Object function does not correctly after an object is converted from json. This is happening on PowerShell version 5.1.
Are we missing something obvious here?

With:
$data | ConvertFrom-Json | Where-Object { $_.Name -eq 'banana' }
The following happens:
ConvertFrom-Json returns an array of objects (which is an object itself). As this is the first (in the end also the only) "finished" object ConvertFrom-Json returns, it is passed as a whole down the pipeline. Remember, a cmdlet could return multiple arrays of objects in general.
So, Where-Object receives only one object in this case (the whole array with three elements). $_ then references the whole array, not each element. Therefore, $_.Name does not return the name of one element, but a list of all element names. Furthermore, the term $_.Name -eq 'banana' in this case is not a boolean expression, but a filtered list of element names (the list then only contains 'banana'). As long as the list is not empty, it will be evaluated to $true by Where-Object and therefore your whole array (one object, with three elements) is piped further (printed in your case). So, it doesn't return three objects like you assumed, but one object, containing three objects.
Your other line in contrast:
($data | ConvertFrom-Json) | Where-Object { $_.Name -eq 'banana' }
Well, to make it short, does what you expect it to do. Why? Because the round brackets break the pipeline. Due to the brackets, everything inside the brackets will be completely evaluated, before something gets piped further. After your brackets are evaluated, there is an array, that will be piped further. A whole array will be piped element by element. So in this case, Where-Object receives three single objects, like you expected it.
Another nice example is this:
You cannot overwrite a file that your are currently reading:
Get-Content test.txt | Set-Content test.txt
But you can overwrite a file after you finished reading it:
(Get-Content test.txt) | Set-Content test.txt

Related

How to get child dynamically with name in power shell

wanted to traverse in json body and get values from list using powershell.
json file
{   
 "TopicPropProfiles": [    
{        
"TP1":{            
"enable-duplicate-detection": true,            
"enable-batched-operations": true,            
"enable-ordering": true      
},        
"TP2":{            
"max-delivery-count": 3,            
"enable-batched-operations": true      
}    
}    
],    
"SubPropProfiles": [    
{        
"SP1":{            
"enable-duplicate-detection": true,                        
"max-size": 1024        
},        
"SP2":{            
"max-delivery-count": 3,            
"enable-batched-operations": true,                       
"enable-session": false                  
}    
}    
],    
"Topics":[    
{        
"TopicName": "topic1",        
"SubNames": ["sub1","sub2","sub3"],        
"TopicPropertyProfile": "TP1",        
"SubPropertyProfile": "SP2" 
},    
{        
"TopicName": "topic2",        
"SubNames": ["sub4","sub5","sub6"],        
"TopicPropertyProfile": "TP2",        
"SubPropertyProfile": "SP1"    
}
]
}
powershell --getting file from somepath($profilepath)
$profilejson = Get-Content -Raw $profilePath | ConvertFrom-Json;
$profileObject = [PSCustomObject]$profilejson;
$TopicProps=$profileObject.TopicPropProfiles.**TP1**;
Write-Host $TopicProps.'enable-duplicate-detection'
Wanted to get fields values under TP1 or TP2(this value will be passed dynamically through some other parameters). Is above syntax/approach correct?
I have reproduced in my environment and I got expected results as below:
$result = Get-Content "Path" -raw | ConvertFrom-Json
$result.TopicPropProfiles.TP1
what if TP1 if also in some var like $profile=TP1 then?
$result = Get-Content "path" -raw | ConvertFrom-Json
$profile='TP1'
$result.TopicPropProfiles[0].$profile
To get the values of specific keys, you can use the dot notation. Example :
$json = Get-Content -Raw .\json.json | ConvertFrom-Json
$json.TopicPropProfiles
To see the content of TP1
$json.TopicPropProfiles.TP1
To get get the value of your first key/value pair inside TP1
$json.TopicPropProfiles.TP1.'enable-batched-operations'
Like this, you'll get the object returned. If you want the values, you need to use Select-Object
$json.TopicPropProfiles.TP1 |Select-Object -ExpandProperty TP1

In PowerShell, How I do I filter for an object by its nested property key/value object?

An object returned from a get-log command can look like
Date: <date>
Properties:
statusCode : OK
serviceRequestId: 97168d7a-4c92-4d65-b509-65785b14ef42
Name: <name>
Details: <details>
I want to do something that returns that the one object by doing something like
get-log | where-object { $_.Properties.serviceRequestId -eq '97168d7a-4c92-4d65-b509-65785b14ef42' }
Of course, this does not work, but I want something that works like this.
My goal is to see the "Details" property.
The filtering sample you provided works as is:
get-log | where-object { $_.Properties.serviceRequestId -eq '97168d7a-4c92-4d65-b509-65785b14ef42' }
That will return the object(s) you want (the full object, not just inner properties).
So you can use the result of that to get at any other property, like Details:
$result = get-log | where-object { $_.Properties.serviceRequestId -eq '97168d7a-4c92-4d65-b509-65785b14ef42' }
$result.Details
Or you can do it all in one line by continuing the pipeline and using Select-Object:
get-log |
where-object {
$_.Properties.serviceRequestId -eq '97168d7a-4c92-4d65-b509-65785b14ef42'
} |
Select-Object -ExpandProperty Details
(did it on multiple lines for better readability)

powershell prevent duplicate object keys

This is a follow up to this question
If I have 2 json files
file1.json
{
"foo": {
"honk": 42
}
}
file2.json
{
"foo": {
"honk": 9000,
"toot": 9000
}
}
And I create an object using ConvertFrom-Json
$bar = #(Get-ChildItem . -Filter *.json -Recurse | Get-Content -Raw |ConvertFrom-Json)
Powershell will happily take both, and overwrite foo.
foo
---
#{honk=42}
#{honk=9000; toot=9000}
The contents of $bar.foo are merged
$bar.foo
honk
----
42
9000
How can I error if importing duplicate objects?
Each JSON file is imported as a separate object, so there's nothing overwritten really. You just get a list of objects.
To throw an error when you get multiple objects with the same top-level property you can group the objects by property name and throw an error if you get a count >1.
$bar | Group-Object { $_.PSObject.Properties.Name } |
Where-Object { $_.Count -gt 1 } |
ForEach-Object { throw "Duplicate object $($_.Name)" }
When importing to an array, every object is unique. In this example it isn't ideal to leave the objects in an array, since there is no way to predictably iterate over them, since some objects might contain multiple keys.
{
"foo": 42
}
vs
{
"bar": 9000,
"buzz": 9000
}
This will cause heartache when trying to loop through all objects.
Instead, I took all array items and combined them into 1 powershell object. Since powershell objects are basically hashes, and hashes by design must have all keys unique, powershell will automatically error if overwriting a key.
function Load-Servers {
$allObjects = #(
Get-ChildItem '.\servers' -Filter *.json -Recurse | Get-Content -Raw | ConvertFrom-Json
)
$object = New-Object PSObject
Foreach ($o in $allObjects) {
$o.psobject.members | ? {$_.Membertype -eq "noteproperty" } | %{$object | add-member $_.Name $_.Value }
}
return $object
}

Powershell using where-object

I am using Powershell and am having trouble with the Where-Object cmdlet. I currently select * and then want to only output when a field is equal to Alabama. This field could be under any column, not just one.
This is what I have:
select * | where {$_.state_name -eq 'Alabama'} .
This works for state_name, but i cant get all columns without doing them individually. I've tried where{$_ -eq....} but that doesn't work.
Kind of a hack, but:
select * | where {($_ | ConvertTo-Csv -NoTypeInformation)[1] -like '*"Alabama"*'}
You have to iterate over all object's properties and check if it contains word 'Alabama'.
Example:
# Import CSV file and feed it to the pipeline
Import-Csv -Path .\My.csv |
# For each object
ForEach-Object {
# Psobject.Properties returns all object properties
# (Psobject.Properties).Value returns only properties' values
# -contains operator checks if array of values contains exact string 'Alabama'
# You can also use -like operator with wildcards, i.e. -like '*labama'
if(($_.PSObject.Properties).Value -contains 'Alabama')
{
# If any of the object properties contain word 'Alabama',
# write it to the pipeline, else do nothing.
$_
}
}

Issues with Powershell Import-CSV

Main Script
$Computers = Get-Content .\computers.txt
If ( test-path .\log.txt ) {
$Log_Successful = Import-CSV .\log.txt | Where-Object {$_.Result -eq "Succesful"}
} ELSE {
Add-Content "Computer Name,Is On,Attempts,Result,Time,Date"
}
$Log_Successful | format-table -autosize
Issues:
Log_Successful."Computer Name" works fine, but if i change 4 to read as the following
$Log_Successful = Import-CSV .\log.txt | Where-Object {$_.Result -eq "Failed"}
Log_Successful."Computer Name" no longer works... Any ideas why?
Dataset
Computer Name,Is On,Attempts,Result,Time,Date
52qkkgw-94210jv,False,1,Failed,9:48 AM,10/28/2012
HELLBOMBS-PC,False,1,Successful,9:48 AM,10/28/2012
52qkkgw-94210dv,False,1,Failed,9:48 AM,10/28/2012
In case of "Successful" a single object is returned. It contains the property "Computer Name". In case of "Failed" an array of two objects is returned. It (the array itself) does not contain the property "Computer Name". In PowerShell v3 in some cases it is possible to use notation $array.SomePropertyOfContainedObject but in PowerShell v2 it is an error always. That is what you probably see.
You should iterate through the array of result objects, e.g. foreach($log in $Log_Successful) {...} and access properties of the $log objects.
And the last tip. In order to ensure that the result of Import-Csv call is always an array (not null or a single object) use the #() operator.
The code after fixes would be:
$logs = #(Import-Csv ... | where ...)
# $logs is an array, e.g. you can use $logs.Count
# process logs
foreach($log in $logs) {
# use $log."Computer Name"
}
I'm not sure if this is the problem but you have a typo, in Where-Object you compare against "Succesful" and the value in the file is "Successful" (missing 's').
Anyway, what's not working?