Building automatic strings based on a variable - powershell

I have a variable called: $backendSubnet
This variable currently contains 4 entries, when I do $backendSubnet.Count it returns '4'
This number of entries will change each time the script is run. What I need to do it automatically break out (based on the number of entries) in this instance there's 4 - to be used in another CmdLet, see.....
-Subnet $backendSubnet1,$backendSubnet2,$backendSubnet3,$backendSubnet2
I need to automate breaking this out arranged like this above.
I have tried the following, bit I think I am on the wrong track:
$max = $backendSubnet.Count -1;0..$max | % {$backendSubnetArray += $backendSubnet[$_]}

I would suggest initializing a new array to a predefined size, then copy N elements from the $backendSubnet into the new array. Then rinse and repeat.
for ($i = 0; $i -lt $backendSubnet.Count; $i += $numToCopy)
{
$numToCopy = [Math]::Min($backendSubnet.Count - $i, $max)
$subset = new-object object[] $numToCopy
[Array]::Copy($backendSubnet, $i, $subset, 0, $numToCopy)
SomeCmdlet -Subnet $subset
}

Related

How to Iterate through an index of n using different index of p

I am doing this in PowerShell, although the question isn't tied to a language.
lets say that I have an indexed array of size n called accounts, and another indexed array of size p called rows. I want to go through each of the items in row and use an account to do something. In my real world example, I have excel rows and I need to log into accounts in the accounts array in a round-robin fashion. the size of these arrays can be anything from 1 to n, and they will very likely not be the same size as the other. What math problem can I use to ensure that, regardless of the size of the array, I get the iterations below (assume $rows.Count is 500 and $accounts.Count is 12:
rows[1] - accounts[1]
rows[2] - accounts[2]
rows[3] - accounts[3]
rows[4] - accounts[4]
rows[5] - accounts[5]
rows[6] - accounts[6]
rows[7] - accounts[7]
rows[8] - accounts[7]
rows[9] - accounts[8]
rows[10] - accounts[9]
rows[11] - accounts[10]
rows[12] - accounts[11]
rows[13] - accounts[12]
rows[14] - accounts[1]
rows[15] - accounts[2]
rows[16] - accounts[3]
rows[17] - accounts[4]
My attempts have landed on this so far:
for ($i = 0; $i -lt $rows.Count; $i++) {
$k = 0
$j = $accounts.Count
if ($i -le $j) {
$k = $j
}
else {
$k = $i % $j
}
$rows[$i] - $accounts[$k]
}
I think that modulus is the key, but I can't seem to finish the equation.
Indeed, using %, the modulus operator, is key; a PowerShell-idiomatic solution (it simply prints the paired array elements):
$rows = 1..500
$accounts = 1..12
$i = 0
$rows.ForEach({ '{0} - {1}' -f $_, $accounts[$i++ % $accounts.Count] })
Note that array indices in PowerShell / .NET are 0-based.

Processing items within a powershell array - multiple items

I have been reading about arrays in powershell and hash tables, I know the basic workings of an array and how to use foreach loop to get items within the array, my challange here is slightly different. I would like to pay what I call a multi dimension array to a script, and process the items contained within the array.
What is my setup.
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
What am i trying to achieve ?
I am trying to achieve the structure of a database table within powershell. So literally treating each array item as a row.
So for example
(1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
could be thought of as a table like below
enter image description here
I then want to loop for each item in the array to get the values, similar to the below example
[0].serverid = 1, [0].servername = server1, [0].locationid = 3, [0].applicationID = 1
[1].serverid = 4, [1].servername = server2, [1].locationid = 6, [1].applicationID = 2
what have I done ?
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
$x
for ($i = 0; $i -lt $x.Count; $i++)
{
$myserverid = $x[$i][0]
$myservername = $x[$i][1]
$mylocationid = $x[$i][2]
$myappid = $x[$i][3]
write-host $myserverid
}
The Issues
If I set the following $x = (1,"Server1",3,1), then the loop is somewhat incorrect which is why I think the approach is wrong (more than one item works i.e $x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)). The loop only works if you have more than one item within the array, hence why I want to re-examine the way the loop works.
Thanks in advance
Your approach relies on a nested (jagged) array: That is, you have an array of subarrays, each of which represents the tuple of values you want to assign to properties.
If there's only one subarray, you must create the nested array explicitly, using the unary form of , the array constructor operator:
# Construct a 1-element array that contains the 4-element subarray.
$x = , (1,"Server1",3,1)
With two or more, subarrays, you implicitly get a nested array:
# Construct a 3-element array, each element of which contains a 4-element subarray.
$x = (1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3)
Note that in PSv5+ you could use a custom class to solve your problem:
class Custom {
[int] $serverid; [string] $servername;[int] $locationid; [int] $appid
Custom($propValueArray) {
$this.serverid = $propValueArray[0]; $this.servername = $propValueArray[1]; $this.locationid = $propValueArray[2]; $this.appid = $propValueArray[3]
}
}
# Use an array cast to construct [Custom] instances.
# Note the need for (...) around the array, because casts have high
# precedence in PowerShell.
[Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
This would allow for processing such as:
# Construct all objects
$objects = [Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
# Process each object.
foreach ($object in $objects) {
($myserverid = $object.serverid) # assign a property to a var; `()` also outputs
# ...
}

Parsing a file to create an array of lines

This seems so incredibly simple but I am missing something. I just need to add an array to array[0], array[1], etc.
I am taking a vcard file and trying to read all the lines of one vcard and put them in an array and then place that array in an array so array[0] will be vcard 1, array[1] will be the next, etc.
$c = Get-Content -Path C:\temp\Contacts_Backup.vcf
$counter=0
$contact=#()
$allcontacts=#()
Foreach ($line in $c){
$contact += $line
if ($line -eq 'END:VCARD'){
$allcontacts[$counter++] = $contact
$contact=#()
}
}
Result:
Unable to index into an object of type System.String.
tl;dr:
You cannot "grow" an array by assigning to a nonexistent index; if you start with #() - an empty array - you must use += to "append" elements (arrays are fixed-size collections, so what really happens is that a new array must be allocated every time that contains the old elements followed by the new one).
Using += is therefore inefficient in loops, and there are two alternatives:
Use a .NET extensible list type to build an array-like collection more efficiently.
Preferably - because it is both more convenient and faster - let PowerShell create the array for you, simply by capturing the output from a foreach loop in a variable
($array = #(foreach (...) { ... }))
Details below.
Your code indeed has a problem, though the symptom it would produce differs from what your question currently states; using a simplified example:
PS> $allcontacts=#(); $allcontacts[0] = 'one', 'two'
Index was outside the bounds of the array. # ERROR
...
That is, #() creates an empty array, which you cannot implicitly "extend" by accessing a non-existent index.
Using +=, as you do with your $contacts array, does work:
$allcontacts=#(); $allcontacts += , ('one', 'two')
Note the use of array-construction operator , to ensure that the RHS operand is added as a whole as a single new element; without it, multiple elements would be added, one for each element.
However, while "extending" an array with += works, in reality you're creating a new array behind the scenes every time, because arrays are by definition fixed-size collections.
With larger collections, this can become a performance issue, and it is better to use a list data type instead, such as [System.Collections.Generic.List[object]][1]:
$allcontacts = New-Object Collections.Generic.List[object]
$allcontacts.Add(('one', 'two'))
Note the need to enclose the array to add - as a single list element - in (...) so that the .Add() method recognizes it as a single argument.
Taking a step back: You can let PowerShell collect the $contact sub-arrays in the overall $allcontacts array by simply capturing the output from the entire foreach command:
$c = Get-Content -Path C:\temp\Contacts_Backup.vcf
$contact=#()
$allcontacts = #(foreach ($line in $c){
$contact += $line
if ($line -eq 'END:VCARD'){
# Output the $contact array as a *single* object,
# using ",", the array-construction operator
, $contact
# Reset for the next contact.
$contact=#()
}
})
$allcontacts will end up as a regular PowerShell array, typed [object[]].
Use of the array-subexpression operator (#(...)) is only necessary if you need to ensure that $allcontacts is an array even if the *.vcf file contains only one contact definition.
[1] A non-generic alternative is [System.Collections.ArrayList], but its downside is that its .Add() method returns a value, requiring you to suppress that value with, e.g., $null = $arrayList.Add(...) so as not to pollute PowerShell's output stream.
This should do exactly what you want:
Add-Type -AssemblyName System.Collections
[System.Collections.Generic.List[object]]$allContacts = #()
[System.Collections.Generic.List[string]]$contact = #()
$filePath = 'C:\temp\Contacts_Backup.vcf'
$endMarker = 'END:VCARD'
foreach($line in [System.IO.File]::ReadLines($filePath))
{
if( $line -eq $endMarker ) {
$allContacts.Add( $contact.ToArray() )
$contact.Clear()
}
else {
$contact.Add( $line )
}
}
# Ready. Show result.
foreach( $vcf in $allContacts ) {
"Contact: "
$vcf
}

Adding a hash-table to a hash-table

I'm trying to add a hash-table to a hash-table using powershell. However, Im getting the following error:
Item has already been added. Key in dictionary: 'Dev' Key being added: 'Dev'
Here's my code:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications += #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
What am I doing wrong?
I think what you want is something more like this:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
I will also point out that Hashtables often need to be handled defensively. Each key must be unique but values do not need to be. Here is the typical method of handling that:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
if($Applications.ContainsKey($i)){
#Do things here if there is already an entry for this key
}else{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
}
EBGreen's helpful answer offers a solution for what you likely meant to do.
To complement that with an explanation of why your code failed:
When you use + to "add" two hashtables, their entries are merged: in other words: the entries of the RHS are added to the LHS hashtable.
(Technically, a new instance is created with the merged entries.)
However - by sensible design - merging is only performed if the hashtables have no keys in common; otherwise, you'll get the error message you saw, complaining about duplicate keys.
If this safeguard weren't in place, you would lose data if the values associated with duplicate entries differ.
Since your loop repeatedly tried to merge a hashtable with the same keys directly into an existing hashtable, your 2nd loop iteration invariably failed.
You can verify this more simply:
$Applications = #{} # create empty hashtable.
# Merge a hashtable literal into $Applications.
# This works fine, because the two hashtables have no keys in common.
$Applications += #{ first = 1; second = 2 }
# $Application now contains the following: #{ first = 1; second = 2 }
# If you now try to add a hashtable with the same set of keys again,
# the operation invariably fails due to duplicate keys.
$Applications += #{ first = 10; second = 20 } # FAILS
# By contrast, adding a hashtable with unique keys works fine:
$Applications += #{ third = 3; fourth = 4 } # OK
# $Application now contains: #{ first = 1; second = 2; third = 3; fourth = 4 }
I was looking for a way to do some sort of "list of enum" and since I'm fairly new to PowerShell, the only thing I could think of is "HashTable of HashTables!".
Could not find a way to do it all "inline", and I got some "can't add a null key to a hashtable" error. After some experimentation I ended up with this, working exactly as I wanted :
$Enums = #{}
$Enums.Frequency = #{
Daily = "daily"
Weekly = "weekly"
Monthly = "monthly"
}
$Enums.SomethinElse = #{
Thing1 = "dflskdjf"
Thing2 = 12
Thing3 = $null
}
You can then use it simply
$Enums.SomethinElse.Thing2
or (if you add it to a Global Variable)
$Global:Enums.Frequency.Daily
It's not exactly the same as the example provided but directly in line with the topic, so I hope it helps someone ending up here :)

Powershell - Looping through objects in batches of 3

I have an object which contains 7 items.
$obj.gettype().name
Object[]
$obj.length
7
I want to loop through in batches of 3. I do NOT want to use the modulus function, I actually want to be able to create a new object with just the 3 items from that batch. Pseudo code:
$j=0
$k=1
for($i=0;$i<$obj.length;$i+=3){
$j=$i+2
$myTmpObj = $obj[$i-$j] # create tmpObj which contains items 1-3, then items 4-6, then 7 etc
echo "Batch $k
foreach($item in $myTmpObj){
echo $item
}
$k++
}
Batch 1
item 1
item 2
item 3
Batch 2
item 4
item 5
item 6
Batch 3
Item 7
Regards,
ted
Your pseudo code is almost real. I have just changed its syntax and used the range operator (..):
# demo input (btw, also uses ..)
$obj = 1..7
$k = 1
for($i = 0; $i -lt $obj.Length; $i += 3) {
# end index
$j = $i + 2
if ($j -ge $obj.Length) {
$j = $obj.Length - 1
}
# create tmpObj which contains items 1-3, then items 4-6, then 7 etc
$myTmpObj = $obj[$i..$j]
# show batches
"Batch $k"
foreach($item in $myTmpObj) {
$item
}
$k++
}
The output looks exactly as required.
See if this one may work as required (I assume your item n is an element of $obj)
$obj | % {$i=0;$j=0;$batches=#{}}
if($i!=3 and $batches["Batch $j"]) { $batches["Batch $j"]+=$_; $i+=1 }
else {$i=1;$j+=1;$batches["Batch $j"]=#($_)}
} {$batches}
Should return an HashTable ($batches) with keys such as "Batch 1", "Batch 2", ... each key related to an array of three items.