Apologies I am not clued up with PowerShell just yet.
I have two hash tables, one containing the label name and the ID. The other contains a folder path and the label name.
I am trying to extract the label ID e.g 'e58e1e18-da7e-493c-9961-a7deff6dc7f7' to use in a for each statement.
I am looping through the folder's hash table, and need to be able to match the folder key with the label value, if there is a match, extract that value from the labels hash.
Example: 'C:\OTD\Cat' would have the id of 'e58e1e18-da7e-493c-9961-a7deff6dc7f7' as Label1 matches in both hash tables.
$labels = #{
'Label1' = 'e58e1e18-da7e-493c-9961-a7deff6dc7f7'
'Label2' = '6514c00d-001e-4041-9207-f31aebc5e13a'
'Label3' = 'e58e1e18-da7e-493c-9961-a7deff6dc7f7'
}
$folders = #{
'C:\OTD\Cat' = 'Label1'
'C:\OTD\Dog' = 'Label2'
'C:\OTD\Frog' = 'Label3'
}
# Match key/value in a hashtable and return the labels id value
$folders.GetEnumerator() | ForEach-Object{
If($_.value = matches key name in labels hash, grab that label ID to use here)
}
Thank you for looking at my question!
Use the corresponding values as index keys for the second hashtable:
$folders.Keys | % {
# Grab the label
$folderLabel = $folders[$_]
# Use label as key to resolve label ID
$labelId = $labels[$folderLabel]
}
You can nest these as crazily as you want (although it might hurt readability of your code):
$labelIDs = $folders.Keys | % {
$labels[$folders[$_]]
}
As a side note, I recommend iterating through dictionaries as lists of key-value pairs by explicitly calling GetEnumerator() instead of iterating over Keys:
$folders.GetEnumerator() |%{
$folderPath = $_.Key
$folderLabel = $_.Value
$labelID = $labels[$_.Value]
}
Related
I have a nested hashtable with an array and I want to loop through the contents of another array and add that to the nested hashtable. I'm trying to build a Slack message block.
Here's the nested hashtable I want to add to:
$msgdata = #{
blocks = #(
#{
type = 'section'
text = #{
type = 'mrkdwn'
text = '*Services Being Used This Month*'
}
}
#{
type = 'divider'
}
)
}
$rows = [ ['azure vm', 'centralus'], ['azure sql', 'eastus'], ['azure functions', 'centralus'], ['azure monitor', 'eastus2'] ]
$serviceitems = #()
foreach ($r in $rows) {
$servicetext = "*{0}* - {1}" -f $r[1], $r[0]
$serviceitems += #{'type'='section'}
$serviceitems += #{'text'= ''}
$serviceitems.text.Add('type'='mrkdwn')
$serviceitems.text.Add('text'=$servicetext)
$serviceitems += #{'type'='divider'}
}
$msgdata.blocks += $serviceitems
The code is partially working. The hashtables #{'type'='section'} and #{'type'='divider'} get added successfully. Trying to add the nested hashtable of #{'text' = #{ 'type'='mrkdwn' 'text'=$servicetext }} fails with this error:
Line |
24 | $serviceitems.text.Add('type'='mrkdwn')
| ~
| Missing ')' in method call.
I tried looking through various Powershell posts and couldn't find one that applies to my specific situation. I'm brand new to using hashtables in Powershell.
Complementing mklement0's helpful answer, which solves the problem with your existing code, I suggest the following refactoring, using inline hashtables:
$serviceitems = foreach ($r in $rows) {
#{
type = 'section'
text = #{
type = 'mrkdwn'
text = "*{0}* - {1}" -f $r[1], $r[0]
}
}
#{
type = 'divider'
}
}
$msgdata.blocks += $serviceitems
This looks much cleaner and thus easier to maintain in my opinion.
Explanations:
$serviceitems = foreach ... captures all output (to the success stream) of the foreach loop in variable $serviceitems. PowerShell automatically creates an array from the output, which is more efficient than manually adding to an array using the += operator. Using += PowerShell has to recreate an array of the new size for each addition, because arrays are actually of fixed size. When PowerShell automatically creates an array, it uses a more efficient data structure internally.
By writing out an inline hash table, without assigning it to a variable, PowerShell implicitly outputs the data, in effect adding it to the $serviceitems array.
We output two hash tables per loop iteration, so PowerShells adds two array elements to $serviceitems per loop iteration.
Note:
This answer addresses your question as asked, specifically its syntax problems.
For a superior solution that bypasses the original problems in favor of streamlined code, see zett42's helpful answer.
$serviceitems.text.Add('type'='mrkdwn') causes a syntax error.
Generally speaking, IF $serviceitems.text referred to a hashtable (dictionary), you need either:
method syntax with distinct, ,-separated arguments:
$serviceitems.text.Add('type', 'mrkdwn')
or index syntax (which would quietly overwrite an existing entry, if present):
$serviceitems.text['type'] = 'mrkdwn'
PowerShell even lets you access hashtable (dictionary) entries with member-access syntax (dot notation):
$serviceitems.text.type = 'mrkdwn'
In your specific case, additional considerations come into play:
You're accessing a hashtable via an array, instead of directly.
The text entry you're trying to target isn't originally a nested hashtable, so you cannot call .Add() on it; instead, you must assign a new hashtable to it.
Therefore:
# Define an empty array
$serviceItems = #()
# "Extend" the array by adding a hashtable.
# Note: Except with small arrays, growing them with +=
# should be avoided, because a *new* array must be allocated
# every time.
$serviceItems += #{ text = '' }
# Refer to the hashtable via the array's last element (-1),
# and assign a nested hashtable to it.
$serviceItems[-1].text = #{ 'type' = 'mrkdwn' }
# Output the result.
$serviceItems
Some people like to use long folder names and deep folder structures. This is especially painful when you use OneDrive/SharePoint Online and sync long paths with Windows.
Hence I am looking for an approach to shorten those paths and keep the meaning, especially when archiving files and folders.
Basically trying to transform:
VeryLongPath\AnotherLongFolderPath\AndAnotherOne\VeryLongFilename.xlsx
Into:
1\2\3\4.xlsx
And generating the following index file:
1 VeryLongPath
2 AnotherLongFolderPath
3 AndAnotherOne
4 VeryLongFilename.xlsx
You could create a helper class to track the individual labels:
class PathLabelIndexer
{
# This will hold the label translations, [string] -> [int]
[hashtable]$_terms = #{}
# This will keep track of how many distinct labels we've encountered
[int]$_length = 0
# Transforms a path into index values
[string] Transform([string]$Path){
return #($Path.Split('\') |%{
if($this._terms.ContainsKey($_)){
# If we already have a translation for $_, use that!
$this._terms[$_]
}
else {
# No existing translation found, add a new one
($this._terms[$_] = $this._length++)
}
}) -join '\'
}
# Produces the index needed to translate them back to labels
[string[]] GetIndex(){
# Since $_length starts at 0, a sorted array of the index values will give us the correct mapping
return [string[]]#($this._terms.GetEnumerator() |Sort Value |ForEach-Object Key)
}
}
Now you can do:
PS ~> $indexer = [PathLabelIndexer]::new()
PS ~> $indexer.Transform('VeryLongPath\AnotherLongFolderPath\AndAnotherOne\VeryLongFilename.xlsx')
0\1\2\3
To get the reverse, simply produce the resulting index and look up the individual labels again:
PS ~> $index = $indexer.GetIndex()
PS ~> $index['0\1\2\3'.Split('\')] -join '\'
VeryLongPath\AnotherLongFolderPath\AndAnotherOne\VeryLongFilename.xlsx
I am trying to sort an hashtable in Powershell with the following structure :
https://i.stack.imgur.com/bjSX6.png
Each value of the hashtable is an array containing multiple elements.
I would like to sort the hashtable based on the value of second element of each array, is it possible ?
Create an ordered dictionary, then enumerate all the key-value pairs in the existing hashtable, sort them by the desired value and then add them to your new ordered dictionary:
# Create ordered dictionary
$ordered = [ordered]#{}
# Sort KVPs by value at index 1 (that's the second element) of the array
$hashtable.GetEnumerator() |Sort-Object { $_.Value[1] } |ForEach-Object {
# Copy the KVP to the ordered dictionary in... order
$ordered[$_.Key] = $_.Value
}
$ordered now contains the same entries as $hashtable, but sorted according to your criteria
$hash = #{}
$hash.x1 = 1
$hash.x2 = 2
Can I display the hash table's values like this?
$hash.[x1,x2]
$hash.[x1,x2] (with dot and bare words) will not work, but you can use the index operator directly on a hashtable with an array of the key names (as strings). Like this:
$hash['x1','x2']
or like this:
$keys = 'x1', 'x2'
$hash[$keys]
Let's say I have hash like this:
$NATO = #{
"A" = "Alpha";
"B" = "Bravo";
"C" = "Charlie";
# ...
"Y" = "Yankee";
"Z" = "Zulu";
}
I could get all values of hash using $NATO.keys key collection:
$NATO[$NATO.keys] # gives me all values
But if I want to pass subset of keys, to get subset of values, I can't do that:
$NATO["BUNYK".ToCharArray()] # gives me nothing, but I want
# Bravo, Uniform, November, Yankee, Kilo
Do you know how this could be done?
You could also pipe the string array into a ForEach-Object loop:
[string[]]'BUNYK'.ToCharArray() | % { $NATO[$_] }
The hash table stores the keys as objects, not necessarily simple strings. The keys in your example are strings, but you're trying to access them as [char] objects. The easiest way to handle your example is to cast the [char[]] array to a [string[]] array:
$NATO[([string[]] "BUNYK".ToCharArray())]