Convert a list of key value pairs to a hashtable - powershell

What is the best way to convert a List to a Hashtable?
Say I have a list like ("Key",$value,"Key2",$value2)
What is the shortest syntax to convert it into a Hashtable?

Try the following
$table = new-object System.Collections.Hashtable
for ( $i = 0; $i -lt $list.Length; $i += 2 ) {
$table.Add($list[$i],$list[$i+1]);
}

Function ConvertTo-Hashtable($list) {
$h = #{}
while($list) {
$head, $next, $list = $list
$h.$head = $next
}
$h
}
ConvertTo-Hashtable ("Key",1,"Key2",2)

If your list is composed of NameValuePair objects, then you can use:
Function ConvertListOfNVPTo-Hashtable($list) {
$h = #{}
$list | ForEach-Object {
$h[$_.Name] = $_.Value
}
return $h
}

$h = #{}
0..($l.count - 1) | ? {$_ -band 1} | % {$h.Add($l[$_-1],$l[$_])}
$h = #{}
0..($l.count - 1) | ? {$_ -band 1} | % {$h.($l[$_-1]) = $l[$_]}
$h = #{}
$i = 0
while ($i -lt $l.count) {$h.Add($l[$i++],$l[$i++])}

How about:
$ht = #{}
$key = "";
("Key",5,"Key2",6) | foreach `
{
if($key)
{
$ht.$key = $_;
$key="";
} else
{$key=$_}
}

function Select-Hashtable {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSObject[]]$InputObject,
[Parameter(Mandatory)]
[string]$Property
)
begin { $hashtable = [Hashtable]::new() }
process { $InputObject | %{ $hashtable[$_."$Property"] = $_} }
end { $hashtable }
}
then (a pointless example):
$items = Get-ChildItem -Path $path | Select-Hashtable -Property Name
$documents = $items['Documents']

Related

Is there a way to speed up dynamic member look-up in ps-objects

In the following code, most time is spent in $v = $proc.$columnName and I am wondering if there
is a way to speed up looking up the objects's members's values.
In the code below, I have chosen $objs to be the result of get-process but in my case, $objs could be an array of any type of objects, thus the need to look up the objects's members dynamically.
$objs= get-process
$columnNames = #()
foreach ($member in ($objs | get-member -memberType property, noteproperty)) {
[string]$name = $member.name
$columnNames += $name
}
[Int64 ] $sum = 0
[string] $columnName = ''
foreach ($obj in $objs) {
foreach ($columnName in $columnNames) {
$v = $obj.$columnName
# $v = $obj.psObject.members.Item($columnName).value
if ($v -eq $null) {
}
elseif ($v -is [System.IntPtr]) {
$sum = $sum + ($v -as [int64] )
}
elseif ($v -is [System.Int64] -or $v -is [System.Int32]) {
$sum = $sum + $v
}
}
}
"sum = $sum"
Perhaps there are more ways to speed this up, but below I have taken out the unnecessary bits:
$objs= Get-Process
$columnNames = ($objs | Get-Member -MemberType property, noteproperty).Name
[Int64 ] $sum = 0
foreach ($obj in $objs) {
foreach ($v in $columnNames) {
if ($obj.$v -as [int64]) { $sum += [int64]$obj.$v }
}
}
"sum = $sum"
This is a hidden property on PSObjects called .PSObject that way too many people don't know about, including myself until a few weeks ago despite spending thousands of hours working with PowerShell. From there you can use .where to SIGNIFICANTLY increase filtering performance.
New Code Block
New-Variable -Force -Name:'Processes' -value:(Get-Process)
New-Variable -Force -Name:'Sum' -Value:([Int64]$Null)
ForEach ($Process in $Processes) {
$Process.PSObject.Properties.Where({
($_.MemberType -in #('Property','NoteProperty')) -and
($_.TypeNameOfValue -in #('System.IntPtr','System.Int64','System.Int32')) -and
(-Not [String]::IsNullOrWhiteSpace($_.value))
}) |ForEach-Object {
$Sum = $Sum + ($_.value -as [Int64])
}
}
"Sum = $Sum"
Comparison Result
Name Value
---- -----
BlockBTime 9.18
SameResult True
BlockATime 1.52
BlockASum 1037197387512388
Difference Block A (New Code) is ~7.66s faster than Block B (Old Code)
BlockBSum 1037197387512388
Validation & Comparison
#Stopwatch
New-Variable -Force -Name:'StopWatch' -Value:(New-Object -TypeName 'System.Diagnostics.Stopwatch')
New-Variable -Force -Name:'Result' -Value:#{
BlockATime = [Int]0
BlockASum = [Int64]0
BlockBTime = [Int]0
BlockBSum = [Int64]0
Difference = $Null
SameResult = $Null
}
New-Variable -Force -Name:'Processes' -value:(Get-Process)
$StopWatch.Restart()
New-Variable -Force -Name:'Sum' -Value:([Int64]$Null)
ForEach ($Process in $Processes) {
$Process.PSObject.Properties.Where({
($_.MemberType -in #('Property','NoteProperty')) -and
($_.TypeNameOfValue -in #('System.IntPtr','System.Int64','System.Int32')) -and
(-Not [String]::IsNullOrWhiteSpace($_.value))
}) |ForEach-Object {
$Sum = $Sum + ($_.value -as [Int64])
}
}
$Result.BlockATime = [math]::Round($StopWatch.Elapsed.TotalSeconds,2)
$Result.BlockASum = $Sum
$objs= $Processes
$StopWatch.Restart()
$columnNames = #()
foreach ($member in ($objs | get-member -memberType property, noteproperty)) {
[string]$name = $member.name
$columnNames += $name
}
[Int64 ] $sum = 0
[string] $columnName = ''
foreach ($obj in $objs) {
foreach ($columnName in $columnNames) {
$v = $obj.$columnName
# $v = $obj.psObject.members.Item($columnName).value
if ($v -eq $null) {
}
elseif ($v -is [System.IntPtr]) {
$sum = $sum + ($v -as [int64] )
}
elseif ($v -is [System.Int64] -or $v -is [System.Int32]) {
$sum = $sum + $v
}
}
}
$Result.BlockbTime = [math]::Round($StopWatch.Elapsed.TotalSeconds,2)
$Result.BlockbSum = $Sum
#Which block is faster?
If ($Result.BlockATime -lt $Result.BlockBTime) {
$Result.Difference = "Block A (New Code) is ~$(($Result.BlockBTime - $Result.BlockATime))s faster than Block B (Old Code)"
} Else {
$Result.Difference = "Block A (New Code) is ~$(($Result.BlockBTime - $Result.BlockATime))s Slower than Block B (Old Code)"
}
#Are the results the same?
If ($Result.BlockASum -eq $Result.BlockBSum) {
$Result.SameResult = $True
}

`Write-Output` in `foreach` collection expression

I'm actually working my way through Bruce Payette's Powershell in Action and run to a code snippet I actually don't understand.
$characterData = #{
'Linus' = #{ age = 8; human = $true}
'Lucy' = #{ age = 8; human = $true}
'Snoopy' = #{ age = 2; human = $true}
}
function Get-Character ($name = '*')
{
foreach ($entry in $characterData.GetEnumerator() | Write-Output)
{
if ($entry.Key -like $name)
{
$properties = #{ 'Name' = $entry.Key } +
$entry.Value
New-Object PSCustomObject -Property $properties
}
}
}
My problem is that I don't understand why Write-Output is used in foreach ($entry in $characterData.GetEnumerator() | Write-Output)? Can this syntax be interpreted as equivalent to $characterData.GetEnumerator() | ForEach-Object { ... ?
Thx

Can you have multiple IN conditions in a PowerShell ForEach loop?

I have the following sample code in a function:
[array]$ARR = $null
foreach ($file in $fileTable.identical)
{
[hashtable]$HT=#{
'FileName' = $file.Name
'AppName' = $file.App
'GroupName' = $file.Group
'Valid' = $true
}
$ARR += $HT
}
foreach ($file in $fileTable.removed)
{
[hashtable]$HT=#{
'FileName' = $file.Name
'AppName' = $file.App
'GroupName' = $file.Group
'Valid' = $false
}
$ARR += $HT
}
foreach ($file in $fileTable.modified)
{
[hashtable]$HT=#{
'FileName' = $file.Name
'AppName' = $file.App
'GroupName' = $file.Group
'Valid' = $false
}
$ARR += $HT
}
return $ARR
+3 more foreach loops for other $fileTable.[properties] where 'Valid' = $false as well.
Instead of having to repeat the block of code multiple times, I want to do something like:
foreach (($file in $fileTable.removed) -and ($file in $fileTable.modified))
{
[hashtable]$HT=#{
'FileName' = $file.Name
'AppName' = $file.App
'GroupName' = $file.Group
'Valid' = $false
}
}
So only variable different in the hashtable will be $value.
$fileTable is a pscustomobject with a few custom properties like identical, modified, added, removed.
I know what I want is not possible in foreach loops but I'm looking for a similar solution to reduce the number of lines of code. Any help would be appreciated :)
Thanks!
Combining your and PetSerAls approaches.
Edit: incorporated #mklement0s hint
$ARR = foreach($Variant in 'identical', 'removed', 'modified'){
$fileTable.$Variant | ForEach-Object{
[PSCustomObject]#{
'FileName' = $_.Name
'AppName' = $_.App
'GroupName' = $_.Group
# 'Valid' = if($Variant -eq 'identical'){$True} else {$False}
'Valid' = $Variant -eq 'identical'
}
}
}

Improving the speed of Get-FileMetaData

I'm currently using the below script taken from scriptingguys.com (all credit to them, I just added the bottom 2 lines.) That takes a directory and pulls the file path and comments field from the meta data of the files. Currently the script take's a little over 1.5 minutes to fully run. Is there anyway to speed this up or use a different method to get this data?
I am using this script at the start of some software I have written and 1.5+ minutes is too long for the script to complete. Any thoughts/comments?
Function Get-FileMetaData
{
Param([string[]]$folder)
foreach($sFolder in $folder)
{
$a = 0
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($sFolder)
foreach ($File in $objFolder.items())
{
$FileMetaData = New-Object PSOBJECT
for ($a ; $a -le 266; $a++)
{
if($objFolder.getDetailsOf($File, $a))
{
$hash += #{$($objFolder.getDetailsOf($objFolder.items, $a)) =
$($objFolder.getDetailsOf($File, $a)) }
$FileMetaData | Add-Member $hash
$hash.clear()
} #end if
} #end for
$a=0
$FileMetaData
} #end foreach $file
} #end foreach $sfolder
} #end Get-FileMetaData
$fileMetaData = Get-FileMetaData -folder "C:\Pics" | select 'Name', 'Path', 'Comments' | Sort-Object 'Name'
$fileMetaData | select 'Name', 'Path', 'Comments' | Export-CSV "C:\SCRIPTS\TestDirectory.txt" -encoding Utf8 -NoTypeInformation
Solved by wOxxOm, thanks for your help! Running the below and now working.
Function Get-FileMetaData(
[string[]]$folders,
[string[]]$properties
) {
$shellApp = New-Object -ComObject Shell.Application
$supportsOrdered = $PSVersionTable.PSVersion.Major -ge 3
$hash = if ($supportsOrdered) { [ordered]#{} } else { #{} }
# walk the folders and get the properties by index found above
$folders | ForEach {
$shellFolder = $shellApp.namespace($_)
# get all headers and find their indexes
$allProps = #{}
foreach ($index in 0..266) {
$allProps[$shellFolder.getDetailsOf($shellFolder.items, $index)] = $index
}
$shellFolder.items() | ForEach {
$file = $_
$hash.Clear()
foreach ($prop in $properties) {
if (($index = $allProps[$prop]) -ne $null) {
if ($value = $shellFolder.getDetailsOf($file, $index)) {
$hash[$prop] = $value
}
}
}
if ($supportsOrdered) {
[PSCustomObject]$hash
} else {
Select $properties -inputObject (
New-Object PSObject -Property $hash
)
}
}
}
}
Get-FileMetaData -folders 'C:\PICS' -properties Name, Path, Comments | Sort-Object Name |
select Name, Path, Comments | Export-Csv 'C:\Scripts\test.txt' -encoding UTF8 -NoTypeInformation
getDetailsOf is slow, and your code needlessly invokes it 267 times for each file when you only need it for 3 properties.
Collect the property names just once at the start of the function, don't do it on every file
Add-Member is slow. Don't invoke it on every property. Collect all found properties in a hashtable and pass it once to Add-Member or, since you create an empty object, directly to New-Object. To enforce the order of properties use Select-Object in PowerShell 2. Note, PowerShell 3.0 and newer support [ordered] and [PSCustomObject] typecast (see the code below).
Use pipelining instead of foreach statements so that the results appear immediately
Files are already sorted by name, at least on NTFS file system in Windows, so no need to sort.
Function Get-FileMetaData(
[string[]]$folders,
[string[]]$properties
) {
$shellApp = New-Object -ComObject Shell.Application
# get all headers and find their indexes
$shellFolder = $shellApp.namespace($folders[0])
$allProps = #{}
foreach ($index in 0..266) {
$allProps[$shellFolder.getDetailsOf($shellFolder.items, $index)] = $index
}
$supportsOrdered = $PSVersionTable.PSVersion.Major -ge 3
$hash = if ($supportsOrdered) { [ordered]#{} } else { #{} }
# walk the folders and get the properties by index found above
$folders | ForEach {
$shellFolder = $shellApp.namespace($_)
$shellFolder.items() | ForEach {
$file = $_
$hash.Clear()
foreach ($prop in $properties) {
if (($index = $allProps[$prop]) -ne $null) {
$hash[$prop] = $shellFolder.getDetailsOf($file, $index)
}
}
if ($supportsOrdered) {
[PSCustomObject]$hash
} else {
Select $properties -inputObject (
New-Object PSObject -Property $hash
)
}
}
}
}
Usage example 1:
Get-FileMetaData -folders 'r:\folder1', 'r:\folder2' -properties Name, Path, Comments
Usage example 2:
Get-FileMetaData -folders 'r:\folder1', 'r:\folder2' -properties Name, Path, Comments |
Export-Csv r:\results.csv -encoding UTF8 -NoTypeInformation
Usage example 3 gets all properties, which is slow:
Get-FileMetaData -folders 'r:\folder1', 'r:\folder2'

PSCustomObject to Hashtable

What is the easiest way to convert a PSCustomObject to a Hashtable? It displays just like one with the splat operator, curly braces and what appear to be key value pairs. When I try to cast it to [Hashtable] it doesn't work. I also tried .toString() and the assigned variable says its a string but displays nothing - any ideas?
Shouldn't be too hard. Something like this should do the trick:
# Create a PSCustomObject (ironically using a hashtable)
$ht1 = #{ A = 'a'; B = 'b'; DateTime = Get-Date }
$theObject = new-object psobject -Property $ht1
# Convert the PSCustomObject back to a hashtable
$ht2 = #{}
$theObject.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }
Keith already gave you the answer, this is just another way of doing the same with a one-liner:
$psobject.psobject.properties | foreach -begin {$h=#{}} -process {$h."$($_.Name)" = $_.Value} -end {$h}
Here's a version that works with nested hashtables / arrays as well (which is useful if you're trying to do this with DSC ConfigurationData):
function ConvertPSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = #(
foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
)
Write-Output -NoEnumerate $collection
}
elseif ($InputObject -is [psobject])
{
$hash = #{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
}
$hash
}
else
{
$InputObject
}
}
}
My extremely lazy approach, enabled by a new feature in PowerShell 6:
$myhashtable = $mypscustomobject | ConvertTo-Json | ConvertFrom-Json -AsHashTable
This works for PSCustomObjects created by ConvertFrom_Json.
Function ConvertConvertFrom-JsonPSCustomObjectToHash($obj)
{
$hash = #{}
$obj | Get-Member -MemberType Properties | SELECT -exp "Name" | % {
$hash[$_] = ($obj | SELECT -exp $_)
}
$hash
}
Disclaimer: I barely understand PowerShell so this is probably not as clean as it could be. But it works (for one level only).
My code:
function PSCustomObjectConvertToHashtable() {
param(
[Parameter(ValueFromPipeline)]
$object
)
if ( $object -eq $null ) { return $null }
if ( $object -is [psobject] ) {
$result = #{}
$items = $object | Get-Member -MemberType NoteProperty
foreach( $item in $items ) {
$key = $item.Name
$value = PSCustomObjectConvertToHashtable -object $object.$key
$result.Add($key, $value)
}
return $result
} elseif ($object -is [array]) {
$result = [object[]]::new($object.Count)
for ($i = 0; $i -lt $object.Count; $i++) {
$result[$i] = (PSCustomObjectConvertToHashtable -object $object[$i])
}
return ,$result
} else {
return $object
}
}
For simple [PSCustomObject] to [Hashtable] conversion Keith's Answer works best.
However if you need more options you can use
function ConvertTo-Hashtable {
<#
.Synopsis
Converts an object to a hashtable
.DESCRIPTION
PowerShell v4 seems to have trouble casting some objects to Hashtable.
This function is a workaround to convert PS Objects to [Hashtable]
.LINK
https://github.com/alainQtec/.files/blob/main/src/scripts/Converters/ConvertTo-Hashtable.ps1
.NOTES
Base ref: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/turning-objects-into-hash-tables-2
#>
PARAM(
# The object to convert to a hashtable
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
$InputObject,
# Forces the values to be strings and converts them by running them through Out-String
[switch]$AsString,
# If set, empty properties are Included
[switch]$AllowNulls,
# Make each hashtable to have it's own set of properties, otherwise,
# (default) each InputObject is normalized to the properties on the first object in the pipeline
[switch]$DontNormalize
)
BEGIN {
$headers = #()
}
PROCESS {
if (!$headers -or $DontNormalize) {
$headers = $InputObject | Get-Member -type Properties | Select-Object -expand name
}
$OutputHash = #{}
if ($AsString) {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col | Out-String -Width 9999 | ForEach-Object { $_.Trim() }
}
}
} else {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col
}
}
}
}
END {
return $OutputHash
}
}
Maybe this is overkill but I hope it Helps
Today, the "easiest way" to convert PSCustomObject to Hashtable would be so:
$custom_obj | ConvertTo-HashtableFromPsCustomObject
OR
[hashtable]$custom_obj
Conversely, you can convert a Hashtable to PSCustomObject using:
[PSCustomObject]$hash_table
Only snag is, these nifty options may not be available in older versions of PS