Iterate through Hashtable containing List of values in Powershell - windows

I am trying to print Keys and a list of values together in a single string in powershell.
I have something like this
List(dict(key:value)) (key= string , value=list<String>)
so my input is like this
dict(
"apple"=$list1
"banana"=$list2
"orange"=$list3)
$list1=#('red','green')
$list2=#('yellow','black')
$list3=#('orange')
Now I want output something like that:
$Final_ans= apple,red_green banana,yellow_black orange,orange
How can I do this in PowerShell? I am not able to iterate like this.
I tried few methods but it is giving me output System Collection.HashTable

Assuming you have a hashtable or dictionary where all keys are strings and the value entries are arrays of strings, like this:
$list1 = #('red','green')
$list2 = #('yellow','black')
$list3 = #('orange')
$hashtable = #{
"apple" = $list1
"banana" = $list2
"orange" = $list3
}
(#{} is PowerShell's native syntax for a hashtable (an unordered dictionary) literal.)
You can enumerate each key/value pair like this:
$hashtable.GetEnumerator() |ForEach-Object {
$_.Key # this will resolve to the key (ex. "apple")
$_.Value # this will resolve to the values (ex. #('red', 'green'))
}
So to construct a string like the one you describe, we can do something like this:
#($hashtable.GetEnumerator() |ForEach-Object {
$_.Key,($_.Value -join '_') -join ','
}) -join ' '
Here, we use the -join operator to concatenate the individual strings with different delimiters:
$_.Value -join '_' turns the value pairs (ex. #('red', 'green')) into a string like red_green
$_.Key,(...) -join ',' turns the key + string we created in the previous step into a string like apple,red_green
#(...) -join ' ' then turns all of those strings into one big space-separated string apple,red_green banana,yellow_black orange,orange

Related

PowerShell Input Validation - Input should NOT be ALL numbers

I have the following code that works well for validating length...
DO {
$NewID = Read-Host -Prompt " NEW ID NAME of object (8-15 chars) "
} UNTIL ($NewID.Length -gt 7 -and $WS_NewName.Length -lt 16)
How can I include code that ensures input contains either an ALPHA or ALPHANUMERIC string, but NOT a purely NUMERIC one?
This can be easily doable using regular expressions like that:
($NewID -match '^[A-z0-9]*$') -and ($NewID -notmatch '^[0-9]*$')
Short explanation: first expression looks for alpha/alphanumeric string and the second discards purely numeric entries.
By the way, in your example you use $NewID and then $WS_NewName in Until expression, that might be confusing (however, I assume you just forgot to change it while pasting here)

Powershell Strange behaviour with Import-CSV

I have following powershell code:
clear;
$importedIDs = (Import-Csv "testing.csv" -Delimiter ';').id;
Write-Host $importedIDs.Length;
for ($i=0; $i -lt $importedIDs.Length; $i++) {
Write-Host $($importedIDs[$i]);
}
The goal is to read only the id column in the csv file which looks like this:
"created";"id"
"2018-04-04 21:03:01";"123456"
"2018-04-04 21:03:01";"123457"
When there are two or more rows the output is as expected:
2
123456
123457
However when there is only 1 row in the csv file (row with id 123456) the output is:
6
1
2
3
4
5
6
Desired output would be:
1
123456
Can anyone explain why this is happening and how can I fix this?
Any help is appreciated
If there are multiple rows in the csv you get an array of strings. One array-element per row. Therefore the index applies to the rows. If there is only one row you don't get a array containing one string, as you would probably expect. You get a single string instead. When using an index on a string powershell treats the string as an array of characters and therefore returns only one char.
You can slightly rewrite your script to get around the problem described by J. Bergmann.
Instead of using a for loop to loop through each element in the array, where an "element" may refer to a string in a string array or a character in a string, you can use a foreach loop and loop through elements in an array like this. foreach won't treat a string as an character array
clear;
$importedIDs = (Import-Csv "testing.csv" -Delimiter ';').id;
Write-Host $importedIDs.Length;
foreach ($importedID in $importedIDs) {
Write-Host $($importedID);
}

Find array elements which values are not part of another array PowerShell

I have two arrays
$adg - (A list of AD groups)
$dbs - (A list of database names)
Lets say I use this command
$adg -match $dbs.i
The output will be all the AD groups which have the string $dbs in its name.
However, I am aiming to find the DBs in which are not part of the AD groups array.
Eg:
$adg = #("Dev22_Owner","Test49_Owner","Rocket_Owner")
$dbs = #("Dev22", "Confidential", "InternDB", "Rocket", "Test49")
What approach should I take to get the output:
Confidential
InternDB
I tried $dbs | Where $adg -notmatch $dbs.i but there is no output.
I would first remove the unecessary user part from the ad group:
$groups = $adg | ForEach-Object {
$_ -replace '_.*?$'
}
Then you can use the Where-Object cmdlet with the -notin operator to filter them:
$dbs | Where-Object { $_ -notin $groups }
Output:
Confidential
InternDB
To offer a more concise PSv3+ alternative to Martin Brandl's helpful answer:
PS> (Compare-Object ($adg -replace '_Owner$') $dbs).InputObject
Confidential
InternDB
($adg -replace '_Owner$') returns a new array with copies of the input string that have _Owner suffix stripped from them.
Compare-Object compares the stripped array with the other array and returns objects that represent the differences between the two arrays.
Accessing the .InputObject on the difference objects returns the values that differ.
Note: If $dbs contained items that aren't in the stripped $adg array, the above would return a union of all differing items, irrespective of which side they're unique to; to distinguish the sides / limit to a side, you'd have to use the .SideIndicator property (value => indicates values exclusive to the right side, <= those exclusive to the left side).
As for what you tried:
$adg -match $dbs.i doesn't work as intended and is effectively the same as $adg -match '', which invariably returns all $adg items.
The reason is that array $dbs has no .i property, so the expression evaluates to $null, which on the RHS of match is coerced to a string and therefore is converted to the empty string.
Generally, the RHS of -match does not support arrays - only a single regular expression.
If you do supply an array, it is implicitly converted to a string by joining the elements to form a space-separated list; e.g. array 1, 2 is coerced to '1 2' and '1 2' -match (1, 2) therefore evaluates to $True.

Sorting movie titles into CSV

I've got a media server at home, and I've created a script that pulls all the file names and sorts them before putting them into a CSV. My only problem is that it sorts alphanumeric, but from a movie titles perspective, I'd like to ignore "A", "An", and "The". Is there a way to ignore those strings and have the sort work correctly without actually altering the file name in the CSV?
Yes, you can sort multiple objects into order by any property, and if none of the properties are quite what you want then you can provide a scriptblock to Sort-Object with some code "do xyz to each object" and it will sort them based on the output of the scriptblock - and that will only be used for sorting, it won't change anything.
So calculate the name without the leading words A, An, The using any code you want to. Here, I'm cooking with regex because it's quick, tasty and does case-insensitive matching by default:
Get-ChildItem | Sort-Object -Property { $_.Name -replace '^(A|An|The).' }
But you can do something just as effective with the plain ingredients around your kitchen:
Function Mangle-FilmName
{
param($file)
$name = $file.Name.ToLower()
if ($name.startswith('an'))
{
$name.Substring(4)
}
elseif ($name.startswith('the')
{
$name.Substring(5)
}
...
else
{
$name
}
}
Get-ChildItem | Sort-Object -Property Mangle-FilmName
Or with switch statements or loops over arrays of words, and/or/etc.
You could use something like the following.
Due to missing sample data I don't know the structure of your file names (word separators etc.) but you can customize the following code to your needs. What the code essentially does is splitting the base file name by separators '_', ' ' and '.', filters out your ignored words ('The', 'A', 'An' etc.) and joins back the parts to a single string.
Please note that at the end of this, the file names are compared without their initial word separators (i.e. 'The_Blue_House.mpg' and 'The.Blue.House.mpg' would be considered the same) which IMHO is a good thing but your needs may be different.
Hope that helps
$wordSeparator = '_| |\.'
$ignoredWords = #(
'The'
'A'
'An'
# add more
)
filter sortableFileName {
($_ -split $wordSeparator | ? { $_ -notin $ignoredWords }) -join ''
}
Get-ChildItem | Sort-Object { $_.BaseName | sortableFileName } # | Export-CSV

What does the "#" symbol do in PowerShell?

I've seen the # symbol used in PowerShell to initialise arrays.
What exactly does the # symbol denote and where can I read more about it?
In PowerShell V2, # is also the Splat operator.
PS> # First use it to create a hashtable of parameters:
PS> $params = #{path = "c:\temp"; Recurse= $true}
PS> # Then use it to SPLAT the parameters - which is to say to expand a hash table
PS> # into a set of command line parameters.
PS> dir #params
PS> # That was the equivalent of:
PS> dir -Path c:\temp -Recurse:$true
PowerShell will actually treat any comma-separated list as an array:
"server1","server2"
So the # is optional in those cases. However, for associative arrays, the # is required:
#{"Key"="Value";"Key2"="Value2"}
Officially, # is the "array operator." You can read more about it in the documentation that installed along with PowerShell, or in a book like "Windows PowerShell: TFM," which I co-authored.
While the above responses provide most of the answer it is useful--even this late to the question--to provide the full answer, to wit:
Array sub-expression (see about_arrays)
Forces the value to be an array, even if a singleton or a null, e.g. $a = #(ps | where name -like 'foo')
Hash initializer (see about_hash_tables)
Initializes a hash table with key-value pairs, e.g.
$HashArguments = #{ Path = "test.txt"; Destination = "test2.txt"; WhatIf = $true }
Splatting (see about_splatting)
Let's you invoke a cmdlet with parameters from an array or a hash-table rather than the more customary individually enumerated parameters, e.g. using the hash table just above, Copy-Item #HashArguments
Here strings (see about_quoting_rules)
Let's you create strings with easily embedded quotes, typically used for multi-line strings, e.g.:
$data = #"
line one
line two
something "quoted" here
"#
Because this type of question (what does 'x' notation mean in PowerShell?) is so common here on StackOverflow as well as in many reader comments, I put together a lexicon of PowerShell punctuation, just published on Simple-Talk.com. Read all about # as well as % and # and $_ and ? and more at The Complete Guide to PowerShell Punctuation. Attached to the article is this wallchart that gives you everything on a single sheet:
You can also wrap the output of a cmdlet (or pipeline) in #() to ensure that what you get back is an array rather than a single item.
For instance, dir usually returns a list, but depending on the options, it might return a single object. If you are planning on iterating through the results with a foreach-object, you need to make sure you get a list back. Here's a contrived example:
$results = #( dir c:\autoexec.bat)
One more thing... an empty array (like to initialize a variable) is denoted #().
The Splatting Operator
To create an array, we create a variable and assign the array. Arrays are noted by the "#" symbol. Let's take the discussion above and use an array to connect to multiple remote computers:
$strComputers = #("Server1", "Server2", "Server3")<enter>
They are used for arrays and hashes.
PowerShell Tutorial 7: Accumulate, Recall, and Modify Data
Array Literals In PowerShell
I hope this helps to understand it a bit better.
You can store "values" within a key and return that value to do something.
In this case I have just provided #{a="";b="";c="";} and if not in the options i.e "keys" (a, b or c) then don't return a value
$array = #{
a = "test1";
b = "test2";
c = "test3"
}
foreach($elem in $array.GetEnumerator()){
if ($elem.key -eq "a"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "b"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "c"){
$key = $elem.key
$value = $elem.value
}
else{
Write-Host "No other value"
}
Write-Host "Key: " $key "Value: " $value
}

Resources