I had another question that I ended up solving myself where I take a JSON input that has names and IP addresses. Then I resolve those IP addresses by looping through and need to replace the IP address with the resolve FQDN if there is one
I have no idea how to update/replace these values from the original JSON. I’ve read that arrays cannot be changed, only added to. This is where I’m stuck as I can get my script to write-out the resolve FQDN is there was one or the IP if there wasn’t... but I can’t get these values to replace the original value from the JSON with the ultimate goal to then take the newly modified JSON and upload it as a new config
Sample JSON input
{
"entry": [
{
"#name": "31.170.162.203",
"ip-netmask": "31.170.162.203",
"description": "test1"
},
{
"#name": "37.193.217.222",
"ip-netmask": "37.193.217.222",
"description": "test2"
},
{
"#name": "46.17.63.169",
"ip-netmask": "46.17.63.169",
"description": "test3"
}
]
}
$input = Get-Content 'C:\Users\e\Desktop' -raw | ConvertFrom-Json
$iplist = $input.entry.'ip-netmask'
foreach ($ip in $iplist) #for each line in the file...
{
$hostnames = $null
try {
$hostnames = [System.Net.Dns]::GetHostByAddress("$ip").Hostname #...resolve the ip
}
catch [System.Management.Automation.MethodInvocationException] {
$hostnames = "Server IP cannot resolve."
}
catch {
$hostnames = "unknown error."
}
if ($hostnames -ne "Server IP cannot resolve.") {
$ip -replace $ip, $hostnames
} else {
Write-Host $ip
}
}
Your json had an extra comma. I would do it this way. A property with a dash is harder to work with.
$a = cat file.json | convertfrom-json
$a.entry | foreach {
if ($namehost = (resolve-dnsname $_.'ip-netmask').namehost ) { # not null
$_.'ip-netmask' = $namehost
}
}
$a.entry
#name ip-netmask description
----- ---------- -----------
31.170.162.203 31.170.162.203 test1
37.193.217.222 l37-193-217-222.novotelecom.ru test2
46.17.63.169 46.17.63.169 test3
This is sort of like saying:
$namehost = (resolve-dnsname $_.'ip-netmask').namehost
if ($namehost -ne $null) { # ...
# or
if ($namehost) { # ...
but I'm doing the assignment and testing the value of the assignment at the same time, like in C. An assignment can be an expression.
$a = ($b = 1)
Then I'm going through the "entry" array and assigning each 'ip-netmask' property to the results if they aren't null.
The following code snippet could help:
$Json = #'
{
"entry":[
{
"#name":"31.170.162.203",
"ip-netmask":"31.170.162.203",
"description":"test1"
},
{
"#name":"37.193.217.222",
"ip-netmask":"37.193.217.222",
"description":"test2"
},
{
"#name":"46.17.63.169",
"ip-netmask":"46.17.63.169",
"description":"test3"
}
]
}
'# | ConvertFrom-Json
for ( $i = 0; $i -lt $Json.entry.Count; $i++ ) {
$entry = $Json.entry[$i]
$ip = $entry.'ip-netmask'
$hostnames = $null
try {
$hostnames = [System.Net.Dns]::GetHostByAddress("$ip").Hostname #...resolve the ip
$entry.'ip-netmask' = $hostnames
}
catch [System.Management.Automation.MethodInvocationException] {
$hostnames = "Server IP cannot resolve."
}
catch {
$hostnames = "unknown error."
}
Write-Host $entry.'#name', $hostnames -ForegroundColor Cyan
}
### debugging output:
$Json.entry
### final conversion hinted:
### $Json | ConvertTo-Json
###
Note that I use a here-string instead of (probably incorrect)
Get-Content 'C:\Users\e\Desktop' -raw
Related
For context, I am attempting to create a cmdlet that would allow for single value substitutions on arbitrary Json files, for use in some pipelines. I've managed to get this working for non-array-containing Json.
A representative bit of Json:
{"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
I want to be able to replace values by providing a function with a path like test.array[1].name and a value.
After using ConvertFrom-Json on the Json above, I attempt to use the following function (based on this answer) to replace second with third:
function SetValue($object, $key, $value) {
$p1, $p2 = $key.Split(".")
$a = $p1 | Select-String -Pattern '\[(\d{1,3})\]'
if ($a.Matches.Success) {
$index = $a.Matches.Groups[1].Value
$p1 = ($p1 | Select-String -Pattern '(\w*)\[').Matches.Groups[1].Value
if ($p2.length -gt 0) { SetValue -object $object.$p1[$index] -key $p2 -value $value }
else { $object.$p1[$index] = $value }
}
else {
if ($p2.length -gt 0) { SetValue -object $object.$p1 -key $p2 -value $value }
else {
Write-Host $object.$p1
$object.$p1 = $value
}
}
}
$content = SetValue -object $content -key "test.array[1].name" -rep "third"
Unfortunately this results in the following:
{ "test": {
"env": "dev",
"concept": "abstraction",
"array": [
"#{id=1; name=first}",
"#{id=2; name=third}"
]
}
}
If the values in the array aren't objects the code works fine as presented, it's only when we get to objects within arrays that this output happens.
What would be a way to ensure that the returned Json contains an array that is more in line with the input?
Edit: please note that the actual cause of the issue lay in not setting the -Depth property of ConvertTo-Json to 3 or greater. Doing so restored the resulting Json to the expected format. The accepted answer was still helpful in investigating the cause.
While Invoke-Expression (iex) should generally be avoided, there are exceptional cases where it offers the simplest solution.
$fromJson = #'
{
"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
'# | ConvertFrom-Json
$nestedPropertyAccessor = 'test.array[1].name'
$newValue = 'third'
Invoke-Expression "`$fromJson.$nestedPropertyAccessor = `"$newValue`""
Important:
Be sure that you either fully control or implicitly trust the content of the $nestedPropertyAccessor and $newValue variables, to prevent inadvertent or malicious execution of injected commands.
On re-conversion to JSON, be sure to pass a high-enough -Depth argument to ConvertTo-Json; with the sample JSON, at least -Depth 3 is required - see this post.
Have a JSON array with same key values , want to loop through those and get one key of the same value of the array and store the output to an array
{
"contents":[
{
"name":"windows-Instance",
"Buildid":"1234",
"Buildtime":"1563350400238"
},
{
"name":"linux-Instance",
"Buildid":"1454",
"Buildtime":"1563264000198"
},
{
"name":"linux-Instance",
"Buildid":"1278685",
"Buildtime":"1563177600092"
}
]
}
Here is code i tried and doesn't give any output.
$result = #()
foreach ($Builtime in $contents) {
}
return $result
You can do it like this:
First convert the json string to an object:
$contents = '{
"contents":[
{
"name":"windows-Instance",
"Buildid":"1234",
"Buildtime":"1563350400238"
},
{
"name":"linux-Instance",
"Buildid":"1454",
"Buildtime":"1563264000198"
},
{
"name":"linux-Instance",
"Buildid":"1278685",
"Buildtime":"1563177600092"
}
]
}' | ConvertFrom-Json
Than retrieve the required properties:
$result = $contents | Select-Object -ExpandProperty "contents" | Select-Object -ExpandProperty "Buildtime"
You could loop over the JSON and create a custom-object that would allow you to further "manipulate" the data if needed.
Otherwise the example of #mhu is a perfect onliner
$json_content = (Get-Content ".\sample.json") | ConvertFrom-Json
$result = foreach ($content in $json_content."contents") {
[PSCustomObject]#{
"Buildtime" = $content."Buildtime"
}
}
I'm having a little trouble finding the index of a hashtable in an array. I create a JSON with this code:
$start = {
Clear-Host
$BIB = Read-Host 'Bibliothek'
$BIBName = Read-Host 'Bibliothek Name'
$Standort = Read-Host 'Bibliothek Standort'
$Autor = Read-Host 'Buchautor'
$BuchName = Read-Host 'Buchname'
$jsonfile = "C:\Skripte\bibV2-1000.xml"
if(![System.IO.File]::Exists($jsonfile)){
$Data = #{BIBs = #(
#{$BIB = #{BIBName=$BIBName},
#{Standort = $Standort},
#{Bücher = #(
#{BuchName = $BuchName;
Autor = $Autor})
}}
)}
ConvertTo-Json -Depth 50 -InputObject $Data | Add-Content $jsonfile
.$continue
} else {
$jsonfile = "C:\Skripte\bibV2-1000.json"
$Data = Get-Content $jsonfile | ConvertFrom-Json
$Data.BIBs += New-Object -TypeName psobject -Property #{$BIB =
#{BIBname=$BIBName},
#{Standort=$Standort},
#{Bücher = #(#{
Buchname=$BuchName;
Autor=$Autor})
}
}
ConvertTo-Json -Depth 50 -InputObject $Data | Out-File $jsonfile}
.$continue
}
$continue = {
Write-Host ""
Write-Host "Was wollen Sie machen?"
Write-Host "(1) Eine weitere Bibliothek hinzufügen"
Write-Host "(2) Einer Bibliothek neue Bücher hinzufügen"
Write-Host "(E) Script beenden"
If (($read = Read-Host ) -eq "1") {
&$start} else {
if (($read) -eq "2") {
. C:\Skripte\büc.ps1 } else {
if (($read) -eq "E") {
exit} else {
Write-Host "+++ FALSCHE EINGABE! Bitte wählen Sie (1) oder (2) für die entsprechende Aktion +++"
.$continue
}
}
}
}
&$start
The output is as follows:
{
"BIBs": [{
"BIB1": [{
"BIBName": "123"
},
{
"Standort": "123"
},
{
"Bücher": [{
"Autor": "123",
"BuchName": "123"
}]
}
]
},
{
"BIB2": [{
"BIBname": "345"
},
{
"Standort": "345"
},
{
"Bücher": [{
"Autor": "345",
"Buchname": "345"
}]
}
]
}
]
}
Now I want to find out the index of "BIB1". I already tried the IndexOf()-Method which should create the output "0" but it gives me "-1" instead, because it can't find the value. How can I get the index of "BIB1"?
Judging by your earlier question, you're attempting to get the index of a specific object so you can access it via its containing array. However, you can do this more directly: $objOfInterest = $Data.BIBs | ? BIB1 - see my answer to your earlier question for details.
You need to iterate over the array elements of $Data.BIBs, which - on reading your serialized-to-a-file-as-JSON hashtables back in with ConvertFrom-Json - are custom objects (as Ansgar correctly points out; they are instances of [System.Management.Automation.PSCustomObject]), and check each for the presence of property 'BIB1':
(In a hashtable, you'd check for the presence of key 'BIB1' with .ContainsKey('BIB1'))
To test the existence of an object property, you need reflection, which is most easily - but somewhat obscurely - achieved via the hidden .PSObject property, as demonstrated in Ansgar Wiechers' more elegant solution.
However, given that the properties of interest have nonempty values, we can infer from the presence of a nonempty value that a given property exists, using implicit Boolean (logic): $obj.'BIB1' by default returns $null if there is no BIB1 property, which is "falsy" in a Boolean context such as an if conditional; conversely, any nonempty value is "truthy":
$propName = 'BIB1'
$i = $ndx = -1
foreach ($obj in $Data.BIBs) {
++$i
if ($obj.$propName) { $ndx = $i; break}
}
$ndx # $ndx now contains the index of the target object or -1 if there was no match
$Date.BIBs is an array of custom objects, not hashtables (since you wrote your original data to a JSON file and then converted that back), so you need something like this:
$arr = $Data.BIBs | ForEach-Object { $_.PSObject.Properties.Name }
$arr.IndexOf('BIB1') # returns 0
$arr.IndexOf('BIB2') # returns 1
I'm trying to pack my data into objects before displaying them with ConvertTo-Json. The test case below shows perfectly how I'm dealing with data and what problem occurs:
$array = #("a","b","c")
$data = #{"sub" = #{"sub-sub" = $array}}
$output = #{"root" = $data}
ConvertTo-Json -InputObject $data
ConvertTo-Json -InputObject $output
Output (formatted by hand for clarity):
{ "sub": { "sub-sub": [ "a", "b", "c" ] }}
{ "root": { "sub": { "sub-sub": "a b c" } }}
Is there any way to assign $data to $output without this weird implicit casting?
As mentioned in the comments, ConvertTo-Json will try to flatten the object structure beyond a maximum nesting level, or depth, by converting whatever object it finds beyond that depth to a string.
The default depth is 2, but you can specify that it should go deeper with the Depth parameter:
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json
{
"root": {
"level1": {
"level2": "level3-1 level3-2"
}
}
}
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json -Depth 3
{
"root": {
"level1": {
"level2": [
"level3-1",
"level3-2"
]
}
}
}
I'm trying to pack my data into objects before displaying them with ConvertTo-Json. The test case below shows perfectly how I'm dealing with data and what problem occurs:
$array = #("a","b","c")
$data = #{"sub" = #{"sub-sub" = $array}}
$output = #{"root" = $data}
ConvertTo-Json -InputObject $data
ConvertTo-Json -InputObject $output
Output (formatted by hand for clarity):
{ "sub": { "sub-sub": [ "a", "b", "c" ] }}
{ "root": { "sub": { "sub-sub": "a b c" } }}
Is there any way to assign $data to $output without this weird implicit casting?
As mentioned in the comments, ConvertTo-Json will try to flatten the object structure beyond a maximum nesting level, or depth, by converting whatever object it finds beyond that depth to a string.
The default depth is 2, but you can specify that it should go deeper with the Depth parameter:
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json
{
"root": {
"level1": {
"level2": "level3-1 level3-2"
}
}
}
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json -Depth 3
{
"root": {
"level1": {
"level2": [
"level3-1",
"level3-2"
]
}
}
}