How create multidimensional dynamic array in powershell - powershell

How i can create array like:
$a -> [1] ->
[1] = value1
[2] = value2
.................
[n] = valueN
[2] ->
[1] = value1
[2] = value2
.................
[n] = valueN
and so on.
Thank you
i have tried like this:
$b = #{}
$b[0][0] = 1
$b[0][1] = 2
$b[0][2] = 3
$b[1][0] = 4
$b[1][1] = 5
$b[1][2] = 6
$b
But it doesn't give the required output

I think this has been posted multiple times, but simply declare the array and give it values:
[array]$1 = "value1","value2"
[array]$2 = "value1","value2"
[array]$a = $1,$2
$a[0][0]
will output -> value1 from the first
Please note that declaring the array with [array] is for clarifiying, it is not necessary. If you add comma-seperated values the variable automatically is an array.
EDIT:
What you have tried is a hashtable. A hashtable contains a key and a value. An array is only a list of values. A hashtable is created as follows:
$b = #{
1 = #{
1 = "value1"
2 = "value2"
}
2 = #{
1 = "value1"
2 = "value2"
}
3 = "value3"
}
$b
As you can see, you can add as many sublevels as you like. To show the value of the first "value1" type:
$b[1].1

You could use the class approach as well I would prefer:
Add-Type -AssemblyName System.Collections
# Create class with needed members
class myListObject {
[int]$myIndex
[System.Collections.Generic.List[string]]$myValue = #()
}
# generic list of class
[System.Collections.Generic.List[myListObject]]$myList = #()
#create and add objects to generic list
$myObject = [myListObject]::new()
$myObject.myIndex = 1
$myObject.myValue = #( 'value1', 'value2' )
$myList.Add( $myObject )
$myObject = [myListObject]::new()
$myObject.myIndex = 2
$myObject.myValue = #( 'value3', 'value4' )
$myList.Add( $myObject )
# search items
$myList | Where-Object { $_.myIndex -eq 1 } | Select-Object -Property myValue
$myList | Where-Object { $_.myValue.Contains('value3') } | Select-Object -Property myIndex

The Windows Powershell in Action answer.
$2d = New-Object -TypeName 'object[,]' -ArgumentList 2,2
$2d.Rank
#2
$2d[0,0] = "a"
$2d[1,0] = 'b'
$2d[0,1] = 'c'
$2d[1,1] = 'd'
$2d[1,1]
#d
# slice
$2d[ (0,0) , (1,0) ]
#a
#b
# index variable
$one = 0,0
$two = 1,0
$pair = $one,$two
$2d[ $pair ]
#a
#b

Related

Select some properties of an array of object and rename it

I have an array of objects, which have the property of p1, p2, p3, p4.
$objectArray = [PSCustomObject]#{
p1 = "Value1"; p2 = "Value2"; p3 = "Value3"; p4 = "Value4"
},[PSCustomObject]#{
p1 = "Value5"; p2 = "Value6"; p3 = "Value7"; p4 = "Value8"
}
And I have a property-name-mapping hashtable, for example
$propertyReplacements = #{
"p1" = "x1"
"p2" = "y1"
}
How to select only the properties which exist in the keys of hash table $propertyReplacements; and rename the property name to the values in the hash table?
The result will be
[PSCustomObject]#{
x1 = "Value1"; y1 = "Value2";
},[PSCustomObject]#{
x1 = "Value5"; y1 = "Value6";
}
The properties filtering can be
$objectArray | select -Property #($propertyReplacements.Keys) |
....
The following uses an ordered dictionary and casting [pscustomobject] to create new objects, I really don't think Select-Object is the proper tool for this.
# use a temporary ordered dictionary
$tmp = [ordered]#{}
# enumerate each object
foreach($obj in $objectArray) {
# store the PSMemberInfoIntegratingCollection for later access
$properties = $obj.PSObject.Properties
# enumerate the hash keys
foreach($key in $propertyReplacements.Keys) {
# if the hash key exists as property Name in this object
if($prop = $properties.Item($key)) {
# create a new property Name using the Value of the hashtable
# and keep the current Value of the value
$tmp[$propertyReplacements[$prop.Name]] = $prop.Value
}
}
# create a new object casting this type accelerator
[pscustomobject] $tmp
# and clear the dictionary for the next object
$tmp.Clear()
}
Here is another solution
foreach($k in $propertyReplacements.Keys) {
if ($k -ne $propertyReplacements.$k) {
$objectArray = $objectArray | select *,{n=$propertyReplacements.$k; $e={$_.$k}}
$objectArray = $objectArray | select -ExcludeProperty $k
}
}
# $objectArray has updated property names now

How to add values from nested hashTable values

Below is my code. I would like to add then read individual values.
$ht = #{
'Hcohesity01' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
'Hcohesity02' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
}
$ht['Hcohesity02']['percentage']['server4'] = 20
foreach ( $value -in $ht['Hcohesity02']['percentage'].Values){
$server5 += $value
}
$ht['Hcohesity02']['percentage']['server5'] =$server5
$ht['Hcohesity02']['percentage']
Below code is not working , any idea ?
foreach ( $value -in $ht['Hcohesity02']['percentage'].Values)
The only real mistake in your code is by writing -in in the foreach loop. That should have been just in.
Instead of using a loop to add-up the values, you can use a one-liner with Measure-Object:
$ht = #{
'Hcohesity01' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
'Hcohesity02' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
}
$ht['Hcohesity02']['percentage']['server4'] = 20
# instead of a foreach loop:
# $server5 = 0 # initialize
# foreach ( $value in $ht['Hcohesity02']['percentage'].Values) {
# $server5 += $value
# }
# you can do this:
$server5 = ($ht['Hcohesity02']['percentage'].Values | Measure-Object -Sum).Sum
$ht['Hcohesity02']['percentage']['server5'] = $server5
$ht['Hcohesity02']['percentage']
Result:
Name Value
---- -----
server2 5
server5 39
server3 10
server1 4
server4 20
If you don't like addressing the properties with the [property] syntax, you can also use dot notation as hoppy7 showed in his answer:
$ht.'Hcohesity02'.'percentage'.Values # and so on
Its not quite clear what you're trying to do. If its just iterating through your foreach loop and assigning a value, you need to ditch the dash on "-in". It should look like the below:
foreach ($value in $ht.Hcohesity02.percentage.Values)
{
# do some stuff
}

Powershell - Expand subArray and rename fields

I have a source array like this
$myArray = (
#{ id = "1"; name = "first item";
subArray = (
#{ id = "A"; name = "first subitem" },
#{ id = "B"; name = "second subitem" } )
},
#{ id = "2"; name = "second item";
subArray = (
#{ id = "C"; name = "third subitem" },
#{ id = "D"; name = "fourth subitem" } )
}
)
I need to extract the relations between the parent and child arrays like following:
source target
-----------------
1 A
1 B
2 C
2 D
I have come up with a following code to achieve that
$myArray | ForEach-Object {
$id = $_.id
$_.subArray | ForEach-Object {
#{
source = $id
target = $_.id
}
}
}
I wonder if there is some more straight forward solution.
Edit:
Based on Marsze answer - slightly modified solution
$myArray| ForEach-Object
{$a=$_; $a.subArray | Select-Object #{n="source";e={$a.id}},#{n="target";e={$_.id}}
}
Looks pretty straightforward to me. You could use foreach loops instead of the pipeline cmdlet, which is faster and has the advantage, that you can reference variables on each level directly.
Also I recommended converting to PSCustomObject for the proper output format:
foreach ($a in $myArray) {
foreach ($b in $a.subArray) {
[PSCustomObject]#{source = $a.id; target = $b.id}
}
}
Alternatively with New-Object:
foreach ($a in $myArray) {
foreach ($b in $a.subArray) {
New-Object PSObject -Property #{source = $a.id; target = $b.id}
}
}
Or the Select-Object version:
foreach ($a in $myArray) {
$a.subArray | select #{n="source";e={$a.id}},#{n="target";e={$_.id}}
}

Import-CSV Nested HashTable

Say I have a CSV file that looks like:
arch,osversion,kb
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
How would this CSV get imported into structured hash table that looks like this?
32 -> 6.1 -> KB1 -> http://kb1
-> 6.2 -> KB2 -> http://kb2
64 -> 6.1 -> KB3 -> http://kb3
-> 6.2 -> KB4 -> http://kb4
The command below yields http://kb1:
$data['32'].'6.1'.'KB1'
Probably Group-Object is what you want.
$csv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$data = ConvertFrom-Csv $csv
$data | Group-Object -Property arch
Or maybe closer to what you want to query:
$groups = $data | Group-Object -Property arch, osversion, kb
($groups | ? Name -eq '32, 6.1, KB1').Group.link
You could even use variables...
$a = '32'
$o = '6.1'
$k = 'KB1'
($groups | ? Name -eq "$a, $o, $k").Group.link
From this, you can determine if such a pattern works for you.
Interesting task. The following code snippet could help (solves arch duplicates):
Remove-Variable data*, aux* -ErrorAction SilentlyContinue ### clear for debugging purposes
$datacsv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$datac = ConvertFrom-Csv $datacsv
$datag = #{}
$datac | ForEach-Object {
$auxLeaf = #{ $_.kb = $_.link }
$auxParent = #{ $_.osversion = $auxLeaf }
if ( $datag.ContainsKey( $_.arch) ) {
$auxParent += $datag[ $_.arch]
}
$datag.Set_Item( $_.arch, $auxParent )
}
Then, $datag['32']['6.1']['KB1'] returns desired value http://kb1
Another interesting problem: solve osversion duplicates in a particular arch:
Remove-Variable data*, aux* -ErrorAction SilentlyContinue ### clear for debugging purposes
$datacsv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.1,KB5,http://kb5
32,6.1,KB7,http://kb7
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$datac = ConvertFrom-Csv $datacsv
$datag = #{}
$datac | ForEach-Object {
$auxLeaf = #{ $_.kb = $_.link }
$auxParent = #{ $_.osversion = $auxLeaf }
if ( $datag.ContainsKey( $_.arch) ) {
if ( $datag[$_.arch].ContainsKey($_.osversion) ) {
$auxLeaf += $datag[$_.arch][$_.osversion]
$auxParent = #{ $_.osversion = $auxLeaf }
} else {
$auxParent += $datag[ $_.arch]
}
}
$datag.Set_Item( $_.arch, $auxParent )
}
The latter code snippet is roughly equivalent to
$datag =
#{
'32' = #{ '6.1' = #{ 'KB1'='http://kb1';
'KB5'='http://kb5';
'KB7'='http://kb7' };
'6.2' = #{ 'KB2'='http://kb2' }
};
'64' = #{ '6.1' = #{ 'KB3'='http://kb3' };
'6.2' = #{ 'KB4'='http://kb4' }
}
}

Looping through a hash, or using an array in PowerShell

I'm using this (simplified) chunk of code to extract a set of tables from SQL Server with BCP.
$OutputDirectory = 'c:\junk\'
$ServerOption = "-SServerName"
$TargetDatabase = "Content.dbo."
$ExtractTables = #(
"Page"
, "ChecklistItemCategory"
, "ChecklistItem"
)
for ($i=0; $i -le $ExtractTables.Length – 1; $i++) {
$InputFullTableName = "$TargetDatabase$($ExtractTables[$i])"
$OutputFullFileName = "$OutputDirectory$($ExtractTables[$i])"
bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
}
It works great, but now some of the tables need to be extracted via views, and some don't. So I need a data structure something like this:
"Page" "vExtractPage"
, "ChecklistItemCategory" "ChecklistItemCategory"
, "ChecklistItem" "vExtractChecklistItem"
I was looking at hashes, but I'm not finding anything on how to loop through a hash. What would be the right thing to do here? Perhaps just use an array, but with both values, separated by space?
Or am I missing something obvious?
Shorthand is not preferred for scripts; it is less readable. The %{} operator is considered shorthand. Here's how it should be done in a script for readability and reusability:
Variable Setup
PS> $hash = #{
a = 1
b = 2
c = 3
}
PS> $hash
Name Value
---- -----
c 3
b 2
a 1
Option 1: GetEnumerator()
Note: personal preference; syntax is easier to read
The GetEnumerator() method would be done as shown:
foreach ($h in $hash.GetEnumerator()) {
Write-Host "$($h.Name): $($h.Value)"
}
Output:
c: 3
b: 2
a: 1
Option 2: Keys
The Keys method would be done as shown:
foreach ($h in $hash.Keys) {
Write-Host "${h}: $($hash.$h)"
}
Output:
c: 3
b: 2
a: 1
Additional information
Be careful sorting your hashtable...
Sort-Object may change it to an array:
PS> $hash.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
PS> $hash = $hash.GetEnumerator() | Sort-Object Name
PS> $hash.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
This and other PowerShell looping are available on my blog.
Christian's answer works well and shows how you can loop through each hash table item using the GetEnumerator method. You can also loop through using the keys property. Here is an example how:
$hash = #{
a = 1
b = 2
c = 3
}
$hash.Keys | % { "key = $_ , value = " + $hash.Item($_) }
Output:
key = c , value = 3
key = a , value = 1
key = b , value = 2
You can also do this without a variable
#{
'foo' = 222
'bar' = 333
'baz' = 444
'qux' = 555
} | % getEnumerator | % {
$_.key
$_.value
}
I prefer this variant on the enumerator method with a pipeline, because you don't have to refer to the hash table in the foreach (tested in PowerShell 5):
$hash = #{
'a' = 3
'b' = 2
'c' = 1
}
$hash.getEnumerator() | foreach {
Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}
Output:
Key = c and Value = 1
Key = b and Value = 2
Key = a and Value = 3
Now, this has not been deliberately sorted on value, the enumerator simply returns the objects in reverse order.
But since this is a pipeline, I now can sort the objects received from the enumerator on value:
$hash.getEnumerator() | sort-object -Property value -Desc | foreach {
Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}
Output:
Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Here is another quick way, just using the key as an index into the hash table to get the value:
$hash = #{
'a' = 1;
'b' = 2;
'c' = 3
};
foreach($key in $hash.keys) {
Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
}
About looping through a hash:
$Q = #{"ONE"="1";"TWO"="2";"THREE"="3"}
$Q.GETENUMERATOR() | % { $_.VALUE }
1
3
2
$Q.GETENUMERATOR() | % { $_.key }
ONE
THREE
TWO
A short traverse could be given too using the sub-expression operator $( ), which returns the result of one or more statements.
$hash = #{ a = 1; b = 2; c = 3}
forEach($y in $hash.Keys){
Write-Host "$y -> $($hash[$y])"
}
Result:
a -> 1
b -> 2
c -> 3
If you're using PowerShell v3, you can use JSON instead of a hashtable, and convert it to an object with Convert-FromJson:
#'
[
{
FileName = "Page";
ObjectName = "vExtractPage";
},
{
ObjectName = "ChecklistItemCategory";
},
{
ObjectName = "ChecklistItem";
},
]
'# |
Convert-FromJson |
ForEach-Object {
$InputFullTableName = '{0}{1}' -f $TargetDatabase,$_.ObjectName
# In strict mode, you can't reference a property that doesn't exist,
#so check if it has an explicit filename firest.
$outputFileName = $_.ObjectName
if( $_ | Get-Member FileName )
{
$outputFileName = $_.FileName
}
$OutputFullFileName = Join-Path $OutputDirectory $outputFileName
bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
}