Create array from keys of an array of hashtables - powershell

I have an array where each element is a hashtable. Each hashtable has the same keys. Here it is:
#(
#{"MarketShortCode"="abc";"MarketName"="Market1" },
#{"MarketShortCode"="def";"MarketName"="Market2" },
#{"MarketShortCode"="ghi";"MarketName"="Market3" },
#{"MarketShortCode"="jkl";"MarketName"="Market4" }
)
I want a nice elegant way to extract an array containing just the value of the MarketShortCode key. So I want this:
#("abc","def","ghi","jkl")
This is the best I've come up with:
$arr = #()
$hash | %{$arr += $_.MarketShortCode}
$arr
But I don't like that cos its three lines of code. Feels like something I should be able to do in one line of code. Is there a way?

Just do this:
$hash | %{$_.MarketShortCode}
That is, return the value from the block instead of adding it to an array and then dereferencing the array.
If you're using PowerShell 3+, there's even shorter way:
$hash.MarketShortCode
PowerShell automatically applies dot . to each item in an array when it's used this way, but it wasn't supported until v3.

Related

Running a for loop in Powershell

I am new o scripting in powershell and am from a Python background. I want to know if I'm doing this right.
I created this array and want to extract each item one by one
$M365_E3_Grps = ("O365-CHN-DomainUser,O365-Vendor-Exchange-User")
ForEach ($Indiv_Grp in $M365_E3_Grps) {
ForEach ($Indiv_Grp in $M365_E3_Grps) {
`$ADGroup = $Indiv_Grp$ADGroup = $Indiv_Grp`
I want to know if we can extract vals with a for loop like this and assign it to a variable like this.
Construct of your array
Your array is not quite correct and will be populated as a string. To create a string array you will need to quote each item in comma separated list. The parentheses are also not required.
$M365_E3_Grps = "O365-CHN-DomainUser","O365-Vendor-Exchange-User"
Your foreach keyword syntax is however correct, even if the formatting in your question was slightly off.
foreach ($Indiv_Grp in $M365_E3_Grps) {
# Assigning $Indiv_Grp to $ADGroup here is kind of redundant since
# the value is already assinged to $Indiv_Grp
$Indiv_Grp
}

Getting error when adding nested hashtable to array in Powershell

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

PowerShell create array failed in a loop

Thought I have read enough examples here and elsewhere. Still I fail creating arrays in Power Shell.
With that code I hoped to create slices of pair values from an array.
$values = #('hello','world','bonjour','moon','ola','mars')
function slice_array {
param (
[String[]]$Items
)
[int16] $size = 2
$pair = [string[]]::new($size) # size is 2
$returns = [System.Collections.ArrayList]#()
[int16] $_i = 0
foreach($item in $Items){
$pair[$_i] = $Item
$_i++;
if($_i -gt $size - 1){
$_i = 0
[void]$returns.Add($pair)
}
}
return $returns
}
slice_array($values)
the output is
ola
mars
ola
mars
ola
mars
I would hope for
'hello','world'
'bonjour','moon'
'ola','mars'
Is possible to slice that array to an array of arrays with length 2 ?
Any explenation why it doesn't work as expected ?
How should the code be changed ?
Thanks for any hint to properly understand Arrays in PowerShell !
Here's a PowerShell-idiomatic solution (the fix required for your code is in the bottom section):
The function is named Get-Slices to adhere to PowerShell's verb-noun naming convention (see the docs for more information).
Note: Often, the singular form of the noun is used, e.g. Get-Item rather than Get-Items, given that you situationally may get one or multiple output values; however, since the express purpose here is to slice a single object into multiple parts, I've chosen the plural.
The slice size (count of elements per slice) is passed as a parameter.
The function uses .., the range operator, to extract a single slice from an array.
It uses PowerShell's implicit output behavior (no need for return, no need to build up a list of return values explicitly; see this answer for more information).
It shows how to output an array as a whole from a function, which requires wrapping it in an auxiliary single-element array using the unary form of ,, the array constructor operator. Without this auxiliary array, the array's elements would be output individually to the pipeline (which is also used for function / script output; see this answer for more information.
# Note: For brevity, argument validation, pipeline support, error handling, ...
# have been omitted.
function Get-Slices {
param (
[String[]] $Items
,
[int] $Size # The slice size (element count)
)
$sliceCount = [Math]::Ceiling($Items.Count / $Size)
if ($sliceCount -le 1) {
# array is empty or as large as or smaller than a slice? ->
# wrap it *twice* to ensure that the output is *always* an
# *array of arrays*, in this case containing just *one* element
# containing the original array.
,, $Items
}
else {
foreach ($offset in 0..($sliceCount-1)) {
, $Items[($offset * $Size)..(($offset+1) * $Size - 1)] # output this slice
}
}
}
To slice an array into pairs and collect the output in an array of arrays (jagged array):
$arrayOfPairs =
Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 2
Note:
Shell-like syntax is required when you call functions (commands in general) in PowerShell: arguments are whitespace-separated and not enclosed in (...) (see this answer for more information)
Since a function's declared parameters are positional by default, naming the arguments as I've done above (-Item ..., -Size ...) isn't strictly necessary, but helps readability.
Two sample calls:
"`n-- Get pairs (slice count 2):"
Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 2 |
ForEach-Object { $_ -join ', ' }
"`n-- Get slices of 3:"
Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 3 |
ForEach-Object { $_ -join ', ' }
The above yields:
-- Get pairs (slice count 2):
hello, world
bonjour, moon
ola, mars
-- Get slices of 3:
hello, world, bonjour
moon, ola, mars
As for what you tried:
The only problem with your code was that you kept reusing the very same auxiliary array for collecting a pair of elements, so that subsequent iterations replaced the elements of the previous ones, so that, in the end, your array list contained multiple references to the same pair array, reflecting the last pair only.
This behavior occurs, because arrays are instance of reference types rather than value types - see this answer for background information.
The simplest solution is to add a (shallow) clone of your $pair array to your list, which ensures that each list entry is a distinct array:
[void]$returns.Add($pair.Clone())
Why you got 3 equal pairs instead of different pairs:
.Net (powershell based on it) is object-oriented language and it has consept of reference types and value types. Almost all types are reference types.
What happens in your code:
You create $pair = [string[]] object. $pair variable actually stores memory address of (reference to) [string[]] object, because arrays are reference types
You fill $pair array with values
You add (!) $pair to $returns. Remember that $pair is reference to memory block. And when you add it to $returns, it adds memory address of [string[]] you wrote values to.
You repeat step2: You fill $pair array with different values, but address of this array in memory keeps the same. Doing this you actually replace values from step2 with new values in the same $pair object.
= // = step3
= // = step4
= // = step3
As a result: in $returns there are three same memory addresses: [[reference to $pair], [reference to $pair], [reference to $pair]]. And $pair values were overwritten by code with last pair values.
On output it works like this:
Powershell looks at $results which is array.
Powershell looks to $results[0] which reference to $pair
Powershell outputs reference to $pair[0]
Powershell outputs reference to $pair[1]
Powershell looks to $results[1] which reference to $pair
Powershell outputs reference to $pair[0]
Powershell outputs reference to $pair[1]
Powershell looks to $results[1] which reference to $pair
Powershell outputs reference to $pair[0]
Powershell outputs reference to $pair[1]
So you see, you triple output the object from the same memory address. You overwritten it 3 times in slice_array and now it stores only last pair values.
To fix it in your code, you should create a new $pair in memory: add $pair = [string[]]::new($size) just after $returns.Add($pair)

How to create KeyCollection from array in Powershell?

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())]

Changing each entry in a list returned from powershell

I have a list of paths (filtered by special criteria). I want to mutate each entry in this list, but can't find a way (I think it's immutable). What's the best way of going about this?
Thanks
I guess you have some collection, not only list (probably array).
PS> $myList = 'first','second','third'
You can mutate the collection by indexing or just by creating new array like this:
PS> $myList[1] = '2nd'
#or
PS> $myList | % { $_.Substring(0,2) }
fi
se
th