How to remove object from array in power shell? - json

I am trying to remove the complete object from the array not a member of the object.I am not able to find the way to remove the object there are so many solutions available to remove the item.
Can someone please suggest a way remove the complete object.
JSON Data: JSON data stored in the file.
{
"data": [
{
"id": "Caption",
"firstname": "Caption",
"lastname": "test",
"email": "test",
"requester": "test",
"password": "test",
"incNumber": "test"
}
]
}
Code : I have written the following code to read the object from the array and store into variables to do the task.Once the task is completed I want to remove the object from the array.
$file = Get-Content '\path\to\file' -Raw | ConvertFrom-Json
$file.data | % {if($_.id -eq 'Caption'){
$1 = $_.id
Write-Host $1
###Here I want to remove the whole object related to the id
}}

I think the answer in the comments is what you are looking for: file.data = $file.data | ? id -ne 'Caption'.
To give a little explanation to this, it uses ? which is actually an alias (alternative name) for the Where-Object cmdlet, which is what you use when you want to filter a collection based on some criteria (very similar to the WHERE statement in SQL if you are familiar).
The above answer is a short version, you might alternatively see this:
$file = Get-Content '\path\to\file' -Raw | ConvertFrom-Json
$Result = $file.data | Where-Object {$_.id -ne 'Caption'}
Which is passing a ScriptBlock { } to the Where-Object cmdlet and using $_ to represent each item in the pipeline, then using -ne as the not equals comparison operator to see if the ID property of that object does not match the string Caption. If that is evaluated as true, then it allows the item to pass through the pipeline, which in the above example means it ends up in the $Result variable. If the statement evaluates as false, then it discards that item in the collection and moves on to the next.

Related

Updating JSON Array of Objects with NULL Value in Powershell When Key is Present

I am new to Powershell and am having trouble doing something that I imagine may be pretty simple, but not having luck so far.
I have an array with two objects in it. It looks like this basically:
[
{
"name":"John",
"age":30,
...
...
},
{
"score":null,
"vehicle":"Camaro",
"engine":"V8",
...
...
}
]
My goal is to update the score value in the second object. I have had luck doing so when the key's value is already present as a String, but am not understanding why I am unable to get this to work when the key is present but the value is null (as shown above).
I have tried using a function which I learned about when doing a search for a previously posted question trying to do something similar:
Set Value of Nested Object Property by Name in PowerShell
The function from that question looks like this:
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".")
if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
else { $object.$p1 = $Value }
}
and can be called like this:
SetValue -object $Obj -key $Key -Value $Value
I am not sure why it matters if the value is NULL or not. It can find the key, but just not doing anything if its value is NULL.
My apologies if this already out there. Powershell is just a little different than anything I have worked with before! Any help is greatly appreciated! :)
The object generated by your JSON string is an array of two objects:
$json = #'
[
{
"name":"John",
"age":30,
},
{
"score":null,
"vehicle":"Camaro",
"engine":"V8"
},
]
'# | ConvertFrom-Json
$json[0] # => Element 0 of the Array
name age
---- ---
John 30
$json[1] # => Element 1 of the Array
score vehicle engine
----- ------- ------
Camaro V8
Updating the Value of the score property considering the JSON will always be the same, meaning, the element 1 of the array will always have that property would be as simple as:
$json[1].score = 'hello' # => Updating the property value
$json | ConvertTo-Json # => Converting the array back to Json
[
{
"name": "John",
"age": 30
},
{
"score": "hello",
"vehicle": "Camaro",
"engine": "V8"
}
]
On the other hand, if the arrangement of the objects wouldn't be always the same, you could loop over all elements of the array searching for that property and, once found, update it. For example:
# For each element of the array
foreach($object in $json) {
# if this object has a property with name `score`
# assign that `NoteProperty` to the variable `$prop`
if($prop = $object.PSObject.Properties.Item('score')) {
# update the Value of the `NoteProperty`
$prop.Value = 'hello'
}
}
$json | ConvertTo-Json # Convert it back

Populate collection of objects from one JSON file to the collection of another one with PowerShell

I have two JSON files and want to transfer collection of objects from one file to another. Suppose, the from.json file contains property which represents collection of clients:
"Clients":
[
{
"Name": "Name1",
"Age": "12"
},
{
"Name": "Name2",
"Age": "14"
}
]
to.json file contains an empty collection, "Objects: []" ,which must be filled with objects from from.json. Each objects in toJson variable must contain additional property - Id, so eventually, my "to.json" file should look like this:
"Objects":
[
{
"Id": "{new-id}",
"Name": "Name1",
"Age": "12"
},
{
"Id": "{new-id}",
"Name": "Name1",
"Age": "12"
}
]
I've converted two files into variables:
$fromJson = (Get-Content -Raw -Path {fromPath}) | ConvertFrom-Json
$toJson = (Get-Content -Raw -Path {toPath}) | ConvertFrom-Json
I know that objects from fromJson to toJson can be transferred in the following manner:
toJson.Objects += fromJson.Clients, but that's not enough in my case. I think that it could be done by iterating through fromJson.Clients array but have no idea how to create an object and add it into toJson.Objects collection.
Here's a more efficient solution, based on:
Use of a calculated property with Select-Object, which allows you to place the new property first in the output objects.
Instead of building the array one by one with += (which is inefficient, because a new array must technically be created behind the scenes in every iteration), the solution below lets PowerShell collect the output objects of the Select-Object call in an array automatically (the [array] type constraint is needed to ensure that an array is created even if only one object happens to be output.)
# Sample input.
$fromJson = ConvertFrom-Json '{"Clients":[{"Name":"Name1","Age":"12"},{"Name":"Name2","Age":"14"}]}'
$toJson = ConvertFrom-Json '{ "Objects": [] }'
[array] $toJson.Objects =
$fromJson.Clients |
Select-Object #{ Name='Id'; Expression = { [string] (New-Guid) } }, *
$toJson | ConvertTo-Json -Depth 3 # append | Set-Content as needed.
Kind of new to the PowerShell, but after a bit of investigation came up with the following solution:
fromJson.Clients | ForEach-Object {
$_ | Add-Member -MemberType NoteProperty -Name 'Id' -Value ([guid]::NewGuid().Guid.ToString())
$toJson += $_
}
...
$toJson | ConvertTo-Json | Out-File {to.json_path}
Frankly, don't know if that is a 'proper' way to do that, but generally it works for that particular case. For now, see no other solution.

Compare-Object in Powershell for 2 objects based on a field within. Objects populated by JSON and XML

Apologies for my lack of powershell knowledge, have been searching far and wide for a solution as i am not much of a programmer.
Background:
I am currently trying to standardise some site settings in Incapsula. To do this i want to maintain a local XML with rules and use some powershell to pull down the existing rules and compare them with what is there to ensure im not doubling up. I am taking this approach of trying to only apply the deltas as:
For most settings incapsula is not smart enough to know it already exists
What can be posted to the API is different varies from what is returned by the API
Examples:
Below is an example of what the API will return on request, this is in a JSON format.
JSON FROM WEBSITE
{
"security": {
"waf": {
"rules": [{
"id": "api.threats.sql_injection",
"exceptions": [{
"values": [{
"urls": [{
"value": "google.com/thisurl",
"pattern": "EQUALS"
}],
"id": "api.rule_exception_type.url",
"name": "URL"
}],
"id": 256354634
}]
}, {
"id": "api.threats.cross_site_scripting",
"action": "api.threats.action.block_request",
"exceptions": [{
"values": [{
"urls": [{
"value": "google.com/anotherurl",
"pattern": "EQUALS"
}],
"id": "api.rule_exception_type.url",
"name": "URL"
}],
"id": 78908790780
}]
}]
}
}
}
And this is the format of the XML with our specific site settings in it
OUR XML RULES
<waf>
<ruleset>
<rule>
<id>api.threats.sql_injection</id>
<exceptions>
<exception>
<type>api.rule_exception_type.url</type>
<url>google.com/thisurl</url>
</exception>
<exception>
<type>api.rule_exception_type.url</type>
<url>google.com/thisanotherurl</url>
</exception>
</exceptions>
</rule>
<rule>
<id>api.threats.cross_site_scripting</id>
<exceptions>
<exception>
<type>api.rule_exception_type.url</type>
<url>google.com/anotherurl</url>
</exception>
<exception>
<type>api.rule_exception_type.url</type>
<url>google.com/anotherurl2</url>
</exception>
</exceptions>
</rule>
</ruleset>
</waf>
I have successfully been able to compare other settings from the site against the XML using the compare-object command, however they had a bit simpler nesting and didn't give me as much trouble. I'm stuck to whether it is a logic problem or a limitation with compare object. An example code is below, it will require the supplied json and xml saved as stack.json/xml in the same directory and should produce the mentioned result :
$existingWaf = Get-Content -Path stack.json | ConvertFrom-Json
[xml]$xmlFile = Get-Content -Path stack.xml
foreach ($rule in $xmlFile)
{
$ruleSet = $rule.waf.ruleset
}
foreach ($siteRule in $ExistingWaf.security.waf.rules)
{
foreach ($xmlRule in $ruleSet)
{
if ($xmlRule.rule.id -eq $siteRule.id)
{
write-output "yes"
$delta = Compare-Object -ReferenceObject #($siteRule.exceptions.values.urls.value | Select-Object) -DifferenceObject #($xmlRule.rule.exceptions.exception.url | Select-Object) -IncludeEqual | where {$xmlRule.rule.id -eq $siteRule.id}
$delta
}
}
}
This is kind of working but not quite what i wanted. I do get a compare between the objects but not for the specific id's, it shows me the results below:
InputObject SideIndicator
----------- -------------
google.com/thisurl ==
google.com/thisanotherurl =>
google.com/anotherurl =>
google.com/anotherurl2 =>
google.com/anotherurl ==
google.com/thisurl =>
google.com/thisanotherurl =>
google.com/anotherurl2 =>
Where as i am more after
InputObject SideIndicator
----------- -------------
google.com/thisurl ==
google.com/thisanotherurl =>
google.com/anotherurl ==
google.com/anotherurl2 =>
Hopefully that makes sense.
Is it possible to only do the compares only on the values where the ids match?
Please let me know if you have any further questions.
Thanks.
The problem was your iteration logic, which mistakenly processed multiple rules from the XML document in a single iteration:
foreach ($xmlRule in $ruleSet) didn't enumerate anything - instead it processed the single <ruleset> element; to enumerate the child <rule> elements, you must use $ruleSet.rule.
$xmlRule.rule.exceptions.exception.url then implicitly iterated over all <rule> children and therefore reported the URLs across all of them, which explains the extra lines in your Compare-Object output.
Here's a streamlined, annotated version of your code:
$existingWaf = Get-Content -LiteralPath stack.json | ConvertFrom-Json
$xmlFile = [xml] (Get-Content -raw -LiteralPath stack.xml )
# No need for a loop; $xmlFile is a single [System.Xml.XmlDocument] instance.
$ruleSet = $xmlFile.waf.ruleset
foreach ($siteRule in $ExistingWaf.security.waf.rules)
{
# !! Note the addition of `.rule`, which ensures that the rules
# !! are enumerated *one by one*.
foreach ($xmlRule in $ruleSet.rule)
{
if ($xmlRule.id -eq $siteRule.id)
{
# !! Note: `$xmlRule` is now a single, rule, therefore:
# `$xmlRule.rule.[...]-> `$xmlRule.[...]`
# Also note that neither #(...) nor Select-Object are needed, and
# the `| where ...` (Where-Object) is not needed.
Compare-Object -ReferenceObject $siteRule.exceptions.values.urls.value `
-DifferenceObject $xmlRule.exceptions.exception.url -IncludeEqual
}
}
}
Additional observations regarding your code:
There is no need to ensure that operands passed to Compare-Object are arrays, so there's no need to wrap them in array sub-expression operator #(...). Compare-Object handles scalar operands fine.
... | Select-Object is a virtual no-op - the input object is passed through[1]
... | Where-Object {$xmlRule.rule.id -eq $siteRule.id} is pointless, because it duplicates the enclosing foreach loop's condition.
Generally speaking, because you're not referencing the pipeline input object at hand via automatic variable $_, your Where-Object filter is static and will either match all input objects (as in your case) or none.
[1] There is a subtle, invisible side effect that typically won't make a difference: Select-Object adds an invisible [psobject] wrapper around the input object, which on rare occasions does cause different behavior later -
see this GitHub issue.

Powershell - Retain Complex objects with ConvertTo-Json

Powershell command-let ConvertTo-Json has the following limitations
1) It returns Enum values as Integers instead of their text
2) It doesn't return the date in a readable format
For point #1 see below, Status and VerificationMethod Properties
PS C:\Windows\system32> Get-Msoldomain | ConvertTo-Json
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": 1,
"VerificationMethod": 1
}
To handle this, I changed my command as below
PS C:\Windows\system32> Get-Msoldomain | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
{
"ExtensionData": "System.Runtime.Serialization.ExtensionDataObject",
"Authentication": "Managed",
"Capabilities": "Email, OfficeCommunicationsOnline",
"IsDefault": "True",
"IsInitial": "True",
"Name": "curtisjmspartnerhotmail.onmicrosoft.com",
"RootDomain": "",
"Status": "Verified",
"VerificationMethod": "DnsRecord"
}
Now you see, that the enums are being returned with their text values above (Status and VerificationMethod) instead of their integer values.
However, There are a few limitations with this approach:
1) ConvertTo-Csv doesn't retain the Arrays or Complex Objects, and
outputs them as their Class Names (Watch the ExtensionData Properties
in both the outputs). In the second output, we tend to lose the data,
and just get the className
System.Runtime.Serialization.ExtensionDataObject as a string
2) Both ConvertTo-Csv and ConvertFrom-Csv are not the script-level
commandlets, but they are command-level commandlets, which means that
we can't use them at the end of the script , but they will have to be
used with the individual commands like I am doing above. WHEREAS,
ConvertTo-Json need not be applied at the commmandLevel, but just
applied once for the script output.
My question is:
1) How do I still use the convertTo-Json, so that all my enum properties are returned with their texts and not integers, and ALSO the Complex Objects or Arrays are not lost? In the approach I have used, the complex objects are getting lost
2) Also, it should be generic enough so that It can be applied at the end of the script, and not at the command level
ConvertTo-Json and ConvertTo-Csv are both forms of serializing objects in some sort of text representation and both are useful in different use cases.
ConvertTo-Csv is perhaps best used for 2-dimensional data that can be expressed in a table such as a spreadsheet. Hence it is awkward to try to convert "complex" objects, i.e. those with properties that contain other structured data, into a simple table. In such cases PowerShell represents such data as the full name of the data type.
ConvertTo-Json is capable of serializing more complicated objects, since the format allows for nested arrays/data structures, e.g. the ExtensionData property in your example. Note that you may need to use the -Depth parameter to ensure that deeply nested data is serialized correctly.
So the problem really comes down to how the ConvertTo-Json cmdlet serializes enums, which can be demonstrated with:
[PS]> (Get-Date).DayOfWeek
Tuesday
[PS]> (Get-Date).DayOfWeek.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True DayOfWeek System.Enum
[PS]> Get-Date | Select DayOfWeek | ConvertTo-Json
{
"DayOfWeek": 2
}
So before you convert to JSON you need to ensure that the DayOfWeek property (in this example) or Status and VerificationMethod properties (from your example) are converted to their string equivalents first.
You can do this using an expression with Select-Object to convert the data as it passes down the pipe. Note that you do need to include all the properties that you want included in the final JSON:
[PS]> Get-Date |
Select DateTime,#{Label="DayOfWeek";Expression={$_.DayOfWeek.ToString()}} |
ConvertTo-Json
{
"DateTime": "13 June 2017 10:33:51",
"DayOfWeek": "Tuesday"
}
So in your case you'd need something like this:
[PS]> Get-Msoldomain |
Select-Object ExtensionData,IsDefault,IsInitial,Name,RootDomain `
,{Label="Authentication";Expression={$_.Authentication.ToString()}} `
,{Label="Capabilities";Expression={$_.Capabilities.ToString()}} `
,{Label="Status";Expression={$_.Status.ToString()}} `
,{Label="VerificationMethod";Expression={$_.VerificationMethod.ToString()}} |
ConvertTo-Json
#puneet, following your comment on my other answer, here is an example of how you might build up a new object, based on an existing one, with the Enum types converted to strings.
The idea is to create a new "empty" object, then loop through all the properties of the original object and add them to the new one, but if any of the original properties are Enums, then those are converted to strings.
$data = [PSCustomObject]#{}
(Get-Date).PSObject.Properties | Select Name,Value | Foreach-Object {
if($_.Value.GetType().BaseType.FullName -eq "System.Enum"){
$data | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value.ToString()
}
else {
$data | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value
}
}
$data | ConvertTo-Json
You may want to finesse this a little for your own application, but hopefully the idea behind it is clear. Definitely check to see that all the properties are being treated correctly in the JSON output.
to keep enum,array and date when converting psObject to json, you can use newtonsoft. a sample here https://github.com/chavers/powershell-newtonsoft using Nerdy Mishka powershell module.
$obj = New-Object pscustomobject -Property #{Enum = (Get-DAte).DayOfWeek; int = 2; string = "du text"; array = #("un", "deux", "trois"); obj= #{enum = (Get-DAte).DayOfWeek; int = 2; string = "du text"; array = #("un", "deux", "trois")}}
Import-Module Fmg-PrettyJson
$settings = Get-NewtonsoftJsonSettings
$enumconv = "Newtonsoft.Json.Converters.StringEnumConverter"
$e = New-Object $enumconv
$settings.Converters.Add($e)
Set-NewtonsoftJsonSettings $settings
$obj | ConvertTo-NewtonsoftJson
return:
{
"array": [
"un",
"deux",
"trois"
],
"enum": "Thursday",
"int": 2,
"obj": {
"enum": "Thursday",
"array": [
"un",
"deux",
"trois"
],
"int": 2,
"string": "du text"
},
"string": "du text"
}

Why does ConvertFrom-Json produce an Object which turns into a PSCustomObject after assignment? And how do I shortcut that?

I have a simple json file as follows:
[
{"ClientName": "Test Site 1", "ClientID": "000001"},
{"ClientName": "Test Site 2", "ClientID": "000002"},
{"ClientName": "Test Site 3", "ClientID": "000003"}
]
When I use the following PowerShell command:
ConvertFrom-Json (Get-Content TestSites.json -Raw)
I get back a System.Object[]. This doesn't allow me to pipe the output to another function I have which accepts "ClientName" and "ClientID" parameters.
However, when I assign that object to another variable, like this:
$myobj = ConvertFrom-Json (Get-Content TestSites.json -Raw)
$myobj is actually a System.Management.Automation.PSCustomObject which is capable of being passed to my function.
How can I just pipe the results of the original command without having to assign it to another variable first?
I hope that makes sense.
Your JSON is an array correct? PowerShell will unroll arrays in the pipeline unless you explicity change that behavior. Assuming your JSON is stored in the variable $json as a single string consider the following examples.
ConvertFrom-Json $json | ForEach-Object{$_.gettype().fullname}
System.Object[]
(convertFrom-Json $json) | ForEach-Object{$_.gettype().fullname}
System.Management.Automation.PSCustomObject
System.Management.Automation.PSCustomObject
System.Management.Automation.PSCustomObject
You should be able to wrap the expression in brackets to change the outcome. In the second example it should be sending the 3 objects individually down the pipe. In the first it is being sent as a single object array.
My explanation needs work but I am sure of the cause is how PowerShell deals with arrays and the pipeline. Unrolling being a common word used to describe it.
So depending on your use case you might just be able to wrap the expression in brackets so it gets processed before the pipe to ForEach in my example.
If you have an array such as System.Object[] you could try piping via foreach:
ConvertFrom-Json (Get-Content TestSites.json -Raw) | %{ $_ | your-function }
If you want to pass the whole array down the pipe as-is, you can try adding a comma (aka a unary comma before the variable:
,$myobj | whatever
You can probably see how the latter works by comparing the following:
$myobj | Get-Member # Shows the type of the elements of the array
Get-Member -InputObject $myobj # Shows the type of the array
,$myobj | Get-Member # Shows the type of the array