Sorting and Comparing Arraylists - powershell

I'm pretty new to Powershell and have a problem.
It seems I can get neither the Compare-Object nor the Sort-Object funtions to work.
I got two Arraylists which I fill with 10 objects of type "Table".
#Create Arraylists
$list1 = new-object System.Collections.ArrayList
$list2 = new-object System.Collections.ArrayList
#Declare Table-Class
class Table {
[String] $name
[Int] $number
}
#Fill list1
[Int] $i = 0
while ($i -lt 10) {
$entry = new-Object Table
$entry.name = "Name";
$entry.number = $i;
$list1.Add($entry)
$i++
}
#Fill list2
[Int] $j = 10
while ($j -gt 0) {
$entry = new-Object Table
$entry.name = "name";
$entry.number = $j;
$list2.Add($entry)
$j--
}
Now I want to compare these two ArrayLists like this:
Compare-Object $list1 $list2 | ForEach-Object {$_.InputObject}
This doesn't seem to work and I think it's because I'm not really doing anything with the comparison. If someone could help me with the correct Syntax I'd be really happy.
Anyway, I think this comparison would somehow return a $false boolean.
If that were true, I'd like to sort $list2 by $entry.number.
I'm attempting this like that:
$list2 = $list2 | Sort-Object -Property { $_[1] }
But my list doesn't change at all. I already tried several different "solutions" but it seems none of these is a real "solution".
I'd be really glad if someone could help me or at least point me in the right direction.
EDIT: For future readers, here is the working syntax:
#sort list by the property number
$list2 = $list2 | sort-object -Property number
#count differences and if differences greater than 0, do stuff
[Int] $diff
$properties = $list1 | Get-Member -MemberType Property | Select-Object -ExpandProperty Name
foreach ($property in $properties) {
$diff = 0
$diff = (Compare-Object $list1 $list2 -Property "$property").count
if ($diff -ne 0) {
#do stuff
}
else {
#do something else
}
}

Your sorting does not work because -Property expects you to pass the name of an actual property. In your case the class two properties: $name and $number. You can also check what properties an object has by using $list2 | Get-Member. So in order to sort, you can use:
$list2 = $list2 | Sort-Object -Property number
Now the reason Compare-Object is not working is because it's implemented differently that one might expect. See this answer explaining how it works.
One other thing you should keep in mind: Calling $list2.Add($entry) actually returns an integer (the index of the inserted element). PowerShell returns all uncaptured output. This can cause some unexpected behavior/output if you are not careful. So you should get in the habit of writing [void] $list2.Add($entry), unless you really want to return those indexes.

Related

Powershell Pipeline - return a new Object, that was created within pipline

I keep running into the same problem again, and i have my default way of handling it, but it keeps bugging me.
Isn't there any better way?
So basicly i have a pipline running, do stuff within the pipline, and want to return a Key/Value Pair from within the pipline.
I want the whole pipline to return a object of type psobject (or pscustomobject).
Here is the way i do it everytime.
I create a hashtable at the beginning of the pipline and add key/Value Pairs from within the pipline to this hashtable using the .Add() method.
Afterwards i create a psobject by passing the hashtbale to New-Object`s -Property Parameter. This gives me the desired result.
Get-Process | Sort -Unique Name | ForEach-Object -Begin { $ht = #{} } -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key,$val)
}
# Create PSObject from Hashtable
$myAwesomeNewObject = New-Object psobject -Property $ht
# Done - returns System.Management.Automation.PSCustomObject
$myAwesomeNewObject.GetType().FullName
But this seems a bit cluncky, isn't there a more elegant way of doing it?
Something like this:
[PSObject]$myAwesomeNewObject = Get-Process | Sort -Unique Name | ForEach-Object -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# return Key/Val Pair
#{$key=$val}
}
# Failed - returns System.Object[]
$myAwesomeNewObject.GetType().FullName
This unfortunally dosn't work, since the pipe returns an array of hashtables, but i hope you know now what iam trying to achieve.
Thanks
Not sure if this is more elegant but just another way of doing it, this uses an anonymous function so $ht will no longer be available after execution, and casts to [pscustomobject] instead of using New-Object:
[pscustomobject] (Get-Process | Sort -Unique Name | & {
begin { $ht = #{ } }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key, $val)
}
end { $ht }
})
You can also use the -End parameter to convert the final hash table to a pscustomobject as part of the pipeline, without needing to set the whole thing to a variable
$ht[$key]=$val is also a nice shorthand for $ht.Add($key,$val):
Get-Process |
Sort -Unique Name |
Foreach -Begin { $ht = #{} } -Process {
$ht[$_.Name] = $_.Id
} -End {[pscustomobject]$ht} |
## continue pipeline with pscustomobject
Thanks to #Santiago Squarzon and #Cpt.Whale answers, i were able to combine them to create a solution that pleases me:
$myAwesomeNewObject = `
Get-Process | Sort -Unique Name | & {
begin { $ht = #{} }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht[$key]=$val
}
end {[pscustomobject]$ht}
}
# Success - System.Management.Automation.PSCustomObject
$myAwesomeNewObject.Gettype().FullName
# And helper Hashtable is NULL thanks to the
# anonym function
$null -eq $ht
Thanks alot Guys
Alternatively you may create a hashtable using Group-Object -AsHashTable:
# Store the PIDs of all processes into a PSCustomObject, keyed by the process name
$processes = [PSCustomObject] (Get-Process -PV proc |
Select-Object -Expand Id |
Group-Object { $proc.Name } -AsHashtable)
# List all PIDs of given process
$processes.chrome
Notes:
Common parameter -PV (alias of -PipelineVariable) makes sure that we can still access the full process object from within the calculated property of the Group-Object command, despite that we have a Select-Object command in between.
The values of the properties are arrays, which store the process IDs of all instances of each process. E. g. $processes.chrome outputs a list of PIDs of all instances of the chrome process.

Split values table for extract with powershell

I would like to make a new line in my hashtable to extract it in a csv.
I initialize my variable in hastable
$vlr=#{}
$vlr["OS"]=,#("test","test2")
I extract my variable in a .csv
$Output += New-Object PSObject -Property $vlr
$output | Convert-OutputForCSV | export-csv -NoTypeInformation -Delimiter ";" -Path $filepath
and the problem is in the extraction the result of the values ​​is on the same line
My goal is that each value is in a different line
You might want to use the Out-String cmdlet for this:
$vlr=#{}
$vlr["OS"]=,#("test","test2") | Out-String
$Object = New-Object PSObject -Property $vlr
$Object | ConvertTo-Csv
"OS"
"test
test2
"
this solution does not work because in the case where $vlr with several names the extraction will be complicated
$vlr=#{}
$vlr["OS"]=,#("test","test2")
$vlr["PS"]=,#("lous","tique")
it's a problem
https://gallery.technet.microsoft.com/scriptcenter/Convert-OutoutForCSV-6e552fc6
For the function Convert-OutputForCSV
I don't know what the posted function does, but you can make your own function to handle a single-key or multi-key hash table provided all of the key value counts are the same.
function Convert-OutputForCsv {
param(
[parameter(ValueFromPipeline)]
[hashtable]$hash
)
# Array of custom object property names
$keys = [array]$hash.Keys
# Loop through each key's values
for ($i = 0; $i -lt $hash.($keys[0]).count; $i++) {
# Custom object with keys as properties. Property values are empty.
$obj = "" | Select $keys
# Loop through key names
for ($j = 0; $j -lt $keys.Count; $j++) {
$obj.($keys[$j]) = $hash.($Keys[$j])[$i]
}
$obj
}
}
$vlr=[ordered]#{}
$vlr["OS"]='test','test2'
$vlr["PS"]='lous','tique'
$vlr | Convert-OutputForCsv | Export-Csv -NoTypeInformation -Delimiter ";" -Path $filepath
Honestly, if you are in control of the input data, I would just type out a CSV instead of typing out hash tables.
this solution is good in my simplified case but not adapted to my case unfortunately
I'm merging my old base2 array with my new base array and my goal is to concatenate the values ​​in an excel to make them usable
$base2 = Get-content $filepath2 | select -first 1
$base2 = $base2 -split ";"
$base2 = $base2.Replace("`"", "")
$cunt2 = $base2.count - 1
$h2 = ipcsv $filepath2 -Delimiter ";"
$HashTable2 = #{}
for ($i = 0 ; $i -le $cunt2 ; $i++) {
foreach ($r in $h2) {
$HashTable2[$base2[$i]] = $r.($base2[$i])
}
base2 = old tables
$base = Get-content $filepath2 | select -first 1
$base = $base -split ";"
$base = $base.Replace("`"", "")
$cunt = $base.count - 1
$h1 = ipcsv $filepath -Delimiter ";"
$HashTable = #{}
for ($i = 0 ; $i -le $cunt ; $i++) {
foreach ($r in $h1) {
$HashTable[$base[$i]] = $r.($base[$i])
}
New tables $base
once the two arrays are initialized, I merge them and this is where I have to separate the values ​​row by row
$csvfinal = $hashtable, $hashtable2 | Merge-Hashtables

Format-Table in PowerShell inside a ForEach

Not sure what I am doing wrong here with this, I know it has to do with the fact that it is inside a ForEach loop. I have tried moving the below code around. Even with the code half and half (half in and half out of the loop does not seem to work). Still new to PowerShell and I know that I need to variables to $table each iteration or store them some where and read them later.
foreach ($gp in $GPINFO) {
# Code that gets the values for $gp, $aclBefore and $aclAfter is here
$table = new-object psobject -Property #{
GPO_Name = $gp.DisplayName
Old_Owner = $aclBefore
New_Owner = $aclAfter
}
$table | Format-Table GPO_Name,Old_Owner,New_Owner
}
If you can help me figure out what I am doing wrong that would be great, I know that every time the ForEach gets $gp out of the $GPINFO it is running the $table stuff and that is my problem. So instead of one continuous table I end up with multiple tables with one set of data in each.
Thanks in advance
Just simply output the table after the loop completes.
$table = foreach ($gp in $GPINFO) {
# Code that gets the values for $aclBefore and $aclAfter is here
new-object psobject -Property #{
GPO_Name = $gp.DisplayName
Old_Owner = $aclBefore
New_Owner = $aclAfter
}
}
$table | Format-Table
Any output from within the foreach loop will be stored in $table. Each iteration will output an object, ultimately creating an array of those objects stored in $table.
You just can't pipe from foreach (). It's an odd part of the language that comes up a lot. Other ways:
Foreach-object:
$GPINFO | foreach-object {
$gp = $_
new-object psobject -Property #{
GPO_Name = $gp.DisplayName
Old_Owner = $aclBefore
New_Owner = $aclAfter
}
} | Format-Table GPO_Name,Old_Owner,New_Owner
Call operator and scriptblock:
& {
foreach ($gp in $GPINFO) {
new-object psobject -Property #{
GPO_Name = $gp.DisplayName
Old_Owner = $aclBefore
New_Owner = $aclAfter
}
}
} | Format-Table GPO_Name,Old_Owner,New_Owner

Compare-Object - Separate side columns

Is it possible to display the results of a PowerShell Compare-Object in two columns showing the differences of reference vs difference objects?
For example using my current cmdline:
Compare-Object $Base $Test
Gives:
InputObject SideIndicator
987654 =>
555555 <=
123456 <=
In reality the list is rather long. For easier data reading is it possible to format the data like so:
Base Test
555555 987654
123456
So each column shows which elements exist in that object vs the other.
For bonus points it would be fantastic to have a count in the column header like so:
Base(2) Test(1)
555555 987654
123456
Possible? Sure. Feasible? Not so much. PowerShell wasn't really built for creating this kind of tabular output. What you can do is collect the differences in a hashtable as nested arrays by input file:
$ht = #{}
Compare-Object $Base $Test | ForEach-Object {
$value = $_.InputObject
switch ($_.SideIndicator) {
'=>' { $ht['Test'] += #($value) }
'<=' { $ht['Base'] += #($value) }
}
}
then transpose the hashtable:
$cnt = $ht.Values |
ForEach-Object { $_.Count } |
Sort-Object |
Select-Object -Last 1
$keys = $ht.Keys | Sort-Object
0..($cnt-1) | ForEach-Object {
$props = [ordered]#{}
foreach ($key in $keys) {
$props[$key] = $ht[$key][$_]
}
New-Object -Type PSObject -Property $props
} | Format-Table -AutoSize
To include the item count in the header name change $props[$key] to $props["$key($($ht[$key].Count))"].

Powershell Select-Object from array not working

I am trying to seperate values in an array so i can pass them to another function.
Am using the select-Object function within a for loop to go through each line and separate the timestamp and value fields.
However, it doesn't matter what i do the below code only displays the first select-object variable for each line. The second select-object command doesn't seem to work as my output is a blank line for each of the 6 rows.
Any ideas on how to get both values
$ReportData = $SystemStats.get_performance_graph_csv_statistics( (,$Query) )
### Allocate a new encoder and turn the byte array into a string
$ASCII = New-Object -TypeName System.Text.ASCIIEncoding
$csvdata = $ASCII.GetString($ReportData[0].statistic_data)
$csv2 = convertFrom-CSV $csvdata
$newarray = $csv2 | Where-Object {$_.utilization -ne "0.0000000000e+00" -and $_.utilization -ne "nan" }
for ( $n = 0; $n -lt $newarray.Length; $n++)
{
$nTime = $newarray[$n]
$nUtil = $newarray[$n]
$util = $nUtil | select-object Utilization
$util
$tstamp = $nTime | select-object timestamp
$tstamp
}
Let me slightly modify the processing code, if it will help.
$csv2 |
Where-Object {$_.utilization -ne "0.0000000000e+00" -and $_.utilization -ne "nan" } |
Select-Object Utilization,TimeStamp
It will produce somewhat different output, but that should be better for working with.
The result are objects with properties Utilization and TimeStamp. You can pass them to the another function as you mention.
Generally it is better to use pipes instead of for loops. You don't need to care about indexes and it works with arrays as well as with scalar values.
If my updated code won't work: is the TimeStamp property really filled with any value?