PowerShell array unshift - arrays

After reading this helpful article on the Windows PowerShell Blog, I realized I could "shift" off the first part of an array, but didn't know how to "unshift" or push elements onto the front of an array in PowerShell.
I am creating an array of hash objects, with the last read item pushed onto the array first. I'm wondering if there is a better way to accomplish this.
## Create a list of files for this collection, pushing item on top of all other items
if ($csvfiles[$collname]) {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }, $csvfiles[$collname] | %{$_}
}
else {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }
}
A couple of things to note:
I need to unroll the previous array element with a foreach loop %{$_}, because merely referencing the array would create a nested array structure. I have to have all the elements at the same level.
I need to differentiate between an empty array and one that contains elements. If I attempt to unroll an empty array, it places a $null element at the end of the array.
Thoughts?
PS: The reason the empty hash element produces a NULL value is that $null is treated as a scalar in PowerShell. For details, see https://connect.microsoft.com/PowerShell/feedback/details/281908/foreach-should-not-execute-the-loop-body-for-a-scalar-value-of-null.
ANSWER:
Looks like the best solution is to pre-create the empty array when necessary, rather than code around the $null issue. Here's the rewrite using a .NET ArrayList and a native PoSh array.
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = [System.Collections.ArrayList]#()
}
$csvfiles[$collname].insert(0, #{ repdate = $repdate; fileobj = $csv })
## NATIVE POSH SOLUTION...
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = #()
}
$csvfiles[$collname] = #{ repdate = $repdate; fileobj = $csv }, `
$csvfiles[$collname] | %{$_}

You might want to use ArrayList objects instead, as they support insertions at arbitrary locations. For example:
# C:\> $a = [System.Collections.ArrayList]#(1,2,3,4)
# C:\> $a.insert(0,6)
# C:\> $a
6
1
2
3
4

You can simply use a plus operator:
$array = #('bar', 'baz')
$array = #('foo') + $array
Note: this re-creates actually creates a new array instead of changing the existing one (but the $head, $tail = $array way of shifting you refer to works extactly in the same way).

Related

Powershell Compare 2 Arrays of Hashtables based on a property value

I have one array of hashtables like the one below:
$hashtable1 = #{}
$hashtable1.name = "aaa"
$hashtable1.surname =#()
$hashtable1.surname += "bbb"
$hashtable2 = #{}
$hashtable2.name = "aaa"
$hashtable2.surname =#()
$hashtable2.surname += "ccc"
$hashtable3 = #{}
$hashtable3.name = "bbb"
$hashtable3.surname = #()
$hashtable3.surname += "xxx"
$A = #($hashtable1; $hashtable2; $hashtable3)
I need to iterate though the array and I need to find out duplicates based on hashtable[].name
Then I need to group those hashtable.surname to hashtable[].surname so that the result will be an array of hashtables that will group all for name all the surnames:
$hashtable1.name = "aaa"
$hashtable1.surname = ("bbb","ccc")
$hashtable3.name = "bbb"
$hashtable3.surname = ("xxx")
I was looking into iterating to empty array
+
I have found this link:
powershell compare 2 arrays output if match
but I am not sure on how to reach into the elements of the hashtable.
My options:
I was wondering if -contain can do it.
I have read about compare-object but I am not sure it can be done like that.
(It looks a bit scary in the moment)
I am on PS5.
Thanks for your help,
Aster
You can group your array items by the names using a scriptblock like so.
Once grouped, you can easily build your output to do what you seek.
#In PS 7.0+ you can use Name directly but earlier version requires the use of the scriptblock when dealing with arrays of hashtables.
$Output = $A | Group-Object -Property {$_.Name} | % {
[PSCustomObject]#{
Name = $_.Name
Surname = $_.Group.Surname | Sort-Object -Unique
}
}
Here is the output variable content.
Name Surname
---- -------
aaa {bbb, ccc}
bbb xxx
Note
Improvements have been made in PS 7.0 that allows you to use simply the property name (eg: Name) in Group-Object for arrays of hashtables, just like you would do for any other arrays type. For earlier version though, these particular arrays must be accessed by passing the property in a scriptblock, like so: {$_.Name}
References
MSDN - Group_Object
SS64 - Group Object
Dr Scripto - Use a Script block to create custom groupings in PowerShell

PowerShell read Excel cells in a array and create xml doument?

I use an array reading Excel content which should be used to create an xml file, but have troubles to allocate the array values to the write xml statements.
Code of the array:
# Parse Excel columns A and B and store it in array "$headerInit"
for ($j=1; $j -le $rowMax-1; $j++)
{
$Obj = New-Object Psobject -Property #{
Value = $ws.Cells.Item($j,2).text
Header = $ws.Cells.Item($j,1).text
No = $j
}
$DataArray.add($obj) # Visualize the array content
}
$DataArray | Out-Gridview
Example of the array content:
No Header Value
...
12 Address 1234,0x3030
...
Here the code excerpt to create the xml document (values "1234" and "0x3030" are insert manually, but should be allocated from the array $DataArray):
...
# Write node <address1>
$xmlWriter.WriteStartElement('address1')
$xmlWriter.WriteAttributeString('address','1234')
$xmlWriter.WriteEndElement() # <-- Closing the node <address1>
# Write node <address2>
$xmlWriter.WriteStartElement('address2')
$xmlWriter.WriteAttributeString('address','0x3030')
$xmlWriter.WriteEndElement() # <-- Closing the node <address2>
...
How can I allocate the 2 values, divided by comma "1234,0x3030" from the array $DataArray, Header "Address" in the create xml statement? Any help is appreciated - Thanks.
Meanwhile I have solved the issue seperating the 2 values devided by comma.
Solution is:
$a,$b = $DataArray[12].Value.split(',')
Is there any easy way to alocate the values direct to the write xml statement without additional variables?
As you've found from my suggestion, you can use Split to separate the two values you want:
$addressLeft,$addressRight = $DataArray[12].Value.Split(',')
You can also reference the seperate parts of the split directly, as Split creates an array you can use the index number to access each element:
$DataArray[12].Value.split(',')[0]
$DataArray[12].Value.split(',')[1]
The other option is to split the value into two propertys when you build $DataArray:
$Obj = New-Object Psobject -Property #{
Value = $ws.Cells.Item($j,2).text
addressLeft = $ws.Cells.Item($j,2).text.Split(',')[0]
addressRight = $ws.Cells.Item($j,2).text.Split(',')[1]
Header = $ws.Cells.Item($j,1).text
No = $j
}
I've left the original Value property in, but you could remove it if you only want the split values.

How can I initialize an array of arrays (multidimensional) in PowerShell 5.0

I'm trying to partition out chunks of work using PowerShell (5.0) and am having a hard time instantiating a multidimensional array.
$n = 456;
$MaxChunks = 6;
$Chunks = #();
for($x = 0; $x -lt $MaxChunks; $x++)
{
Write-Host "Creating chunk $x"
$Chunks += #();
}
$Chunks.Count always returns 0 and I cannot access anything in $Chunks by index (i.e. $Chunks[0] is null).
Ultimately, my goal is to access the array located at $Chunks[$i] and add multiple System.Data.DataRow objects to it. However, as I've said, I'm not able to access the array at that index because that array is never instantiated.
I've read through this and this but am not quite able to translate the hashtable scenario to my situation.
Alternatively:
[System.Array] $chunks = [System.Array]::CreateInstance( [Int32], 3, 3 );
$chunks[0,0];
0
Not native PS, but works.
Replicate an empty array.
The same object is referenced in each element but in this particular case it's not a problem because standard arrays are read-only and recreated when elements are added via +=.
$Chunks = ,#() * $MaxChunks
Collect foreach output:
$Chunks = #(foreach ($x in 1..$MaxChunks) { ,#() })
The outer #() handles a theoretically possible case when $MaxChunks = 1.
You can use a more verbose (arguably slower) Write-Output -NoEnumerate:
$Chunks = #(foreach ($x in 1..$MaxChunks) { echo -NoEnumerate #() })
And in case the sub-arrays will be modified a lot, use ArrayList instead of +=:
$Chunks = #(foreach ($x in 1..$MaxChunks) { ,[Collections.ArrayList]#() })
and later:
$Chunks[0].Add($something) >$null
P.S. Don't use += to generate entire arrays from scratch regardless of its size as it's a terrible method that recreates and copies the entire array each time; there's a much faster and simpler output collection via loop statements such as foreach, for, while; there's ArrayList object for the case of frequent non-sequential modifications of an array.

How to index into PSObject Array of Hashtables

I'm working in powershell with an imported CSV. I have a data set like this:
ID First_Name Last_Nam
-- ---------- --------
2314 Kenny Anderson
21588 Mark Anderson
2547 Ben Andrews
5797 Benjamin Armour
Except with 2000 people and many more columns. Currently the data is stored as a series of hashes in a #{ID = "..",First_Name:"..",Last_Name:".."} and these are stored in a System Object array. I want to store each hash as an index in an array. I want to get the hashtable at that index, but I don't know how to into the System.Object Array. Here's my code:
$csv = import-csv $csv_name
$row = #(0)*csv.length
$hash = #{}
for($i =0; $i -lt $csv.length; $i++){
$row[$i] += $csv[$i]
}
#error: cannot convert "#{ID : "..", First_Name: "..", Last_Name:".." to Systems.Int32
for($i =0; $i -lt $csv.length; $i++){
$csv[$i].psobject.properties | Foreach { $hash[$_.Name] = $_.Value }
$row[$i]+=$hash
}
#error: Cannot convert systems.collections.hashtable into Systems.Int32
I'm looking for a way to index the array so I can get the hashtable at that index. the The first one, with pointers to the hashtables accessible through the array, is what I think would be the easiest to manipulate. If there's an easier way to get a specific hashtable just from the System.Object[] itself, please tell me.
I should add I don't know the names or amount of the columns in advance.
The return value of Import-Csv is not an array of [hashtable]s, it's an array of [psobject]s, where each column is a property.
There's no need to build any kind of array to get an individual object by index.
$csv = import-csv $csv_name
$csv[0]
$csv[1].First_Name
# etc
The errors in your code have nothing to do with the question you posed though; you're getting errors trying to add objects to an array, because you're actually trying to add an object or hashtable to an existing array element of type integer.
You don't need to precreate the array with a bunch of zeros, so this line isn't needed:
$row = #(0)*csv.length
Instead, you can create an empty array:
$row = #()
Then, you can just add to the array itself:
$row += $hash

create a new variable for each loop in a foreach-loop

How can I put $org into an array together with $count?
Like this example array:
$myArray = #{
1="SampleOrg";
2="AnotherSampleOrg"
}
Another example:
$myArray = #{
$count="$org";
$count="$org"
}
Example foreach:
$count=0;get-organization | foreach {$count++; $org = $_.Name.ToString();write-host $count -nonewline;write-host " $org"}
$answer = read-host "Select 1-$count"
The above will display:
1 SampleOrg
2 AnotherSampleOrg
Select 1-2:
What I would like to do afterwards is to put the array to use in a switch.
Example:
switch ($answer)
{
1 {$org=myArray[1]} #<-- or whatever that corresponds to "SampleOrg"
2 {$org=myArray[2]} #<-- or whatever that corresponds to "AnotherSampleOrg"
}
You have to initialize your hashtable somewhere before the loop:
$myArray = #{}
and add a
$myArray.Add($count, $org)
to your foreach-loop.
EDIT: For the discussion about hastable/array see the whole thread ;) I just kept the name of the variable from the original posting
Looks like you're confusing arrays and Hashtables. Arrays are ordered, and indexed by an numeric value. Hashtables are associative, and indexed by any value that has equality defined.
This is array syntax
$arr = #(1,2,3)
and this is Hashtable syntax
$ht = #{red=1;blue=2;}
For your question, the following will work
$orgs = #(get-organization | % { $_.Name })
this will create a 0 based array, mapping int -> OrgName, so
$orgs[$answer]
will get the correct name. Or if you're using 1 based indexing
$orgs[$answer-1]
Note, I removed the switch, as there's no reason for it.

Resources