How to create an associative array, preferably a one-liner? - powershell

I have this:
$str = "name = username`nemail = user#example.com"
Which is LF separated string, as part of an INI file. I want to create associative array so that for instance $vars['name'] will return 'username'. This is how I split it to lines:
$vars = ([regex]'\n').Split($str) | %{ $_.Trim() }
What change should I make to the line above?

If you are a lucky owner of Powershell 4 then you can use ConvertFrom-StringData cmdlet:
$str = "name = username`nemail = user#example.com"
$vars = ConvertFrom-StringData $str
Result:
PS C:\> $vars
Name Value
---- -----
name username
email user#example.com

A more PoSh version of PeterK's answer would look like this:
$vars = #{}
$str.Trim() -split "\s*`n\s*" | % {
$key, $value = $_ -split '\s*=\s*'
$vars[$key] = $value
}
Using $vars[$key] = $value instead of $vars.Add($key, $value) avoids errors in case you have duplicate keys.
You could also split the string into an array of alternating key and value fields and then fill the hashtable from that array:
$vars = #{}
$list = $str.Trim() -split "\s*`n\s*" -split '\s*=\s*'
while ($list) {
$key, $value, $list = $list
$vars[$key] = $value
}
Each of the examples can be mangled into a single line by separating the statements with semicolons:
$vars = #{}; $str.Trim() -split "\s*`n\s*" | % { $key, $value = $_ -split '\s*=\s*'; $vars[$key] = $value }
$vars = #{}; $list = $str.Trim() -split "\s*`n\s*" -split '\s*=\s*'; while ($list) { $key, $value, $list = $list; $vars[$key] = $value }

It's not a one-liner, but one way to do it is:
$hashtable = #{};
$key_value_pairs = $str.Split("`n");
foreach($key_value in $key_value_pairs)
{
$key_value_pair = $key_value.Split('=');
$hashtable.Add($key_value_pair[0].Trim(), $key_value_pair[1].Trim());
}
The input string is first split into an array, each item containing a key-value pair in a single line. For each key-value pair, we split them into a 'key' and 'value' on "=", again into a regular array. Both the key and value are trimmed and added to the hash table. Once this is done, $hashtable["name"] will return "username" and $hashtable["email"] will return "user#example.com"

Related

Append comma to every element in the Arraylist of strings

I need to append comma to every string , my code appending "," to last element as well how to eliminate that
for($j=0; $j -lt $back_log_bloblist.Count; $j++){
if($back_log_bloblist[$j].Name -like "$Value"){
$string += $back_log_bloblist[$j].Name +","
Write-Host $string
}
}
This can be solved much easier without an explicit loop:
$string = ($back_log_bloblist.Name -like $Value) -join ','
Explanation:
$back_log_bloblist.Name creates an array from the values of the Name property of all $back_log_bloblist elements
-like $Value selects all elements from this array that match pattern in $Value
-join ',' finally joins all selected elements (inserting commas only between elements, not after, exactly what you want)
you could remove the brackets, they are just there for conceptual clarity
Proof-of-concept:
$back_log_bloblist = [pscustomobject]#{ Name='foo' },
[pscustomobject]#{ Name='fop' },
[pscustomobject]#{ Name='bar' }
$Value = 'f*'
$string = ($back_log_bloblist.Name -like $Value) -join ','
$string
Output:
foo,fop

How to combine string inputs and generate array dynamically?

param([string]$roles,[string]$members)
Suppose I am passing input on the command line like this:
PS> role1,role2,role3,role4 member1,member2,,,,member3,,member4
The array I expect for this would be:
$array = #(
#('role1', 'member1,member2'),
#('role2', ''),
#('role3', 'member3'),
#('role4', 'member4')
)
I know to turn string to array:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
Now how do I combine $roles with $members so that each role will be associated with member(s)? and how wouldIi generate the array dynamically?
Pseudocode:
$array = #()
($roles+$members) | %{
$role = $_.roles
if ($_.members) {
$_.members -split ',,' | ForEach-Object { $array += $role $_ }
} else {
$array += $role
}
}
Note: I am splitting members as an index of its own for each double comma because apparently semicolons aren't accepted on a command line because they break the command line, so I have to use double comma as delimiter.
Note 2: notice the 4 commas: ,,,, this indicates that role2 does not have members to add, so in essence it means between the 4 commas is no input for member to that index/item (role2), i.e. ,,EMPTY,,.
If you really want to stick with this parameter format, you can create the desired output array as follows:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$i = 0
$array = #(foreach ($role in $roles) {
, ($role, $members[$i++])
})
Note that if you pass your arguments from PowerShell, you need to quote them, as PowerShell will otherwise parse them as an array.
And with quoting you're free to use ; in lieu of ,,, for instance, to separate the member groups.
A better way to represent the argument data for later processing is to create an array of custom objects rather than a nested array:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$i = 0
$array = #(foreach ($role in $roles) {
[pscustomobject] #{
Role = $role
Members = $members[$i++] -split ','
}
})
Each object in $array now has a .Role and a .Members property, the latter containing the individual members as a an array of strings.
Alternatively, you could create a[n ordered] hashtable from the input, keyed by role name, but that is only necessary if you need to access roles by name or if you wanted to rule out duplicate roles having been specified.
Here's an alternative argument format that is easier to understand:
$rolesAndMembers = 'role1 = member1,member2 ; role2= ; role3=member3 ; role4=member4'
$array = #(foreach ($roleAndMembers in ($rolesAndMembers -replace ' ' -split ';')) {
$role, $members = $roleAndMembers -split '='
[pscustomobject] #{
Role = $role
Members = $members -split ','
}
})
Your parameter format is rather bizarre, but here's one way:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$result = #()
for ( $i = 0; $i -lt $roles.Count; $i++ ) {
$result += ,#($roles[$i],$members[$i])
}
I would recommend redesigning the script to use standard PowerShell parameters (the engineering effort would be worth it, IMO).
I'd strongly recommend using hashtables/dictionaries to pass these role mappings:
param(
[System.Collections.IDictionary]$RoleMembers
)
# now we can access each mapping by role name:
$RoleMembers['role1'] # member1, member2
# or iterate over them like an array:
foreach($role in $RoleMembers.Keys){
$RoleMembers[$role]
}
You could use one of the construct the input argument from your current input strings:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ','
$roleMembers = #{}
for ($i = 0; $i -lt $roles.Count; $i++) {
# `Where Length -ne 0` to filter out empty strings
$roleMembers[$roles[$i]] = $members[($i*2)..($i*2+1)] |Where Length -ne 0
}

When creating a dictionary in PowerShell are duplicate keys all accounted for?

I'm looping through some files and pulling values into a dictionary around a ":" delimiter.
The data in the txt files looks like this:
AD ID: 9999
Ad Placement: Computers
Landing Page: www.something.com
Interests: this and that and this
Interests: also this thing and one final thing
My script for creating a dictionary looks like the following:
$files = ls "*.txt"
$dictionary = #{}
[System.Collections.Generic.List[String]]$list = #()
foreach ($f in $files) {
$in = Get-Content -Raw $f
$in.Split([Environment]::NewLine) | ForEach-Object {
$key, $value = $_.Split(':')
$dictionary[$key] = $value
}
[void]$list.Add($dictionary['Ad ID'] + ',' + $dictionary['Ad Text'] + ',' +
$dictionary['Ad Landing Page'] + ',' + $dictionary['Interests'])
}
That's the basic idea at least. I've gotten unpredictable results when I come across a file that has a key twice, as is the case in the entry in the sample data above called "Interests."
What occurs when adding dictionary items to a list from a file?
In the above example, what is the value of $dictionary['interests'] as it goes through the script?
Since the data can contain duplicate keys, you cannot use the ConvertFrom-StringData cmdlet.
To get the data in a dictionary (hashtable) manually is not that hard to do and you can decide for yourself what to do with duplicate keys: either overwrite the values so the last entry found 'wins' or not:
# this decides which duplicate value you want to store in the hashtable
$allowOverwrite = $false
$hash = #{}
# get the content of the file as string array and loop through
Get-Content -Path 'THE FULL PATH AND FILENAME OF YOUR TEXTFILE' | ForEach-Object {
if ( -not [string]::IsNullOrWhiteSpace($_)) {
# split string to get the key and the value
$key, $value = $_ -split ':', 2 | ForEach-Object { $_.Trim() }
# if a key is found that already exists in the hashtable
if ($hash.ContainsKey($key)) {
# either overwrite the value 'Last-One-Wins'
# or do nothing 'First-One-Wins'
if ($allowOverwrite) { $hash[$key] = $value }
}
else {
$hash[$key] = $value
}
}
}
$hash["interests"]
shows "this and that and this" in case of $allowOverwrite = $false
shows "also this thing and one final thing" in case of $allowOverwrite = $true

How can I transpose and parse a large vertical text file into a CSV file with headers?

I have a large text file (*.txt) in the following format:
; KEY 123456
; Any Company LLC
; 123 Main St, Anytown, USA
SEC1 = xxxxxxxxxxxxxxxxxxxxx
SEC2 = xxxxxxxxxxxxxxxxxxxxx
SEC3 = xxxxxxxxxxxxxxxxxxxxx
SEC4 = xxxxxxxxxxxxxxxxxxxxx
SEC5 = xxxxxxxxxxxxxxxxxxxxx
SEC6 = xxxxxxxxxxxxxxxxxxxxx
This is repeated for about 350 - 400 keys. These are HASP keys and the SEC codes associated with them. I am trying to parse this file into a CSV file with KEY and SEC1 - SEC6 as the headers, with the rows being filled in. This is the format I am trying to get to:
KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6
123456,xxxxxxxxxx,xxxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx
456789,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx
I have been able to get a script to export to a CSV with only one key in the text file (my test file), but when I try to run it on the full list, it only exports the last key and sec codes.
$keysheet = '.\AllKeys.txt'
$holdarr = #{}
Get-Content $keysheet | ForEach-Object {
if ($_ -match "KEY") {
$key, $value = $_.TrimStart("; ") -split " "
$holdarr[$key] = $value }
elseif ($_ -match "SEC") {
$key, $value = $_ -split " = "
$holdarr[$key] = $value }
}
$hash = New-Object PSObject -Property $holdarr
$hash | Export-Csv -Path '.\allsec.csv' -NoTypeInformation
When I run it on the full list, it also adds a couple of extra columns with what looks like properties instead of values.
Any help to get this to work would be appreciated.
Thanks.
Here's the approach I suggest:
$output = switch -Regex -File './AllKeys.txt' {
'^; KEY (?<key>\d+)' {
if ($o) {
[pscustomobject]$o
}
$o = #{
KEY = $Matches['key']
}
}
'^(?<sec>SEC.*?)\s' {
$o[$Matches['sec']] = ($_ | ConvertFrom-StringData)[$Matches['sec']]
}
default {
Write-Warning -Message "No match found: $_"
}
}
# catch the last object
$output += [pscustomobject]$o
$output | Export-Csv -Path './some.csv' -NoTypeInformation
This would be one approach.
& {
$entry = $null
switch -Regex -File '.\AllKeys.txt' {
"KEY" {
if ($entry ) {
[PSCustomObject]$entry
}
$entry = #{}
$key, $value = $_.TrimStart("; ") -split " "
$entry[$key] = [int]$value
}
"SEC" {
$key, $value = $_ -split " = "
$entry[$key] = $value
}
}
[PSCustomObject]$entry
} | sort KEY | select KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6 |
Export-Csv -Path '.\allsec.csv' -NoTypeInformation
Lets leverage the strength of ConvertFrom-StringData which
Converts a string containing one or more key and value pairs to a hash table.
So what we will do is
Split into blocks of text
edit the "; Key" line
Remove an blank lines or semicolon lines.
Pass to ConvertFrom-StringData to create a hashtable
Convert that to a PowerShell object
$path = "c:\temp\keys.txt"
# Split the file into its key/sec collections. Drop any black entries created in the split
(Get-Content -Raw $path) -split ";\s+KEY\s+" | Where-Object{-not [string]::IsNullOrWhiteSpace($_)} | ForEach-Object{
# Split the block into lines again
$lines = $_ -split "`r`n" | Where-Object{$_ -notmatch "^;" -and -not [string]::IsNullOrWhiteSpace($_)}
# Edit the first line so we have a full block of key=value pairs.
$lines[0] = "key=$($lines[0])"
# Use ConvertFrom-StringData to do the leg work after we join the lines back as a single string.
[pscustomobject](($lines -join "`r`n") | ConvertFrom-StringData)
} |
# Cannot guarentee column order so we force it with this select statement.
Select-Object KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6
Use Export-CSV to your hearts content now.

Split string and assign first value to a variable in powershell

In below example: How do I assign each value to a single variable $var one at a time?
$string ="A;B;C"
$Data=$string.split (";")
$Data
A
B
C
You can assign multiple variables when you split the string:
$string ="A;B;C"
$a, $b, $c = $string -split ';'
# $a = A
# $b = B
# $c = B
If you want an array, you already do it right:
$string ="A;B;C"
$Data = $string -split ';'
# $Data[0] = A
# $Data[1] = B
# $Data[2] = B
As you can see by invoking $Data.GetType(), $Data is already an String[]:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String[] System.Array
You can store them in an array with a loop
$string ="A;B;C"
$Data=$string.split (";")
$var = #{}
$i = 0
foreach($item in $Data){
$var[$i] = $item
$i++
}
$var[0]
$var[1]
$var[2]
EDIT: I'm dumb, $data is already an array after the string split, you can call out $data[0], that would be your first variable, or:
$string ="A;B;C"
$Data=$string.split (";")
$var = $Data[0]
You can use multiple variables on the left side as well:
$string = "A;B;C"
$D1,$D2,$D3 = $string.split(";")
In cases where you you're only truly interested in the first output from a string split operation (as the title implies), you can discard the remaining output by assigning it to $null:
$first,$null = 'A;B;C' -split ';'
Since the remaining values are to be discarded anyways, you could ask the -split operator to only split once:
$first,$null = 'A;B;C' -split ';',2
I used below script and it worked.
$dbname=$DBList.split(";")
Foreach($item in $dbname)
{
Backup-Sqldatabase -ServerInstance $server -Database $item -BackupFile $backupfile
}