Powershell v2.0 substitute null values from a Hash table - powershell

I have a hash table as below:
$Hash = #{
Team1=$Team1.count
Team2=$Team2.count
Team3=$Team3.count
}
$GroupByTeam = New-Object psobject -Property $Hash |
Select 'Team1','Team2','Team3' | ConvertTo-Html -Fragment
This is fine and each "team" returns their own value. However, teams may have a null value and I wish to substitute this for "0".
In an attempt to work this out, I have tried to select the null value first but can't seem to do this:
$Hash.values | select -property Values
Values
------
{1, 2}
But
$Hash.values | select -property Values | where {$_.Values is $null}
doesn't pull back anything. Also tried:
$Hash.values | select -expandproperty Values | where {$_.Values is $null}
Any ideas?
thanks

Your best option is to cast the values to int when creating the hashtable:
$Hash = #{
Team1 = [int]$Team1.Count
Team2 = [int]$Team2.Count
Team3 = [int]$Team3.Count
}
If that's not possible for some reason you could go with an enumerator:
($Hash.GetEnumerator()) | ForEach-Object {
if ($_.Value -eq $null) { $Hash[$_.Name] = 0 }
}
or (as Mathias suggested) use the Keys property to the same end:
($Hash.Keys) | ForEach-Object {
if ($Hash[$_] -eq $null) { $Hash[$_] = 0 }
}
Note that either way you need to use a subexpression (or assign the enumerated objects/keys to a variable) otherwise you'll get an error because you're modifying a data structure while it's being enumerated.

What you'll want to do is collect the keys that refer to null values, and then populate those with 0s:
# Create and populate hashtable
$HashTable = #{
Team1 = 123
Team2 = $null
Team3 = 456
}
# Find keys of `$null` values
$nullKeys = $HashTable.Keys |Where-Object { $HashTable[$_] -eq $null }
# Populate appropriate indices with 0
$nullKeys |ForEach-Object { $HashTable[$_] = 0 }

Related

How do I identify hashtable keys with multiple values in powershell

I have a hashtable created from an array like this:
$employeesHashtable = $employees | Group-Object -Property Initials -AsHashTable
How do I find keys having multiple values?
The alternative to using .GetEnumerator() can be using the hash table Keys, the key collection implements ICollection and can be enumerated without issues:
$keysWithMultipleValues = $employeesHashtable.Keys.where{ $employeesHashtable[$_].Count -gt 1 }
I ended up with this:
$keysWithMultipleValues = $employeesHashtable.GetEnumerator() | `
ForEach-Object { [PSCustomObject]#{ Key = $_.Key; Count = $_.Value.Count } } | `
Where-Object { $_.Count -gt 1 }

How to display all properties with Format-Table cmdlet

I have few [pscustomobject] objects that can have not all properties.
For example:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
And I try to display all properties with Format-Table like this:
PS> $1,$2 | Format-Table
A B
- -
a1 b1
a2
PS> $2,$1 | Format-Table
A C
- -
a2 c2
a1
But every time it displays only properties from first object in collection.
I want to display all properties like if I set -Property argument explicitly.
PS> $1,$2 | Format-Table -Property A,B,C
A B C
- - -
a1 b1
a2 c2
Setting -Property argument is good if:
All set of properties is known in advance
Collection is small and I can get all properties with Get-Member -MemberType Properties
But I have a huge collection (above 10000 objects) with unknown properties so I need help with it.
REMARK: Format-Table will be used only for small slices (10-100 elements).
For that, you can use below function to merge all properties into the first object:
function Complete-ObjectHeaders {
# function to add properties to the first item in a collection of PSObjects
# when this object is missing properties from items further down the array.
# you may need this if you have such a collection and want to export it
# to Csv, since Export-Csv (and also Format-Table) only looks at the FIRST
# item to create the csv column headers.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[PSObject[]]$Collection,
[int]$MaxItemsToTest = -1, # < 0 --> test all items in the collection
[switch]$SortHeaders
)
# Try and find all headers by looping over the items in the collection.
# The headers will be captured in the order in which they are found.
if ($MaxItemsToTest -gt 0) {
$MaxItemsToTest = [math]::Min($MaxItemsToTest, $Collection.Count)
$headers = for($i = 0; $i -lt $MaxItemsToTest; $i++) {
($Collection[$i].PSObject.Properties).Name
}
$headers = $headers | Select-Object -Unique
}
else {
$headers = $Collection | ForEach-Object {($_.PSObject.Properties).Name} | Select-Object -Unique
}
if ($SortHeaders) { $headers = $headers | Sort-Object }
# update the first object in the collection to contain all headers
$Collection[0] = $Collection[0] | Select-Object $headers
,$Collection
}
Use like this:
$1 = [pscustomobject]#{ A='a1'; B='b1' }
$2 = [pscustomobject]#{ A='a2'; C='c2' }
# just output to console
Complete-ObjectHeaders -Collection $1,$2 | Format-Table -AutoSize
# or capture the merged array of objects in a new variable you can save as CSV file for instance
$merged = Complete-ObjectHeaders -Collection $1,$2
$merged | Export-Csv -Path 'D:\Test\Merged.csv' -NoTypeInformation
Output:
A B C
- - -
a1 b1
a2 c2
Thanks #theo for the answer.
I used it to write my own version of a function that supports pipelining.
function Expand-Properties {
[Cmdletbinding()]
param(
[Parameter(ValueFromPipeline)]
$InputObject,
[Parameter()]
[Alias('All')]
[switch]
$ExpandAll,
[Parameter()]
[switch]
$SortHeaders
)
begin {
$collection = [System.Collections.ArrayList]::new()
$properties = [System.Collections.ArrayList]::new()
}
process {
[void]$collection.Add($InputObject)
$properties.AddRange((($InputObject.PSObject.Properties).Name))
}
end {
if ($SortHeaders) {
$properties = $properties | Sort-Object -Unique
} else {
$properties = $properties | Select-Object -Unique
}
if ($ExpandAll) {
for ($i = 0; $i -lt $collection.Count; ++$i) {
$collection[$i] = $collection[$i] | Select-Object -Property $properties
}
} else {
$collection[0] = $collection[0] | Select-Object -Property $properties
}
$collection
}
}
EXAMPLE:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
PS> $1, $2 | Expand-Properties
A B C
- - -
a1 b1
a2 c2

Filter ArrayList on nested property

I have an PowerShell ArrayList ($displayObjects) which contains:
Name ID Tags
---- -- ----
Test1 123 {#{id=4567; name=test1;}}
Test2 345 {#{id=4567; name=test1;}, #{id=6789; name=test2}}
Test3 567 {#{id=4567; name=test1;}, #{id=6789; name=test2}, #{id=7890; name=test3}}
And another:
$filter = #('test1', 'test2')
And waht to filter the $displayObjects (Tags.name) based on the values specified in the $filter array.
So in the case above the result should contain only rows 2 and 3 (from $displayObjects).
I've strted thinking and testing with $displayObjects | Where-Object ... but cant think of a way how to loop in there. Any suggestions?
Something like this might work:
... | Where-Object {
$a = #($_.Tags.name)
($filter | Where-Object {$a -contains $_}).Count -eq $filter.Count
}
There is probably a more efficient way to do this with LINQ (like this?), but I'm not versed enough in that.
This should work. It might not be the most efficient way, though.
$displayObjects | Where-Object {
$tags = [string]$_.Tags
$returnObject = $true
$filter | foreach {
if($tags -notlike "*$_*"){
$returnObject = $false
}
}
$returnObject
}

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

Inner Join in PowerShell (without SQL)

How do we make Inner-Join or something a Cross-Join in PowerShell or PowerCLI?
Even though im new to PowerCLI/PowerShell , I do have a basic grasp on them, yet have practically spent 2 days trying to figure this, going through numerous documentations and blogs to no avail.
All I really want to know is if after typing my command
Get-Content File.txt
and getting:
Output1 or Table1 is
Name: Abc
Group: Bad
Policy: Great
Name: redi
Group: Good
Policy: MAD
etc. etc.
100s of these, and obviously more than just the 3 elements of Name, Group, Policy each.
Table2/Output2
Name: Abc
Limit: 10
used: 5
Name: redi
Limit: 20
used: 1
etc. etc.
100s of these.
and like 13 more of these text file tables, all with the "Name" as unique.
How can I combine it into one output at the end using Name with all the other elements?
My most obvious thought was something akin to joins, even if I had to do them 1 at a time, but even that I cant figure out how to do.
Is there anyway to do this in PowerShell itself without me having to go into Python or SQL?
If yes is there a method that is able to combine fields in spots where it's null?
If its not clear what type of result I am hoping for it will look something akin to this:
Name: Abc
Group: Bad
Policy: Great
Limit: 10
used: 5
Name: redi
Group: Good
Policy: MAD
Limit: 20
used: 1
Paweł Dyl provided you a solution
based on your two tables. However you probably need a generic solution where you don't have to specify each property by name yourself.
I would combine each table to a an array. Group the tables on the Name property using the Group-Object cmdlet. Iterate over each group and create a PsObject using the properties:
$table1 = [PSCustomObject]#{ Name = 'Abc'; Group = 'Bad'; Policy = 'Great'}, [PSCustomObject]#{ Name = 'redi'; Group = 'Good'; Policy = 'MAD'}
$table2 = [PSCustomObject]#{ Name = 'Abc'; Limit = '10'; used = '5'}, [PSCustomObject]#{ Name = 'redi'; Limit = '20'; used = '1'}
$allTables = $table1 + $table2
$allTables | group Name | Foreach {
$properties = #{}
$_.Group | Foreach {
$_.PsObject.Properties | Where Name -ne 'Name' | Foreach {
$properties += #{
"$($_.Name)" = "$($_.Value)"
}
}
}
$properties += #{Name = $_.Name}
New-Object PSObject –Property $properties
}
Output:
Group : Bad
Policy : Great
Name : Abc
Limit : 10
used : 5
Group : Good
Policy : MAD
Name : redi
Limit : 20
used : 1
You can use simple loop join as follows:
$table1 = [pscustomobject]#{Name='Abc';Group='Bad';Policy='Great'},[pscustomobject]#{Name='redi';Group='Good ';Policy='MAD'}
$table2 = [pscustomobject]#{Name='Abc';Limit=10;used=5},[pscustomobject]#{Name='redi';Limit=20;used=1}
$table1 | % {
foreach ($t2 in $table2) {
if ($_.Name -eq $t2.Name) {
[pscustomobject]#{Name=$_.Name;Group=$_.Group;Policy=$_.Policy;Limit=$t2.Limit;Used=$t2.Used}
}
}
}
Assuming uniqueness of keys you can also use faster, hashtable approach:
$hashed = $table1 | group Name -AsHashTable
$table2 | % {
$matched = $hashed[$_.Name]
if ($matched) {
[pscustomobject]#{Name=$matched.Name;Group=$matched.Group;Policy=$matched.Policy;Limit=$_.Limit;Used=$_.Used}
}
}
You can also use generic solution and wrap it in function. It matches records by their property names:
function Join-Records($tab1, $tab2){
$prop1 = $tab1 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t1
$prop2 = $tab2 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t2
$join = $prop1 | ? {$prop2 -Contains $_}
$unique1 = $prop1 | ?{ $join -notcontains $_}
$unique2 = $prop2 | ?{ $join -notcontains $_}
if ($join) {
$tab1 | % {
$t1 = $_
$tab2 | % {
$t2 = $_
foreach ($prop in $join) {
if (!$t1.$prop.Equals($t2.$prop)) { return; }
}
$result = #{}
$join | % { $result.Add($_,$t1.$_) }
$unique1 | % { $result.Add($_,$t1.$_) }
$unique2 | % { $result.Add($_,$t2.$_) }
[PSCustomObject]$result
}
}
}
}
$table1 = [pscustomobject]#{Name='Abc';Group='Bad';Policy='Great'},
[pscustomobject]#{Name='redi';Group='Good ';Policy='MAD'},
[pscustomobject]#{Name='Not joined';Group='Very bad';Policy='Great'}
$table2 = [pscustomobject]#{Name='Abc';Limit=10;used=5},
[pscustomobject]#{Name='redi';Limit=20;used=1},
[pscustomobject]#{Name='redi';Limit=20;used=2}
#name is only common property, records joined by name
Join-Records $table1 $table2
#example2
$test1 = [pscustomobject]#{A=1;B=1;C='R1'},
[pscustomobject]#{A=1;B=2;C='R2'},
[pscustomobject]#{A=2;B=2;C='R3'}
$test2 = [pscustomobject]#{A=1;B=1;D='R4'},
[pscustomobject]#{A=3;B=2;D='R5'},
[pscustomobject]#{A=4;B=2;D='R6'}
Join-Records $test1 $test2 #joined by two common columns - A and B
You can also cascade calls:
$test1 = [pscustomobject]#{A=1;B=1;C='R1'},
[pscustomobject]#{A=1;B=2;C='R2'},
[pscustomobject]#{A=2;B=2;C='R3'}
$test2 = [pscustomobject]#{A=1;B=1;D='R4'},
[pscustomobject]#{A=3;B=2;D='R5'},
[pscustomobject]#{A=4;B=2;D='R6'}
$test3 = [pscustomobject]#{B=1;E='R7'},
[pscustomobject]#{B=2;E='R8'},
[pscustomobject]#{B=3;E='R9'}
#first join by common A and B, then join result by common B
Join-Records (Join-Records $test1 $test2) $test3
So I found an Answer which was more suitable and it uses the join-Object function which was defined below:
you can access it at https://github.com/RamblingCookieMonster/PowerShell/blob/master/Join-Object.ps1
All I really had to do was Define my outputs as $A and $B and $C and so on, and just
$Join1= Join-Object -Left $A -Right $B -LeftJoinProperty Name - RightJoinProperty Name
made $Join2 then 3 so on until I got it all done
$Join2 = Join-Object -Left $Join1 -Right $C -LeftJoinProperty Name -RightJoinProperty Name
$Join3 = Join-Object -Left $Join2 -Right $D -LeftJoinProperty Name -RightJoinProperty Name
$Join4 = Join-Object -Left $Join3 -Right $E -LeftJoinProperty Name -RightJoinProperty Name
Until I got it all done
$Table1 | Join $Table2 -Using Name
$Table1 | Join $Table2 #Cross Join
See: In Powershell, what's the best way to join two tables into one?