How to combine string inputs and generate array dynamically? - powershell

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
}

Related

How to add missing rows (URLs containing page numbers) to an array (like seq in Linux)

I have an array consisting of URLS of the form:
$URLs = #("https://somesite.com/folder1/page/1/"
,"https://somesite.com/folder222/page/1/"
,"https://somesite.com/folder222/page/2/"
,"https://somesite.com/folder444/page/1/"
,"https://somesite.com/folder444/page/3/"
,"https://somesite.com/folderBBB/page/1/"
,"https://somesite.com/folderBBB/page/5/")
They always have /page/1/, I need to add (or reconstruct) all missing URLS from the highest page down to 1 so it ends up like so:
$URLs = #("https://somesite.com/folder1/page/1/"
,"https://somesite.com/folder222/page/1/"
,"https://somesite.com/folder222/page/2/"
,"https://somesite.com/folder444/page/1/"
,"https://somesite.com/folder444/page/2/"
,"https://somesite.com/folder444/page/3/"
,"https://somesite.com/folderBBB/page/1/"
,"https://somesite.com/folderBBB/page/2/"
,"https://somesite.com/folderBBB/page/3/"
,"https://somesite.com/folderBBB/page/4/"
,"https://somesite.com/folderBBB/page/5/")
I'd imagine the Pseudo-Code would be something like:
For each folder, extract the highest page number:
hxxps://somesite.com/folderBBB/page/5/
Expand this out from (5) to (1)
hxxps://somesite.com/folderBBB/page/1/
hxxps://somesite.com/folderBBB/page/2/
hxxps://somesite.com/folderBBB/page/3/
hxxps://somesite.com/folderBBB/page/4/
hxxps://somesite.com/folderBBB/page/5/
Output this into an array
Any pointers would be welcome!
You can use a pipeline-based solution via the Group-Object cmdlet as follows:
$URLs = #("https://somesite.com/folder1/page/1/"
, "https://somesite.com/folder222/page/1/"
, "https://somesite.com/folder222/page/2/"
, "https://somesite.com/folder444/page/1/"
, "https://somesite.com/folder444/page/3/"
, "https://somesite.com/folderBBB/page/1/"
, "https://somesite.com/folderBBB/page/5/")
$URLs |
Group-Object { $_ -replace '[^/]+/$' } | # Group by shared prefix
ForEach-Object {
# Extract the start and end number for the group at hand.
[int] $from, [int] $to =
($_.Group[0], $_.Group[-1]) -replace '^.+/([^/]+)/$', '$1'
# Generate the output URLs.
# You can assign the entire pipeline to a variable
# ($generatedUrls = $URLs | ...) to capture them in an array.
foreach ($i in $from..$to) { $_.Name + $i + '/' }
}
Note:
The assumption is that the first and last element in each group of URLs that share the same prefix always contain the start and end point of the desired enumeration, respectively.
If that assumption doesn't hold, use the following instead:
$minMax = $_.Group -replace '^.+/([^/]+)/$', '$1' |
Measure-Object -Minimum -Maximum
$from, $to = $minMax.Minimum, $minMax.Maximum
The regex-based -replace operator is used for two things:
-replace '[^/]+/$' eliminates the last component from each URL, so as to group them by their shared prefix.
-replace '^.+/([^/]+)/$', '$1' effectively extracts the last component from each given URL, i.e. the numbers that represent the start and end point of the desired enumeration.
Procedural alternative:
# Build a map (ordered hashtable) that maps URL prefixes
# to the number suffixes that occur among the URLs sharing
# the same prefix.
$map = [ordered] #{}
foreach ($url in $URLs) {
if ($url -match '^(.+)/([^/]+)/') {
$prefix, [int] $num = $Matches[1], $Matches[2]
$map[$prefix] = [array] $map[$prefix] + $num
}
}
# Process the map to generate the URLs.
# Again, use something like
# $generatedUrls = foreach ...
# to capture them in an array.
foreach ($prefix in $map.Keys) {
$nums = $map[$prefix]
$from, $to = $nums[0], $nums[-1]
foreach ($num in $from..$to) {
'{0}/{1}/' -f $prefix, $num # synthesize URL and output it.
}
}

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

Powershell import-csv splitting and matching cell contents

I have a CSV with 3 columns and multiple rows. Each of the 3 x neighboring cells (per row) has multiple lines of data in the cell, always with the same number of lines per neighboring cell.
Raw CSV data:
NodeName,NodeAddress,NodeAdressDR
"1.com
2.com
3.com
4.com","10.173.5.210
10.173.5.212
10.173.5.214
10.173.5.216","10.49.245.210
10.49.245.212
10.49.245.214
10.49.245.216"
"5.com
6.com
7.com
8.com","10.173.5.203
10.173.5.205
10.173.5.207","10.49.245.203
10.49.245.205
10.49.245.207"
,,
This is working with the following but is there an easier way to cater for indeterminate number of hostnames/IPs per cell without having to create the individual variables i.e one per line?
$csvImportFull = Import-CSV .\CSVFile.csv
foreach ($headingFull in $csvImportFull)
{
$nodeName = $headingFull.("NodeName")
$nodeAddress =$headingFull.("NodeAddress")
$nodeAddressDR = $headingFull.("NodeAddressDR")
$NodeName1 = ($nodeName -split "\n")[0]
$NodeName2 = ($nodeName-split "\n")[1]
$NodeName3 = ($nodeName-split "\n")[2]
$NodeName4 = ($nodeName -split "\n")[3]
$NodeAddress1 = ($nodeAddress -split "\n")[0]
$NodeAddress2 = ($nodeAddress -split "\n")[1]
$NodeAddress3 = ($nodeAddress -split "\n")[2]
$NodeAddress4 = ($nodeAddress -split "\n")[3]
$NodeAddressDR1 = ($nodeAddressDR -split "\n")[0]
$NodeAddressDR2 = ($nodeAddressDR -split "\n")[1]
$NodeAddressDR3 = ($nodeAddressDR -split "\n")[2]
$NodeAddressDR4 = ($nodeAddressDR -split "\n")[3]
write-host $NodeName1 NodeAddress1
write-host $NodeName1 NodeAddressDR1
write-host $NodeName2 NodeAddress2
write-host $NodeName1 NodeAddressDR2
}
So instead of hard writing the variables you could iterate over the split and greate a new list of objects:
$objects = #()
Import-CSV .\CSVFile.csv| ForEach-Object {
$names = $_.NodeName -split '\r\n'
$address = $_.NodeAddress -split '\r\n'
$addressDr = $_.NodeAdressDR -split '\r\n'
for($i = 0; $i -le 4; $i++)
{
$objects += [PsCustomObject]#{
NodeName = $names[$i]
NodeAddress = $address[$i]
NodeAddressDr = $addressDr[$i]
}
}
}
Now you have a list of objects where each object has a NodeName, NodeAddress and NodeAddressDR
So you can do something like this:
$objects | where NodeName -eq '1.com'
Output:
NodeName NodeAddress NodeAddressDr
-------- ----------- -------------
1.com 10.173.5.210 10.49.245.210

append forloop iterator variable value

Am calling for loop and trying to append the $i value along with a variable called inside the function. not sure how to do this. Everytime I get a error saying "Unexpected token 'i' in expression or statement." any suggestions/idea please.
Thanks to Chris. His code works perfectly..
code :
function Get-Data {
param(
# Consider giving this a more meaningful name
[Int]$i
)
# Assigns the value in the first index from -split to $null
# and the value in the second index to $msgs.
$null, $msgs = (b2b.exe -readparams "msgs${i}data" | Select-Object -Skip 1 -First 1) -split '='
$null, $bytes = (b2b.exe -readparams "bytes${i}data" | Select-Object -Skip 1 -First 1) -split '='
[PSCustomObject]#{
MData = $msgs.Trim()
BData = $bytes.Trim()
}
}
for ($i=0; $i-le 3; $i++) {
$data = Get-Data $i
write-host "for MData$i $($data.MData)"
write-host "for BData$i $($data.BData)"
}
I can't tell you if this will work, but I would not rely on globally assigned variables to pass information out of a function.
I suspect it may need a bit of work around construction of the parameters for b2b.exe.
function Get-Data {
param(
# Consider giving this a more meaningful name
[Int]$i
)
# Assigns the value in the first index from -split to $null
# and the value in the second index to $msgs.
$null, $msgs = (b2b.exe -readparams "msgs${i}data" | Select-Object -Skip 1 -First 1) -split '='
$null, $bytes = (b2b.exe -readparams "bytes${i}data" | Select-Object -Skip 1 -First 1) -split '='
[PSCustomObject]#{
MData = $msgs.Trim()
BData = $bytes.Trim()
}
}
for ($i=0; $i-le 3; $i++) {
$data = Get-Data $i
write-host "for MData$i $($data.MData)"
write-host "for BData$i $($data.BData)"
}

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

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"