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
Related
Working with Graph API and Intune. I create hash table in my script and covert it to JSON which POST to GraphAPI.
But in one case during conversion I lose array. Because of this GraphAPI does not want to accept JSON and returns error.
Case when conversion is correct:
$TargetGroupIDs = #('111', '222')
$AppAssignment = #{
mobileAppAssignments = #{
}
}
$AppAssignment.mobileAppAssignments = foreach ($GroupID in $TargetGroupIDs) {
$Hash = [ordered]#{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
write-output (New-Object -Typename PSObject -Property $hash)
}
$AppAssignment | ConvertTo-Json -Depth 50
Output:
{
"mobileAppAssignments": [
{
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "111",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
},
{
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "222",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
}
]
}
But when I have one element in $TargetGroupIDs conversion is not correct:
$TargetGroupIDs = #('111')
$AppAssignment = #{
mobileAppAssignments = #{
}
}
$AppAssignment.mobileAppAssignments = foreach ($GroupID in $TargetGroupIDs) {
$Hash = [ordered]#{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
write-output (New-Object -Typename PSObject -Property $hash)
}
$AppAssignment | ConvertTo-Json -Depth 50
Output:
{
"mobileAppAssignments": {
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "111",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
}
}
Please note difference in brackets after mobileAppAssignments. In first case [], but in second case {}.
Could someone tell what I am missing in second case?
Theo and Santiago Squarzon have provided the crucial hint in the comments, but let me spell it out:
To ensure that the output from your foreach statement is an array, enclose it in #(), the array-subexpression operator:
$AppAssignment.mobileAppAssignments = #(
foreach ($GroupID in $TargetGroupIDs) {
[pscustomobject] #{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
}
)
Also note that simplified syntax custom-object literal syntax ([pscustomobject] #{ ... }).
#(...) ensures that an array (of type [object[]]) is returned, irrespective of how many objects, if any, the foreach statement outputs.
Without #(...), the data type of the collected output depends on the number of output objects produced by a statement or command:
If there is no output object, the special "AutomationNull" value is returned, which signifies the absence of output; this special value behaves like an empty collection in enumeration contexts, notably the pipeline, and like $null in expression contexts - see this answer for more information; in the context at hand, it would be converted to a null JSON value.
If there is one output object, it is collected as-is, as itself - this is what you saw.
Only with two or more output objects do you get an array (of type [object[]]).
Note: The behavior above follows from the streaming behavior of the PowerShell pipeline / its success output stream: object are emitted one by one, and needing to deal with multiple objects only comes into play if there's a need to collect output objects: see this answer for more information.
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
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 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 create an "switch" statement that will have amount of choices based from the amount of returned objects.
First Example:
[array]$A1 = ("QWERTY", "ASDFGH")
so the "switch" function will look like this:
Switch ($Login = read-Host -Prompt Login) {
1 { $Login = "QWERTY" }
2 { $Login = "ASDFGH" }
}
Second Example:
[array]$A2 = ("A", "B", "C", "D")
so the "switch" function will look like this:
Switch ($Login = read-Host -Prompt Login) {
1 { $Login = "A" }
2 { $Login = "B" }
3 { $Login = "C" }
4 { $Login = "D" }
}
So i was thinking about some foreach loop to create simple string and then run command from string. I mange to do this, but I'm unable to run my generated string as command:
[array]$AR1 = ("QWERTY", "ASDFGH")
$q1 = "switch (Read-Host -Prompt Login) {"
$q2 = " }"
$Number = 0
$Script:ArrayFull = $null
$AR1 | % {
$_
$ArrayElement = $null
$NameOfTheLoginVariable = '$Script:User_Login'
$NumberOfArrayElements = $AR1.count
if ($NumberOfArrayElements -ne 0) {
$Number = $Number+1
$NumberOfArrayElements = $NumberOfArrayElements-1
$ArrayElement = "$Number { $NameOfTheLoginVariable = '$_' }"
$ArrayElement
$Script:ArrayFull += $ArrayElement
}
}
$SwitchCommand = $q1+$ArrayFull+$q2
$SwitchCommand = $SwitchCommand.ToString()
$Test1 = "switch (Read-Host) {1 { $Script:User_Login = 'QWERTY' }1 { $Script:User_Login = 'ASDFGH' } }"
#switch (Read-Host -Prompt Login) {1 { $Script:User_Login = 'QWERTY' }1 { $Script:User_Login = 'ASDFGH' } }
#& $Test1
& $SwitchCommand
But even if this code produce correct "Switch" statement as string, i can't execute it. Why ?
Anyway, this method is really ugly so maybe there is a better one ?
That switch statement is composed of script blocks. You can't just substitute a string for them.
You can use [scriptblock]::create() to create new script blocks from the string, but in this application I think I'd just work up an ordered hash table of script blocks. Then you have keys to present in the manu that will map to the script block that needs to run, and an array index to match to the number of choices you need to present.
While it is possible that with full context of the problem domain #Shay's solution may be necessary, the stated examples are simply asking for an array lookup so the same result may be achieved with this:
[array]$a = "A", "B", "C", "D"
[int]$login = read-host -prompt login
$selected = & {if ($login -ge 0 -and $login -lt $a.Length) { $a[$login-1] }}
So here again if you enter 2 you will get "B". Just as in the stated examples and in Shay's solution, if you enter a value out of bounds you do not get a returned value.
Give this a try, entera number between 1-4 whyen prompted:
[array]$a = "A", "B", "C", "D"
$login = read-host login
$switch = 'switch($login){'
for($i=1;$i -le $a.length; $i++)
{
$switch += "`n`t$i { '$($a[$i-1])'; break }"
}
$switch += "`n}"
Invoke-Expression $switch