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
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 need help optimizing my PowerShell script.
$sorted = #()
$firsttime = 0
$j = 0
$zaehler = $results.Count-1
for ($i=0; $i -le $results.Count-1; $i++) {
$j = $i+1
while ($results.GUID[$i] -eq $results.GUID[$j]) {
$klassen = ""
$rec = $results | where {$_.GUID -eq $results.GUID[$i]}
if ($firsttime -eq 0 -or !$sorted.GUID.contains($rec[0].GUID)) {
$firsttime = 1
foreach ($item in $rec.Klasse) {
if ($klassen -eq "") {
$klassen += $item
} else {
if (!$klassen.Contains($item)) {
$klassen += "," + $item
}
}
}
$rec[0].Klasse = $klassen
$sorted += $rec[0]
}
$j = $j+1
}
Write-Host ($i/$zaehler).ToString("P") "von Schule" $schule
}
if (!$sorted) {
$results
} else {
$sorted
}
Basically in my resultset ($results) I got duplicate lines of teachers and the only difference is the class ("Klasse/Klassen") they are teaching at.
To minimize the output I am checking if the first GUID is the same as the second and then the script appends the second class to the first one. So the $sorted array has just one line per teacher with a comma-seperated string which shows all classes.
Sample line of $results:
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9A;
Schule=123456
}
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9B;
Schule=123456
}
Sample line of $sorted[0]:
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9A,9B,9C,5A;
Schule=123456
}
The sorting process (check if contains, foreach $item, add to $klassen) is pretty slow.
I would be very grateful for any kind of ideas how to optimize the script.
Maybe something like this would work:
$results | ForEach-Object {
New-Object -Type PSObject -Property $_
} | Group-Object GUID | ForEach-Object {
$teacher = $_.Group
$teacher | Select-Object -First 1 -Exclude Klasse -Property *, #{n='Klasse';e={
$teacher | Select-Object -Expand Klasse
}}
}
Convert your hashtables into custom objects, group them by GUID, then replace the original "Klasse" property with a new one containing an array of the values from all objects in that group, and select the first result.
Should $results already be a list of objects you can skip the first ForEach-Object:
$results | Group-Object GUID | ForEach-Object {
$teacher = $_.Group
$teacher | Select-Object -First 1 -Exclude Klasse -Property *, #{n='Klasse';e={
$teacher | Select-Object -Expand Klasse
}}
}
Is it possible to reverse the properties of [Pscustomobject] ?
I have to setup resources in queue order. After testing is over , i have to teardown the resources in reverse order.
below is the sample code.
$volume= #{Name='Vol1';size = "100gb"}
$VolumeCollection = #{Name = 'VolColl'; Volume = $volume}
$ResourceQueue = [pscustomobject]#{
Volume = $Volume
VolumeCollection = $VolumeCollection
}
function SEtup-Resources
{
param
(
[psobject]$resource
)
$resource.PSObject.Properties | foreach-object {
switch ($_.name) {
"volume" {
"Volume is created"
}
"VolumeCollection" {
"volcoll is created"
}
}
}
}
function TearDown-Resources
{
param
(
[psobject]$resource
)
# I have to reverse the object properties
$resource.PSObject.Properties | foreach-object {
switch ($_.name) {
"volume" {
"Volume is deleted"
}
"VolumeCollection" {
"volcoll is deleted"
}
}
}
}
Write-host "-------------------------"
Write-host "Setup resources"
Write-host "-------------------------"
SEtup-Resources -resource $ResourceQueue
Write-host "-------------------------"
Write-host "teardown resources"
Write-host "-------------------------"
TearDown-Resources -resource $ResourceQueue
The result should be
-------------------------
Setup resources
-------------------------
Volume is created
volcoll is created
-------------------------
teardown resources
-------------------------
volcoll is deleted
volume is deleted
But i could not find the way to reverse the properties of an object. How to reverse the pscustomobject properties in powershell?
If you only need to alter order of few properties, you could just list them manually to Select-Object:
$ResourceQueue | Select-Object VolumeCollection, Volume
For more generic solution one could use Get-Memberto get an array of properties, use [Array]::reverse to reverse
order and then Select-Object to get the properties in desired order. I came out with this:
$props = #()
$MyObject | Get-Member | ForEach-Object { $props += $_.name }
[Array]::Reverse($props)
$MyObject | Select-Object $props
You can do it this way:
$object = '' | select PropertyA, PropertyB, PropertyC
$object.PropertyA = 1234
$object.PropertyB = 'abcd'
$object.PropertyC = 'xyz'
$properties = ($object | Get-Member -MemberType NoteProperty).Name
[Array]::Reverse($properties)
$object | select $properties
The result is
PropertyC PropertyB PropertyA
--------- --------- ---------
xyz abcd 1234
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 have two CSVs:
Total 19_01_16.csv:
hostname,user,path,size,creation,LastAccess,Copied,NewName,Completed
comp1,user1,\\comp1\users1\file.pst,100,17/02/2015,17/01/2016,Yes,file_user1_.pst,
comp1,user1,\\comp1\users1\file2.pst,200,17/02/2015,17/01/2016,Yes,file2_user1_.pst,
comp2,user2,\\comp2\users2\file.pst,100,17/02/2015,17/01/2016,Yes,file_user2_.pst,
PST Passwords.csv:
user,Path,Password1,Password2,Password3,Error
user1,\\comp1\users1\file.pst,openme,openme,openme,
I'm trying to merge the two with different headers and additional content.
This is what I have so far:
$a = Import-Csv "$PST_PARENT\DailyReports\Total 19_01_16.csv"
"Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3" |
Set-Content "$PST_PARENT\DailyReports\New Build.csv"
$a | foreach {
$HOSTNAME = $_.hostname
$USER = $_.User
$PATH = $_.path
$NEW_NAME = $_.NewName
$NEWPATH = "$PST_SHARE\$USER\$NEW_NAME"
$SIZE = $_.Size
$SIZE_FAIL = ( [convert]::ToSingle( $SIZE ) / 2 )
$b = Import-Csv "$PST_PARENT\DailyReports\PST Passwords.csv"
$b | foreach {
if ( $USER -like $b.user ) {
$PASSWORD1 = $b.password1
$PASSWORD2 = $b.password2
$PASSWORD3 = $b.password3
} else {
$PASSWORD1 = "none"
$PASSWORD2 = "none"
$PASSWORD3 = "none"
}
}
$HOSTNAME,$USER,$NEW_NAME,$PATH,$NEWPATH,$SIZE,$SIZE_FAIL,$PASSWORD1,$PASSWORD2,$PASSWORD3 |
Add-Content "$PST_PARENT\DailyReports\New Build.csv"
}
The output of New Build.csv looks like this:
Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3
comp1
user1
file.pst
\\comp1\users1\file.pst
\\share\PST_Storage\file_user1_.pst
100
5
none
none
none
In essence the output is working, it's just not scrolling for each line, it's putting each array onto a new line.
I tried adding | ConvertTo-Csv -NoTypeInformation but all that did was convert the arrays to numbers, they still went down not across.
Any ideas? Am I on the right line or doing the whole thing so very wrong?
$HOSTNAME,$USER,... defines an array, which is written to the output file one element per line. You need to put the list in double quotes to turn it into a comma-separated string that you can write to the output file as a single line.
"Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3" |
Set-Content "$PST_PARENT\DailyReports\New Build.csv"
$a | foreach {
...
"$HOSTNAME,$USER,$NEW_NAME,$PATH,$NEWPATH,$SIZE,$SIZE_FAIL,$PASSWORD1,$PASSWORD2,$PASSWORD3"
} | Add-Content "$PST_PARENT\DailyReports\New Build.csv"
or you construct a custom object from the elements that you can export via Export-Csv.
$a | foreach {
...
New-Object -Type PSCustomObject -Property #{
'Hostname' = $HOSTNAME
'User' = $USER
'NewName' = $NEW_NAME
'Path' = $PATH
'NewPath' = $NEWPATH
'Size' = $SIZE
'SizeFail' = $SIZE_FAIL
'Password1' = $PASSWORD1
'Password2' = $PASSWORD2
'Password3' = $PASSWORD3
}
} | Export-Csv "$PST_PARENT\DailyReports\New Build.csv" -NoType