Compare-Object - Separate side columns - powershell

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))"].

Related

Add a column to a csv file and fill up new column based on an existing column powershell

I have been trying to add a new column to a csv file and populating the new column based on value in an existing column.
I have a table like this:
|name | number | state | desc|
| ---- | ------ |-------|-----|
|a | 1 | n | i |
|b | 2 | n | j |
|c | 3 | l | j |
|d | 4 | m | k |
I want to add a new column data and populate it based on number column matching with an array.
This is my code so far:
$a=("a","b","c")
$b=("p","q","r")
.
.
.
$c= import-csv -Path "C:\..."
$b |where-object {filtered the file based on some criteria}| select-object number, state, desc, #{Name="data"; Expression={Foreach-object {if ($_.number in $a){$_data = "x"}
elseif($_.number in $b){$_.data = "y"}.......} | export-csv -notypeinformation -path "C:\...."
The script runs but do not populate the new column. Please help
You've got the right idea. Import-Csv will produce an array of objects and you can use Select-Object to add calculated properties, then pipe again to Export-Csv. However, it's not exactly clear from the description or the example code what the expression should be. How do you want to define the new "data" property?
For now I'll work with what we have. The array variables $a & $b will never match anything. Also you can't use ForEach-Object like that, nor will assigning to $data work. The returning value of the Expression script block gets assigned to the property you named data. The following example demonstrates the point:
$a = ( "1", "2", "3")
$b = ( "4", "5", "6")
Import-Csv -Path "C:\temp\12-22-20.csv"|
Select-Object number, state, desc,
#{Name = 'Data'; Expression = { If( $_.Number -in $a ){ 'x' } elseif( $_.Number -in $b ){ 'y' } Else { $null }}} |
Export-Csv -Path "C:\temp\12-22-20_New.csv" -NoTypeInformation
The resulting Csv file will look something like:
number state desc Data
------ ----- ---- ----
1 n i x
2 n j x
3 l j x
4 m k y
Update: Example Using Add-Member
You do not need to use a loop to add the property:
$a = ( "1", "2", "3")
$b = ( "4", "5", "6")
Import-Csv -Path "C:\temp\12-22-20.csv" |
Add-Member -MemberType ScriptProperty -Name "data" -Value { If( $this.Number -in $a ){ 'x' } elseif( $this.Number -in $b ){ 'y' } Else { $null }} -PassThru |
Export-Csv -Path C:\temp\12-22-20_New.csv -NoTypeInformation
By using a MemberType of ScriptProperty we can make a slight modification to script block, replacing $_ with $this The pipe is an implicit loop. I'm not sure if there are any detractions to using a ScriptProperty, but this exports as expected. This approach doesn't require storing the output in $c, but -PassThru would facilitate that if preferred.
99% of the time Select-Object is used for this. The only difference I'm aware of it Select-Object converts the objects to PSCustomObjects. Get-Member will preserve the underlying type, however Import-Csv only outputs PSCustomObjects in the first place, so there's no impact here.
Try iterating over the $c array of imported objects and add the new property to all objects. You want to make sure the new column exists in all of the objects. You can either use Select-Object as in your example, or you can use Add-Member to add it to the imported object.
$a=("a","b","c")
$b=("p","q","r")
...
$c = Import-Csv -Path "C:\..."
$c | ForEach-Object {
$value = ""
# custom logic for value of "data"
# if (...) { $value = ... }
$_ | Add-Member -MemberType NoteProperty -Name "data" -Value $value
}
$c | Export-Csv -NoTypeInformation -path "C:\...."

Searching for key in ConvertFrom-StringData hash

I have a hash table that I got from a file, command used:
[array]$hash= Get-Content -raw '../../file.txt' | ConvertFrom-StringData
file.txt looks like this:
key=value
It works perfectly, the problem is when I trying to search using $hash.GetEnumerator.
I am trying to do something like this:
$hash.GetEnumerator() | where {$_.value -match 'value'} // or with key
It always returns an empty value. Got it from link, tried to create a local hash using $hash=#{} then add, and it worked(like for the guy from the link).
Note! $hash.GetEnumerator() | Sort-Object Name works for me too, and returning the right table.
Do you have any idea how can I search(-eq or -match) in the hash table that I have created?
try this
$hash.GetEnumerator() | where {$($_.Value) -match 'value'}
or like this
$hash.Keys | % {$($hash[$_]) -eq 'value'} | %{$hash[$_]}
or better, you can transform your hashtable into objects list
$hash.GetEnumerator() |
% {New-Object psobject -Property #{Name=$($_.Name); Value=$($_.Value)} } |
where Value -match 'value'
Your response to my comment asking for clarification didn't clarify too much, but I'm going to assume that you want to find the value for a particular key in the hashtable. That kind of lookup is the core functionality of hashtables.
$ht = #'
foo=23
bar=42
baz=5
'# | ConvertFrom-StringData
$key = 'bar'
$ht[$key] # returns 42
If you actually want a fuzzy match on the keys you could replace the direct lookup with a wildcard match like this:
$partialKey = 'ba*'
$ht.Keys | Where-Object {
$_ -like $partialKey
} | ForEach-Object {
$ht[$_] # returns 42 and 5
}
or a regular expression match like this:
$partialKey = '^ba'
$ht.Keys | Where-Object {
$_ -match $partialKey
} | ForEach-Object {
$ht[$_] # returns 42 and 5
}
As an alternative to looking up multiple keys in a loop you could also build a list of keys and use that list in a single lookup:
$partialKey = 'ba*'
$keys = $ht.Keys | Where-Object { $_ -like $partialKey }
$ht[$keys] # returns 42 and 5

Hashtable with multiple values in GridView

I am storing data in a hashtable with multiple values like this:
$hash = #{}
$folders = dir (...) | where (...)
foreach ($folder in $folders) {
$num1 = (...)
$num2 = (...)
$hash.Add($folder.Name,#($num1,$num2))
}
So this is a hash with an array in its value part. The array always got two items. When finished the foreach part I want to show the data with Out-GridView like this:
$hash | select -Property #{Expression={$_.Name};Label="FolderName"},
#{Expression={$_.Name[0]};Label="num1"},
#{Expression={$_.Name[1]};Label="num2"} | Out-GridView
But as you can imagine, this is not working. How can I split the stored array in the value part of my hash into two new columns to show them in overall three columns in the GridView?
Should be something like Name, Value1, Value2 ...
And then multiple items which are stored in the hashtable as multiple rows.
Hashtables are not lists of objects with a Name and a Value property. That's just how PowerShell displays the data structure for your convenience. For processing a hashtable the way you tried you need an enumerator to produce such objects:
$hash.GetEnumerator() |
Select-Object #{n='FolderName';e={$_.Name}},
#{n='num1';e={$_.Value[0]}},
#{n='num2';e={$_.Value[1]}} |
Out-GridView
Or you can enumerate the keys of the hashtable, use them as the current objects in the pipeline, and look up the values by the respective key and index:
$hash.Keys |
Select-Object #{n='FolderName';e={$_}},
#{n='num1';e={$hash[$_][0]}},
#{n='num2';e={$hash[$_][1]}} |
Out-GridView
If you don't know the number of array elements beforehand you need an inner loop for processing the nested arrays, e.g. like this:
$hash.Keys | ForEach-Object {
$o = New-Object -Type PSObject -Property #{ 'FolderName' = $_ }
$a = $hash[$_]
for ($i = 1; $i -le $a.Count; $i++) {
$o | Add-Member -Type NoteProperty -Name "num$i" -Value $a[$i-1]
}
$o
} | Out-GridView
If you have a variable number of array elements, beware that PowerShell determines by the first object which properties will be displayed.

powershell: Check if any of a bunch of properties is set

I'm importing a csv-file which looks like this:
id,value1.1,value1.2,value1.3,Value2.1,Value2.2,Value3.1,Value3.2
row1,v1.1,,v1.3
row2,,,,v2.1,v2.2
row3,,,,,,,v3.2
Now I want to check, if any of the value-properties in one group is set.
I can do
Import-Csv .\test.csv | where {$_.Value1.1 -or $_.Value1.2 -or $_.Value1.3}
or
Import-Csv .\test.csv | foreach {
if ($_.Value1 -or $_.Value2 -or $_.Value3) {
Write-Output $_
}
}
But my "real" csv-file contains about 200 columns and I have to check 31 properties x 5 different object types that are mixed up in this csv. So my code will be realy ugly.
Is there anything like
where {$_.Value1.*}
or
where {$ArrayWithPropertyNames}
?
You could easily use the Get-Member cmdlet to get the properties which have the correct prefix (just use * as a wildcard after the prefix).
So to achieve what you want you could just filter the data based on whether any of the properties with the correct prefix contains data.
The script below uses your sample data, with a row4 added, and filters the list to find all items which have a value in any property starting with value1.
$csv = #"
id,value1.1,value1.2,value1.3,Value2.1,Value2.2,Value3.1,Value3.2
row1,v1.1,,v1.3
row2,,,,v2.1,v2.2
row3,,,,,,,v3.2
row4,v1.1,,v1.3
"#
$data = ConvertFrom-csv $csv
$data | Where {
$currentDataItem = $_
$propertyValues = $currentDataItem |
# Get's all the properties with the correct prefix
Get-Member 'value1*' -MemberType NoteProperty |
# Gets the values for each of those properties
Foreach { $currentDataItem.($_.Name) } |
# Only keep the property value if it has a value
Where { $_ }
# Could just return $propertyValues, but this makes the intention clearer
$hasValueOnPrefixedProperty = $propertyValues.Length -gt 0
Write-Output $hasValueOnPrefixedProperty
}
Alternate solution:
$PropsToCheck = 'Value1*'
Import-csv .\test.csv |
Where {
(($_ | Select $PropsToCheck).psobject.properties.value) -contains ''
}

PowerShell - filtering for unique values

I have an input CSV file with a column containing information similar to the sample below:
805265
995874
805674
984654
332574
339852
I'd like to extract unique values into a array based on the leading two characters, so using the above sample my result would be:
80, 99, 98, 33
How might I achieve this using PowerShell?
Use Select-Object and parameter -unique:
$values =
'805265',
'995874',
'805674',
'984654',
'332574',
'339852'
$values |
Foreach-Object { $_.Substring(0,2) } |
Select-Object -unique
If conversion to int is needed, then just cast it to [int]:
$ints =
$values |
Foreach-Object { [int]$_.Substring(0,2) } |
Select-Object -unique
I'd use the Group-Object cmdlet (alias group) for this:
Import-Csv foo.csv | group {$_.ColumnName.Substring(0, 2)}
Count Name Group
----- ---- -----
2 80 {805265, 805674}
1 99 {995874}
1 98 {984654}
2 33 {332574, 339852}
You might use a hash table:
$values = #(805265, 995874, 805674, 984654, 332574, 339852)
$ht = #{}
$values | foreach {$ht[$_ -replace '^(..).+','$1']++}
$ht.keys
99
98
33
80
You could make a new array with items containing the first two characters and then use Select-Object to give you the unique items like this:
$newArray = #()
$csv = Import-Csv -Path C:\your.csv
$csv | % {
$newArray += $_.YourColumn.Substring(0, 2)
}
$newArray | Select-Object -Unique
Just another option instead of using Select-Object -unique would be to use the Get-Unique cmdlet (or its alias gu; see the detailed description here) as demonstrated below:
$values = #(805265, 995874, 805674, 984654, 332574, 339852)
$values | % { $_.ToString().Substring(0,2) } | Get-Unique
# Or the same using the alias
$values | % { $_.ToString().Substring(0,2) } | gu