Powershell - Use multidimention arrays for storing data - Need suggestion - powershell

I'm googling since a while, but I didn't find a solution to my problem.
I have to say I'm newbie in Powershell.
I would like to create the following array
$a = (A,B,C,D) where
A = 1 string (always)
B = 1 string (always)
C = undefined number of strings. I need to be able to add elements dynamically
D = undefined number of strings. I need to be able to add elements dynamically (same number as C)
Is this possible?
Example of 2 elements of the array
("WSTM0123456", "192.168.10.155",("WSTM8765421","WSTM9856454","WSTM1289765"),("192.36.36.36", "187.25.25.25","192.69.89.65"))
("WLDN1251254", "156.25.36.54", ("WLDN1234512", "WLDN9865323"), ("187.154.12.12","163.136.25.98"))
I don't know a priori how many elements will be in C and D and I'll have to append strings in position C and D with a for cycle.
Scope: group many strings (C & D) under the same string (A/B) which are in common.
Any help would be appreciated
Thanks,
Marco

You can do this, but it's probably quite painful as dealing with arrays is sometimes cumbersome in PowerShell due to lots of implicit flattening.
I'd suggest creating a custom type for this. Then you can also give the individual parts useful names (I don't know the purpose of what you're doing here, so I'm making up names here. Feel free to change):
$properties = #{
Name = 'WSTM0123456';
IP = [ipaddress]'192.168.10.155';
ListOfNames = #("WSTM8765421","WSTM9856454","WSTM1289765");
ListOfIPs = [ipaddress[]]#("192.36.36.36", "187.25.25.25","192.69.89.65")
}
$foo = New-Object PSObject -Property #properties
Then you can simply append new items like so:
$foo.ListOfNames += 'AnotherName'

I think this is pretty much the same idea. Use a hash table, and make two of the elements arrays. This is how you would create the arrays "on the fly" at runtime, without knowing what any of the contents were going to be in advance, taking $x and putting any item that starts with "t" in "C" , and everything else in "D":
$a = #{A = "Some string";B = "Some other string"}
$x = "one","two","three","four","five"
$x |% {
if ($_ -match "^t"){$a["C"] += #($_)}
else {$a["D"] += #($_)}
}
$a.a
Some string
$a.b
Some other string
$a.c
two
three
$a.d
one
four
five
$obj = new-object psobject -property $a

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

Why would I use an ordered hash in PowerShell?

I'm reading a tutorial and learned that PowerShell supports ordered hashes. When would I use that feature?
Sample code of what I'm talking about:
$hash = [ordered]#{ ID = 1; Shape = "Square"; Color = "Blue"}
Let me complement Maximilian Burszley's helpful answer with a broader perspective:
tl;dr
Most of the time you want [ordered] #{ ... } ([System.Collections.Specialized.OrderedDictionary]) (PSv3+):
It provides enumeration of the entries in the order in which they were defined (also reflected in the .Keys and .Values collection properties).
It also allows accessing entries by index, like an array.
Typically, you can use [ordered] #{ ... } interchangeably with #{ ... }, the regular hashtable, [hashtable] a.k.a [System.Collections.Hashtable], because both types implement the [IDictionary] interface, which is how parameters that accept hash tables are typically typed.
The performance penalty you pay for using [ordered] is negligible.
Some background:
For technical reasons, the most efficient implementation of a hashtable (hash table) is to let the ordering of entries be the outcome of implementation details, without guaranteeing any particular order to the caller.
This is fine for use cases where all you do is to perform isolated lookups by key, where the ordering among keys (entries) is irrelevant.
However, often you do care about the ordering of entries:
in the simplest case, for display purposes; there is something disconcerting about seeing the definition order jumbled; e.g.:
#{ one = 1; two = 2; three = 3 }
Name Value
---- -----
one 1
three 3 # !!
two 2
more importantly, the enumeration of entries may need to be predictable for further programmatic processing; e.g. (note: strictly speaking, property order doesn't matter in JSON, but it is again important for the human observer):
# No guaranteed property order.
PS> #{ one = 1; two = 2; three = 3 } | ConvertTo-Json
{
"one": 1,
"three": 3, # !!
"two": 2
}
# Guaranteed property order.
PS> [ordered] #{ one = 1; two = 2; three = 3 } | ConvertTo-Json
{
"one": 1,
"two": 2,
"three": 3
}
It's unfortunate that PowerShell's hashtable-literal syntax, #{ ... }, doesn't default to [ordered][1], but it is too late to change that.
There is one context in which [ordered] is implied, however: if you cast a hashtable literal to [pscustomobject] in order to create a custom object:
[pscustomobject] #{ ... } is syntactic sugar for [pscustomobject] [ordered] #{ ... }; that is, the resulting custom object's properties are ordered based on the entry order in the hashtable literal; e.g.:
PS> [pscustomobject] #{ one = 1; two = 2; three = 3 }
one two three # order preserved!
--- --- -----
1 2 3
Note, however, that this only works exactly as shown above: if the cast applied directly to a hashtable literal; if you use a variable to store the hashtable in first or if you even just enclose the literal in (...) the ordering is lost:
PS> $ht = #{ one = 1; two = 2; three = 3 }; [pscustomobject] $ht
one three two # !! Order not preserved.
--- ----- ---
1 3 2
PS> [pscustomobject] (#{ one = 1; two = 2; three = 3 }) # Note the (...)
one three two # !! Order not preserved.
--- ----- ---
1 3 2
Therefore, if you construct a hashtable iteratively first and then cast it to [pscustomobject], you must start with an [ordered] hashtable to get predictable ordering of properties; this technique is useful, because it's easier to create hashtable entries than it is to add properties to a custom object; e.g.:
$oht = [ordered] #{} # Start with an empty *ordered* hashtable
# Add entries iteratively.
$i = 0
foreach ($name in 'one', 'two', 'three') {
$oht[$name] = ++$i
}
[pscustomobject] $oht # Convert the ordered hashtable to a custom object
Finally, note that [ordered] can only be applied to hashtable literal; you cannot use it to convert a preexisting regular hashtable to an ordered one (which wouldn't make any sense anyway, because you have no defined order to begin with):
PS> $ht = #{ one = 1; two = 2; three = 3 }; [ordered] $ht # !! Error
...
The ordered attribute can be specified only on a hash literal node.
...
On a side note: Neither ordered nor regular hashtables enumerate their entries when sent through the pipeline; they are sent as a whole.
To enumerate the entries, use the .GetEnumerator() method; e.g.:
#{ one = 1; two = 2; three = 3 }.GetEnumerator() | ForEach-Object { $_.Value }
1
3 # !!
2
As for the performance impact of using [ordered]:
As noted, it is negligible; here are some sample timings, averaged across 10,000 runs, using Time-Command:
Time-Command -Count 10,000 { $ht=#{one=1;two=2;three=3;four=4;five=5;six=6;seven=7;eight=8;nine=9}; foreach($k in $ht.Keys){$ht.$k} },
{ $ht=[ordered] #{one=1;two=2;three=3;four=4;five=5;six=6;seven=7;eight=8;nine=9}; foreach($k in $ht.Keys){$ht.$k} }
Sample timings (Windows PowerShell 5.1 on Windows 10, single-core VM):
Command TimeSpan Factor
------- -------- ------
$ht=#{one=1;two=2;th... 00:00:00.0000501 1.00
$ht=[ordered] #{one=... 00:00:00.0000527 1.05
That is, [ordered] amounted to a mere 5% slowdown.
[1] Maximilian Burszley points out one tricky aspect specific to [ordered] hashtables:
With numeric keys, distinguishing between a key and an index can become tricky; to force interpretation of a number as a key, cast it to [object] or use dot notation (., property-access syntax) instead of index syntax ([...]):
# Ordered hashtable with numeric keys.
PS> $oht = [ordered] #{ 1 = 'one'; 2 = 'two' }
PS> $oht[1] # interpreted as *index* -> 2nd entry
two
PS> $oht[[object] 1] # interpreted as *key* -> 1st entry.
one
PS> $oht.1 # dot notation - interpreted as *key* -> 1st entry.
one
That said, numeric keys aren't common, and to me the benefit of defaulting to predictable enumeration outweighs this minor problem.
The .NET type underlying [ordered], System.Collections.Specialized.OrderedDictionary, has been available since v1, so PowerShell could have chosen it as the default implementation for #{ ... } from the get-go, even in PowerShell v1.
Given PowerShell's commitment to backward compatibility, changing the default is no longer an option, however, as that could break existing code, namely in the following ways:
There may be existing code that checks untyped arguments for whether they're a hashtable with -is [hashtable], which would no longer work with an ordered hashtable (however, checking with -is [System.Collections.IDictionary] would work).
There may be existing code that relies on hashtables with numeric keys, in which case the index-syntax lookup behavior would change (see example above).
The reason for an ordered dictionary is for display / typecast purposes. For example, if you want to cast your hashtable to a PSCustomObject and you want your keys to be in the order you enter them, you use ordered.
The use case here is when you use Export-Csv, the headers are in the right order. This is just one example I could think of off the top of my head. By design, the hashtable type doesn't care about the order you enter keys/values and will be different each time you display it to the success stream.
An additional use-case for the ordered dictionary: you can treat your hashtable as an array and use numerical accessors to find items, such as $myOrderedHash[-1] will grab the last item added to the dictionary.

finding index of key in an ordered dictionary in powershell

I am having a little bit of trouble with hashtables/dictionaries in powershell. The most recent roadblock is the ability to find the index of a key in an ordered dictionary.
I am looking for a solution that isn't simply iterating through the object.
(I already know how to do that)
Consider the following example:
$dictionary = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
If this were a normal array I'd be able to look up the index of an entry by using IndexOf().
[array]::IndexOf($dictionary,'c').
That would return 2 under normal circumstances.
If I try that with an ordered dictionary, though, I get -1.
Any solutions?
Edit:
In case anyone reading over this is wondering what I'm talking about. What I was trying to use this for was to create an object to normalize property entries in a way that also has a numerical order.
I was trying to use this for the status of a process, for example:
$_processState = [Ordered]#{
'error' = 'error'
'none' = 'none'
'started' = 'started'
'paused' = 'paused'
'cleanup' = 'cleanup'
'complete' = 'complete'
}
If you were able to easily do this, the above object would give $_processState.error an index value of 0 and ascend through each entry, finally giving $_processState.complete an index value of 5. Then if you compared two properties, by "index value", you could see which one is further along by simple operators. For instance:
$thisObject.Status = $_processState.complete
If ($thisObject.Status -ge $_processState.cleanup) {Write-Host 'All done!'}
PS > All done!
^^that doesn't work as is, but that's the idea. It's what I was aiming for. Or maybe to find something like $_processState.complete.IndexNumber()
Having an object like this also lets you assign values by the index name, itself, while standardizing the options...
$thisObject.Status = $_processState.paused
$thisObject.Status
PS > paused
Not really sure this was the best approach at the time or if it still is the best approach with all the custom class options there are available in PS v5.
It can be simpler
It may not be any more efficient than the answer from Frode F., but perhaps more concise (inline) would be simply putting the hash table's keys collection in a sub expression ($()) then calling indexOf on the result.
For your hash table...
Your particular expression would be simply:
$($dictionary.keys).indexOf('c')
...which gives the value 2 as you expected. This also works just as well on a regular hashtable... unless the hashtable is modified in pretty much any way, of course... so it's probably not very useful in that case.
In other words
Using this hash table (which also shows many of the ways to encode 4...):
$hashtable = [ordered]#{
sample = 'hash table'
0 = 'hello'
1 = 'goodbye'
[char]'4' = 'the ansi character 4 (code 52)'
[char]4 = 'the ansi character code 4'
[int]4 = 'the integer 4'
'4' = 'a string containing only the character 4'
5 = "nothing of importance"
}
would yield the following expression/results pairs:
# Expression Result
#------------------------------------- -------------
$($hashtable.keys).indexof('5') -1
$($hashtable.keys).indexof(5) 7
$($hashtable.keys).indexof('4') 6
$($hashtable.keys).indexof([char]4) 4
$($hashtable.keys).indexof([int]4) 5
$($hashtable.keys).indexof([char]'4') 3
$($hashtable.keys).indexof([int][char]'4') -1
$($hashtable.keys).indexof('sample') 0
by the way:
[int][char]'4' equals [int]52
[char]'4' has a "value" (magnitude?) of 52, but is a character, so it's used as such
...gotta love the typing system, which, while flexible, can get really really bad at times, if you're not careful.
Dictionaries uses keys and not indexes. OrderedDictionary combines a hashtable and ArrayList to give you order/index-support in a dictionary, however it's still a dictionary (key-based) collection.
If you need to get the index of an object in a OrderedDictionary (or a hasthable) you need to use foreach-loop and a counter. Example (should be created as a function):
$hashTable = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
$i = 0
foreach($key in $hashTable.Keys) {
if($key -eq "c") { $i; break }
else { $i++ }
}
That's how it works internaly too. You can verify this by reading the source code for OrderedDictionary's IndexOfKey method in .NET Reference Source
For the initial problem I was attempting to solve, a comparable process state, you can now use Enumerations starting with PowerShell v5.
You use the Enum keyword, set the Enumerators by name, and give them an integer value. The value can be anything, but I'm using ascending values starting with 0 in this example:
Enum _ProcessState{
Error = 0
None = 1
Started = 2
Paused = 3
Cleanup = 4
Complete = 5
Verified = 6
}
#the leading _ for the Enum is just cosmetic & not required
Once you've created the Enum, you can assign it to variables. The contents of the variable will return the text name of the Enum, and you can compare them as if they were integers.
$Item1_State = [_ProcessState]::Started
$Item2_State = [_ProcessState]::Cleanup
#return state of second variable
$Item2_state
#comparison
$Item1_State -gt $Item2_State
Will return:
Cleanup
False
If you wanted to compare and return the highest:
#sort the two objects, then return the first result (should return the item with the largest enum int)
$results = ($Item1_State,$Item2_State | Sort-Object -Descending)
$results[0]
Fun fact, you can also use arithmetic on them, for example:
$Item1_State + 1
$Item1_State + $Item2_State
Will return:
Paused
Verified
More info on Enum here:
https://blogs.technet.microsoft.com/heyscriptingguy/2015/08/26/new-powershell-5-feature-enumerations/
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_enum?view=powershell-6
https://psdevopsug.scot/post/working-with-enums-in-powershell/

Issues importing csv column and replacing it from hash value

Please note that this data has been cleaned to prevent identifying information and considerable white space has been removed from between the commas in order to aid in readability. Lastly at the end of the TYPE column there is an additional line saying how many lines were exported which hopefully will be ignored by the script.
TYPE ,DATE ,TIME ,STREET ,CROSS-STREET ,X-COORD ,Y-COORD
459 ,2015-05-03 00:00:00.000,00:58:35,FOO DR ,A RD/B CT , 0.0, 0.0
488 ,2015-05-03 00:00:00.000,02:31:54,BAR AV ,C ST/D ST , 0.0, 0.0
I am attempting to import this CSV using Import-CSV, convert the TYPE numeric codes into different strings. An example would be 459 becomes Apple. 488 becomes Banana and so forth. I have created a hash with the TYPE numbers as the key and the value being what I want it changed to.
So my issue is really two-fold; I have been so far unable to get the TYPE CSV column to import into the script (I've been trying an array for the most part) and I am not sure the best way to build the logic to check the array data against my hash keys and replace it with the appropriate value.
# declare filename to modify
$strFileName="test.csv"
# import the type data into its own array
$imported_CSV = Import-Csv $strFileName
# populate hash
$conversion_Hash = #{
187 = Homicide;
211 = Robbery;
245 = Assault;
451 = Arson;
459 = Burglary;
484 = Larceny;
487 = Grand Theft;
488 = Petty Theft;
10851 = Stolen Vehicle;
HS = Drug;
}
# perform the conversion
foreach ($record in $imported_CSV)
{
$conversion_Hash[$record.Type]
}
This has no logic and just contains the code that was presented in the answer below. Note that I addressed that it doesn't work in the comments below.
I think this is an example of what you are looking for:
$hashTable = #{459= Apple; 488= Banana;}
$csv = import-csv <file>
foreach($record in $csv)
{
$hashTable[$record.Type] #returns hash value
}
Output:
Apple
Banana
So we have several little issues here. The two big ones are your source file and the your hashtable keys are integers and not strings.
# declare filename to modify
$strFileName="c:\temp\point.csv"
# import the type data into its own array
$imported_CSV = (Get-Content $strFileName) -replace "\s*,\s*","," | ConvertFrom-Csv
# populate hash
$conversion_Hash = #{
"187" = "Homicide";
"211" = "Robbery";
"245" = "Assault";
"451" = "Arson";
"459" = "Burglary";
"484" = "Larceny";
"487" = "Grand Theft";
"488" = "Petty Theft";
"10851" = "Stolen Vehicle";
"HS" = "Drug";
}
# perform the conversion
foreach ($record in $imported_CSV)
{
$conversion_Hash[$record.Type]
}
Output from naughty people
Burglary
Petty Theft
I don't know if your source file looks like it does in your question but there is a bunch of whitespace there that will be giving you a hassle. Namely you dont have a TYPE column but a "TYPE " (without the spaces). Same goes for the other columns. Data is affected as well. It's not 459 but "459 "(without the spaces).
To fix that I check the file and replace all space surrounding the commas with just the comma.
TYPE,DATE,TIME,STREET,CROSS-STREET,X-COORD,Y-COORD
459,2015-05-03 00:00:00.000,00:58:35,FOO DR,A RD/B CT,0.0,0.0
488,2015-05-03 00:00:00.000,02:31:54,BAR AV,C ST/D ST,0.0,0.0
If your data already looks like that then you need to be careful posting this stuff in your question. Onto the other issue with your comparison
You will see I have quoted almost everything in that hashtable. I had to for the values as they were being taken as commands otherwise. I also quoted the keys as the csv table contains string and not integers. I would have just casted to [int] to avoid the whole issue but one of your keys is called "HS" which does not look like a number to me :).
What I might have done
Just to play a little I might have added another note property to the list called TypeAsString which would add a column.
# perform the conversion
$imported_CSV | ForEach-Object{
$_ | Add-Member -MemberType NoteProperty -Name "TypeAsString" -Value $conversion_Hash[$_.Type] -PassThru
}
So the output from one item would look like this
TYPE : 459
DATE : 2015-05-03 00:00:00.000
TIME : 00:58:35
STREET : FOO DR
CROSS-STREET : A RD/B CT
X-COORD : 0.0
Y-COORD : 0.0
TypeAsString : Burglary
I could have made a more dynamic property like a script property, so that changes in $conversion_Hash are updated instantly, but this should suffice for what you need.