How would you break down the following json variable into individual items in array?
[
{
"server":{
"name":"myUbuntuServer1",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
},
{
"server":{
"name":"myUbuntuServer2",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
},
{
"server":{
"name":"myUbuntuServer3",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
}
]
For instance, the above would translate to an array, with the following items:
Array-item 0
{
"server":{
"name":"myUbuntuServer1",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
}
Array-item 1
{
"server":{
"name":"myUbuntuServer2",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
}
Array-item 2
{
"server":{
"name":"myUbuntuServer3",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
}
I would like to accomplish this in Powershell 2.0 and access each one individually. So far this is what I've managed to accomplish:
$jsonarr = #()
$arr = (Get-Content C:\json.json| Out-String).replace("[","") -split "(})," -replace "]",""
$jsonarr += $arr[0..1] -join ""
$jsonarr += $arr[2..3] -join ""
$jsonarr += $arr[4]
However this is extremely inflexible, and will cease to work the minute I had another server's detail to the JSON file.
for PowerShell v2 you can use Convert between PowerShell and JSON
PS
PowerShell v3+, should be the same using the tool above:
$json = '[
{
"server":{
"name":"myUbuntuServer1",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
},
{
"server":{
"name":"myUbuntuServer2",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
},
{
"server":{
"name":"myUbuntuServer3",
"imageRef":"3afe97b2-26dc-49c5-a2cc-a2fc8d80c001",
"flavorRef":"6"
}
}
]'
$servers = ConvertFrom-Json $json
$servers.server.imageRef
returns
3afe97b2-26dc-49c5-a2cc-a2fc8d80c001
3afe97b2-26dc-49c5-a2cc-a2fc8d80c001
3afe97b2-26dc-49c5-a2cc-a2fc8d80c001
Also, don't forget "Get-Member"
PPS
PS C:\Users\joshua\Desktop> $servers.server| where name -EQ myUbuntuServer2
name imageRef flavorRef
---- -------- ---------
myUbuntuServer2 3afe97b2-26dc-49c5-a2cc-a2fc8d80c001 6
PS C:\Users\joshua\Desktop> $servers.server| where name -EQ myUbuntuServer2 | select -Property flavorRef
flavorRef
---------
6
PPPS
also ofcourse
$servers.server[0]
you should be able to index by name also but I'm making some silly error atm
Related
I have multiple arrays like this:
$arrayList1 = #()
$arrayList2 = #()
$arrayList1 += "james"
$arrayList1 += "henry"
$arrayList1 += "bob"
$arrayList2 += "scott"
$arrayList2 += "john"
$arrayList2 += "meera"
$arrayList2 += "lisa"
$arrayList2 += "joseph"
And i want to display the output like this:
arrayList1
arrayList2
james
scott
henry
john
bob
meera
lisa
joseph
Here is what i tried:
$output = #{}
$output.add('arrayList1',$arrayList)
$output.add('arrayList2',$arrayLis2)
[PSCustomObject]$output
$output
And the output looks like this:
arrayList2
arrayList1
{scott,john,meera,lisa,joseph}
{james,henry,bob}
Note: The array won't have same number of data.
Any suggestions how i can get it in the order i want it?
Thanks
Sanjeev
A concise solution (note: assumes that at least one of the arrays is non-empty):
$arrayList1 = 'james', 'henry', 'bob'
$arrayList2 = 'scott', 'john', 'meera', 'lisa', 'joseph'
foreach ($i in 0..([Math]::Max($arrayList1.Count, $arrayList2.Count)-1)) {
[pscustomobject] #{ arrayList1 = $arrayList1[$i]; arrayList2 = $arrayList2[$i] }
}
The above yields the following, as desired:
arrayList1 arrayList2
---------- ----------
james scott
henry john
bob meera
lisa
joseph
As an aside: The - ultimately rejected - proposal in GitHub issue #14732 suggested enhancing the foreach statement in a way that would have made parallel enumeration more convenient, along the lines of
foreach ($elemFromList1, $elemFromList2 in $arrayList1, $arrayList2) { ... }
Try something like this:
$Boys = "Bob", "Noah", "Liam"
$Girls = "Olivia", "Sophia", "Charlotte", "Emma"
Function Fix-Arrays {
Param(
[Parameter(Mandatory=$True)]
[string[]]$ArrayNames,
[switch]$NoWarnings = $False
)
$ValidArrays,$ItemCounts = #(),#()
$VariableLookup = #{}
ForEach ($Array in $ArrayNames) {
Try {
$VariableData = Get-Variable -Name $Array -ErrorAction Stop
$VariableLookup[$Array] = $VariableData.Value
$ValidArrays += $Array
$ItemCounts += ($VariableData.Value | Measure).Count
}
Catch {
If (!$NoWarnings) {Write-Warning -Message "No variable found for [$Array]"}
}
}
$MaxItemCount = ($ItemCounts | Measure -Maximum).Maximum
$FinalArray = #()
For ($Inc = 0; $Inc -lt $MaxItemCount; $Inc++) {
$FinalObj = New-Object PsObject
ForEach ($Item in $ValidArrays) {
$FinalObj | Add-Member -MemberType NoteProperty -Name $Item -Value $VariableLookup[$Item][$Inc]
}
$FinalArray += $FinalObj
}
$FinalArray
}
Fix-Arrays -ArrayNames "Boys","Girls"
Just edit Fix-Arrays (line 41) and the top two example arrays to suit your needs.
I have this kind of an object:
How can I extract each quota and do calculation with used?
I tried this
foreach($quota in $LKPQuotas.quotas) {
foreach($zone in $quota) {
$totalUsed += $zone.used
}
}
But it wont work for me. $LKPQuotas equals to $quota and $zone, and I do not understand why.
I can do calculation manually with code below, but I believe, that there is the other way.
foreach($zone in $LKPQuotas.quotas.volume_gigabytes_basic) {
$volume_gigabytes_basic += $zone.used
}
Please, help me with that. Thank you and stay safe, pandemic is really hard.
foreach() doesn't magically enumerate all properties on an object.
You'll need to do something like:
# Create hashtable that will hold aggregates for each volume
$quotaStats = #{}
# loop through each volume
foreach($property in $LKPQuotas.quotas.psobject.Properties){
$VolumeName = $property.Name
# Create new object object to hold the results for this volume
$quotaStats[$VolumeName] = [pscustomobject]#{
VolumeName = $VolumeName
TotalUsed = 0
TotalAllowed = 0
}
# Collect and aggregate stats for each zone
foreach($zone in $property.Value){
$quotaStats[$VolumeName].TotalUser += $zone.used
$quotaStats[$VolumeName].TotalAllowed += $zone.value
}
}
After which $quotaStats will contain exactly one entry per volume, with the totals for every zone
Creating test data:
$val = {Get-Random -Minimum 1000 -Maximum 2000}
$used = {Get-Random -Minimum 100 -Maximum 900}
$LKPQuotas = [PSCustomObject]#{
quotas = [PSCustomObject]#{
volume_gigabytes_basic = #(
[PSCustomObject]#{value=&$val; used=& $used},
[PSCustomObject]#{value=&$val; used=& $used},
[PSCustomObject]#{value=&$val; used=& $used}
)
volume_gigabytes_fast = #(
[PSCustomObject]#{value=& $val; used=& $used},
[PSCustomObject]#{value=& $val; used=& $used},
[PSCustomObject]#{value=& $val; used=& $used}
)
images_gigabytes = #(
[PSCustomObject]#{used=& $used},
[PSCustomObject]#{used=& $used},
[PSCustomObject]#{used=& $used}
)
compute_cores = #(
[PSCustomObject]#{sockets=1; cores=8},
[PSCustomObject]#{sockets=4; cores=4}
)
}
}
$LKPQuotas | ConvertTo-Json
Output of test data:
{
"quotas": {
"volume_gigabytes_basic": [
"#{value=1225; used=123}",
"#{value=1746; used=274}",
"#{value=1747; used=409}"
],
"volume_gigabytes_fast": [
"#{value=1252; used=404}",
"#{value=1325; used=438}",
"#{value=1048; used=820}"
],
"images_gigabytes": [
"#{used=613}",
"#{used=690}",
"#{used=278}"
],
"compute_cores": [
"#{sockets=1; cores=8}",
"#{sockets=4; cores=4}"
]
}
}
Processing test data and getting results:
$valprops = #{N='value'; E={[uint32]$_.value}},
#{N='used'; E={[uint32]$_.used}}
$LKPQuotas.quotas.psobject.Properties |
ForEach-Object -Process {$prop = $_.Name
$psobj = [ordered]#{Name=$prop}
$LKPQuotas.quotas.$prop |
Select-Object -Property $valprops |
Measure-Object -Property value,used -Sum |
ForEach-Object -Process {$psobj[$_.Property]=$_.Sum}
[PSCustomObject]$psobj}
Output of results:
Name value used
---- ----- ----
volume_gigabytes_basic 4718 806
volume_gigabytes_fast 3625 1662
images_gigabytes 0 1581
compute_cores 0 0
I am working on PowerShell script that will check server services. I keep getting a "Cannot index into a null array."
The error references the second if statement "if ($select_string_result.Line[$select_string_result.Line.Length-1] -eq '1')"
The object type of "$select_string_result" is displayed as Array and the txt file has data but the script will not process through it.
The "Line" property of the array records as null and the "Length" is recorded as 0.
$filepathserver = 'Path'
$filepathlocal = 'Path'
function Get-Timestamp
{
return Get-Date -Format "MM/dd/yyyy hh:mm:ss tt"
}
function refresh-data
{
# Pulls Services and Services Status
$orionData = Get-SwisData $swis "SELECT ComponentID, StatusDescription FROM Orion.APM.Component"
# Sends output to a txt file
$orionData | Out-File "$filepathlocal\All_App_Services.txt"
}
function check-status($select_string_result)
{
if ($select_string_result.Line -isnot [system.array])
{
if ($select_string_result.Line[$select_string_result.Line.Length-1] -eq '1')
{
return 100
}
else
{
return 0
}
}
else
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match.Line[$match.Line.Length-1] -eq '1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
$sum = [int][Math]::Ceiling($sum)
return $sum
}
}
function main
{
refresh-data
# Filters for Application specific Services
$f = #("94944 ", "94945 ", "94951 ", "94946 ", "94942 ", "94948 ", "94949 ", "94950 ", "94943 ", "94947 ", "94952 ", "94953 ")
$AppServices = Get-Content "Path" | Select-String $f
$AppServices | Set-Content "Path"
#Removes leading spaces from array
(Get-Content "$filepathlocal\File.txt") -replace "Up","1" | % {$_.trim()} | Out-File "$filepathlocal\File.txt"
$AppServices = Get-Content "$filepathlocal\File.txt"
$AppServices.GetType()
# Writes status of each group to .txt file
$logfile= "$filepathserver\ServicesStatus.txt"
$t = Get-Timestamp
$v = check-status $AppServices
$s = "$t|Application-Services|$v"
$s | Out-File $logfile -Append -Encoding "UTF8"
$s
}
main
$select_string_result.Line resolves to $null because the array of strings that you get from Get-Content does not have a Line property, so the if statement should look more like:
if($select_string_result[$select_string_result.Length - 1] -eq '1') { ... }
PowerShell also allows you to address the last index with just -1, allowing us to simplify the statement as:
if($select_string_result[-1] -eq '1') { ... }
That being said, rather than attempting to check whether the parameter passed to a function is an array or not, you'd want to declare the parameter an array in the first place and then use a foreach loop over it:
function check-status([string[]]$select_string_result)
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match[-1] -eq '1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
$sum = [int][Math]::Ceiling($sum)
return $sum
}
much nice, way less code.
Now, instead of attempting to index into the string, I'd suggest using the -like wildcard operator or the -match regex operator to check whether each string ends with 1:
if ($match -like '*1')
{
$sum += $add
}
Since $sum is always exactly 100, or otherwise gets reset to 0, the call to [Math]::Ceiling() is redundant and can be removed:
function check-status([string[]]$select_string_result)
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match -like '*1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
return $sum
}
If you look carefully at the function as implemented, you'll notice that the only case in which 100 is returned is when all strings in $select_string_result end in 1.
We can easily test for this by using the -like operator directly on our input array, it will act as a filter operator:
function check-status([string[]]$select_string_result)
{
if(#($select_string_result -like '*1').Count -eq $select_string_result.Count)
{
$sum = 100
}
else
{
$sum = 0
}
return $sum
}
Now, another way of asserting that all strings in the array end in 1, is to simply test whether no string does not end in 1:
function check-status([string[]]$select_string_result)
{
if(#($select_string_result -notlike '*1'))
{
$sum = 0
}
else
{
$sum = 100
}
return $sum
}
Now all we need to do is shine it up a bit, like change check to a more appropriate verb and we've got a much nicer, short or powershell-idiomatic function :-)
function Measure-StatusValue
{
param(
[ValidateNotNullOrEmpty()]
[string[]]$StatusStrings
)
if(#($StatusStrings -notlike '*1'))
{
return 0
}
return 100
}
I'm tinkering with the code below. It should create a PoshRSJob and run the function foo again in the runspace there.
I want to be able to turn the $list parameter into an [array] or [string[]], but when I do it throws errors. I considered flattening my array into a string, but if I change $list3 to include a space or comma in the string it also throws an error. I believe it is this line that is causing the issue, but I don't know why or what to do to circumvent this issue:
ScriptBlock = [scriptblock]::Create("`$_ | $($PSCmdlet.MyInvocation.MyCommand.Name) -Parallel:`$false -fn:$fn -sqlQuery:$SQLQuery -option:$option -List:$List")
Code:
function foo {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("ComputerName")]
[PSObject] $InputObject,
[switch] $Parallel = $true,
[string] $fn,
[string] $sqlQuery = "none",
[string] $option = "none",
[int] $number,
[int] $Throttle = 100,
#Want this to be [array] or [string[]] :
[string] $List = "none"
)
begin {
$batch = [System.Guid]::NewGuid().Guid #run all jobs under same batch number
}
process {
if ($Global:debugging -eq $true){$host.ui.WriteDebugLine("fn:$fn | SQLQuery:$sqlQuery")}
if (!$Parallel) {
$server = $InputObject.name
switch($fn){
Manage{ return $list }
} #end switch
} else {#region Parallel run
$jobArguments = #{
Throttle = $Throttle
Batch = $batch
FunctionsToLoad = $PSCmdlet.MyInvocation.MyCommand.Name
#This is the problematic line:
ScriptBlock = [scriptblock]::Create("`$_ | $($PSCmdlet.MyInvocation.MyCommand.Name) -Parallel:`$false -fn:$fn -sqlQuery:$SQLQuery -option:$option -List:$List")
}
if ($_ -and $_ -isnot [string]) { $serverName=$_ } else { $serverName=$InputObject.name }
#(if ($_ -and $_ -isnot [string]) { $_ } else { $InputObject }) | Start-RSJob #jobArguments | Out-Null
} #endregion
}
end {#region Wait for results and return them
if ($Parallel) {
Get-RSJob -batch $batch | Wait-RSJob -ShowProgress | Out-Null
}#endregion
}
}
$obj = New-Object -TypeName PSObject -Property #{
Name = 'server1'
Other = 'other'
}
$list2 = $obj
$list3 = "item1-item2"
$list2 | foo -fn 'Manage' -number 2 -option Q -List $list3
This is the error:
The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
Does anyone know how to get this working so I can pass a list into the runspace?
I'm trying to figure out how to simplify this process, but it's not as simple as I thought.
I have a config file that looks similar to this:
[string][1][options]
$List = #(
"c:\path\to\file,1,-a,-b,-c,-d,-e"
)
The only items required are the [string] and the [1]. There are 10 options (-a, -b etc), potentially more.
Each of which is optional and could be supplied in any order.
In the main script I then do the following at present:
foreach ($a in $List) {
$dataSplit = $a -split"(,)"
$string = $dataSplit[0]
$number = $dataSplit[2]
$ds4 = $dataSplit[4]
if(!$ds4) {
$ds4 = "0"
} elseif($ds4.StartsWith("-a")) {
$a_set = 1
write-host "a_set has been set to $a_set"
} elseif($ds4.StartsWith("-b")) {
$b_set = 1
write-host "b_set has been set to $b_set"
}
. . .
if(!$ds5) {
$ds5 = "0"
}
. . .
As you can imagine this gets quite long. So I thought I would simplify it with a function. e.g.
function get-additional($item) {
if($item.StartsWith("-a")) {
$a_set = 1
Write-Host "$a_set has been set"
return $a_set
}
if($item.StartsWith("-b")) {
$b_set = 1
Write-Host "$b_set has been set"
return $b_set
}
}
And then call it thus:
if(!$ds4) {
$ds4 = "0"
} else {
get-additional($ds4)
}
Is there a way to do this? I've seen pleanty of examples if you only have a single variable to return, or even a fixed number, but none that allow for the return of 'one of many' variables.
Here is the (shortened) script in one if it helps:
$List = #(
"c:\path\to\file,1,-b,-c,-d,-e"
)
function get-additional($item) {
if($item.StartsWith("-a")) {
$a_set = 1
Write-Host "a_set has been set to $a_set"
return $a_set
}
if($item.StartsWith("-b")) {
$b_set = 1
Write-Host "b_set has been set to $b_set"
return $b_set
}
}
$a_set = 0
$b_set = 0
$c_set = 0
foreach ($a in $List) {
$dataSplit = $a -split"(,)"
$string = $dataSplit[0]
$number = $dataSplit[2]
$ds4 = $dataSplit[4]
Write-Host "ds4 = $ds4"
if(!$ds4) {
$ds4 = "0"
} else {
get-additional($ds4)
}
$ds5 = $dataSplit[6]
Write-Host "ds5 = $ds5"
if(!$ds5) {
$ds5 = "0"
} else {
get-additional($ds5)
}
}
Write-Host "a = $a_set"
Write-Host "b = $b_set"
The desired result at the end would be
a = 0
b = 1
- - - UPDATE 2015-11-30 16:54
In case it helps to understand what I am going for here's a Sample from my actual script
$cfg_AppList = #(
"C:\Path\to\application1\app1.exe instance1,1"
"C:\Path\to\application2\app2.exe instance2,1,-p12345"
"C:\Path\to\application3\app3.exe instance3,0"
"C:\Path\to\application3\app3.exe instance3,1,-p78901"
)
function get-additional($item)
{
$script:pval = "0"
if($item.StartsWith("-p"))
{
$script:pval = $ds4.substring(2)
write-host "$pval is a pval"
}
}
$AppObject = #()
foreach($a in $cfg_AppList)
{
$dataSplit = $a -split","
$AppVal = $dataSplit[0]
$checkVal = $dataSplit[1]
$ds4 = $dataSplit[2]
if(!$ds4)
{
$ds4 = "0"
}
else
{
get-additional($ds4)
}
$AppObject += New-Object PSObject -property #{
AppVal = "$AppVal";
checkVal = "$checkVal";
pval = "$pval";
}
}
The $AppObject object is then referenced and updated as the script progresses.
The values supplied in pval and (see below eval) will determine what happens.
I now need to add a second element -e which will be included thus:
$cfg_AppList = #(
"C:\Path\to\application1\app1.exe instance1,1"
"C:\Path\to\application2\app2.exe instance2,1,-p12345"
"C:\Path\to\application3\app3.exe instance3,0,-e"
"C:\Path\to\application3\app3.exe instance3,1,-e,-p78901"
)
It will be either selected 1 or not selected 0, and added to the $AppObject Array as eval=$eval (1|0).
Going forward I have more options I plan to introduce, hence the need to find the most efficient way to handle them all.
- - - UPDATE 2015-12-01 11:39
OK, What I have gone with is a combination of both ideas below.
Placing the options into an array and looping through them, then using a SWITCH statement to see which ones are set.
$AppObject = #()
foreach($a in $cfg_AppList)
{
$pval = 0
$eval = 0
$AppVal,$CheckVal,$options = $a -split","
foreach($opt in $options)
{
switch -wildcard ($opt)
{
'-p*' { $pval = $opt.substring(2) }
'-e' { $eval = 1 }
}
}
$AppObject += New-Object PSObject -property #{
AppVal = "$AppVal";
CheckVal = "$CheckVal";
pval = "$pval";
eval = "$eval";
}
}
First off, don't capture the , in your split operation if you're not planning to use it for anything, just use -split "," (no parentheses).
We can make use of multiple variable assignment to "shift" away to string and number 1:
$s,$n,$opts = "string,1,-a,-b,-c" -split ","
$opts will now contain the string array: #("-a","-b","-c")
The easiest way to check for whether a predetermined set of options is present or not, is to simply loop through all possible options and see if they are contained in the input string:
function Parse-InputString
{
param($InputString)
# prepare the options you want to check for
$PossibleOptions = "abcde".ToCharArray()
# Split the input string
$String,$Number,$Options = $InputString -split ","
# Create a new object with the string and number values
$OutputObject = New-Object psobject -Property #{
"String" = $String
"Number" = $Number
}
# Now inspect the $Options array to see if any of them are set
foreach($PossibleOption in $PossibleOptions){
$OptionSet = if($Options -contains "-$PossibleOption"){
1
} else {
0
}
# Add the information to the object
$OutputObject |Add-Member -MemberType NoteProperty -Name $PossibleOption -Value $OptionSet
}
# return the object carrying all the information
return $OutputObject
}
Now you can have your input string parsed nicely into an actual object:
PS C:\> Parse-InputString -InputString "c:\path\to\file,1,-b,-c,-d,-e"
Number : 1
String : c:\path\to\file
a : 0
b : 1
c : 1
d : 1
e : 1
The easiest way would be to update the global variables in your function without returning anything:
function Get-Additional($item) {
if ($item.StartsWith("-a")) {
$global:a_set = 1
Write-Host "a_set has been set to $a_set"
}
if ($item.StartsWith("-b")) {
$global:b_set = 1
Write-Host "b_set has been set to $b_set"
}
}
However, modifying global variables in functions is not a good practice, because it's difficult to debug. I wouldn't recommend going this route.
A better approach is to pass your current values as parameters into the function, return the modified values, and assign them back to variables.
function Get-Additional($item, $a, $b) {
if ($item.StartsWith("-a")) {
$a = 1
Write-Host "a_set has been set to $a_set"
}
if ($item.StartsWith("-b")) {
$b = 1
Write-Host "b_set has been set to $b_set"
}
#($a, $b)
}
$set_a, $set_b = Get-Additional $ds4 $set_a $set_b
In the above sample the function returns a list of the modified values (#($a, $b)), which are then assigned back to the list $set_a, $set_b. The return keyword is not required for returning something from a PowerShell function. It controls only where to return from a function, not what to return.
With that said, for your scenario I wouldn't use a function in the first place. A switch statement would be better suited for this kind of manipulation:
switch -wildcard ($ds4) {
'-a*' { $set_a = 1 }
'-b*' { $set_b = 1 }
}