I have a problem with array as following with only one line:
$list = #()
$list = (("ResourceGroup","Vm1"))
$list | ForEach-Object -Parallel {
write-output $_[0] $_[1]
}
If I loop that array with one line, PowerShell prints the first 2 letter of each word. If I put 2 or more row like following:
$list = #()
$list = (("ResourceGroup","Vm1"),`
("ResourceGroup","Vm2")
)
PowerShell print correctly the values inside.
There is a way to print correctly the value of an array with only one line?
("ResourceGroup","Vm1") is interpreted as one array with two string elements, where as (("ResourceGroup","Vm1"), ("ResourceGroup","Vm2")) is interpreted as one array with 2 array elements, can be also called a jagged array. If you want to ensure that the first example is treated the same as the second example, you can use the comma operator ,:
$list = , ("ResourceGroup","Vm1")
$list | ForEach-Object -Parallel {
Write-Output $_[0] $_[1]
}
To put it in perspective:
$list = ("ResourceGroup","Vm1")
$list[0].GetType() # => String
$list = , ("ResourceGroup","Vm1")
$list[0].GetType() # => Object[]
Write-Output with the -NoEnumerate switch combined with the Array subexpression operator #( ) can be another, more verbose, alternative:
$list = #(Write-Output "ResourceGroup", "Vm1" -NoEnumerate)
$list | ForEach-Object -Parallel {
Write-Output $_[0] $_[1]
}
Related
I have a way of doing Arrays in other languagues like this:
$x = "David"
$arr = #()
$arr[$x]["TSHIRTS"]["SIZE"] = "M"
This generates an error.
You are trying to create an associative array (hash). Try out the following
sequence of commands
$arr=#{}
$arr["david"] = #{}
$arr["david"]["TSHIRTS"] = #{}
$arr["david"]["TSHIRTS"]["SIZE"] ="M"
$arr.david.tshirts.size
Note the difference between hashes and arrays
$a = #{} # hash
$a = #() # array
Arrays can only have non-negative integers as indexes
from powershell.com:
PowerShell supports two types of multi-dimensional arrays: jagged arrays and true multidimensional arrays.
Jagged arrays are normal PowerShell arrays that store arrays as elements. This is very cost-effective storage because dimensions can be of different size:
$array1 = 1,2,(1,2,3),3
$array1[0]
$array1[1]
$array1[2]
$array1[2][0]
$array1[2][1]
True multi-dimensional arrays always resemble a square matrix. To create such an array, you will need to access .NET. The next line creates a two-dimensional array with 10 and 20 elements resembling a 10x20 matrix:
$array2 = New-Object 'object[,]' 10,20
$array2[4,8] = 'Hello'
$array2[9,16] = 'Test'
$array2
for a 3-dimensioanl array 10*20*10
$array3 = New-Object 'object[,,]' 10,20,10
To extend on what manojlds said above is that you can nest Hashtables. It may not be a true multi-dimensional array but give you some ideas about how to structure the data. An example:
$hash = #{}
$computers | %{
$hash.Add(($_.Name),(#{
"Status" = ($_.Status)
"Date" = ($_.Date)
}))
}
What's cool about this is that you can reference things like:
($hash."Name1").Status
Also, it is far faster than arrays for finding stuff. I use this to compare data rather than use matching in Arrays.
$hash.ContainsKey("Name1")
Hope some of that helps!
-Adam
Knowing that PowerShell pipes objects between cmdlets, it is more common in PowerShell to use an array of PSCustomObjects:
$arr = #(
[PSCustomObject]#{Name = 'David'; Article = 'TShirt'; Size = 'M'}
[PSCustomObject]#{Name = 'Eduard'; Article = 'Trouwsers'; Size = 'S'}
)
Or for older PowerShell Versions (PSv2):
$arr = #(
New-Object PSObject -Property #{Name = 'David'; Article = 'TShirt'; Size = 'M'}
New-Object PSObject -Property #{Name = 'Eduard'; Article = 'Trouwsers'; Size = 'S'}
)
And grep your selection like:
$arr | Where {$_.Name -eq 'David' -and $_.Article -eq 'TShirt'} | Select Size
Or in newer PowerShell (Core) versions:
$arr | Where Name -eq 'David' | Where Article -eq 'TShirt' | Select Size
Or (just get the size):
$arr.Where{$_.Name -eq 'David' -and $_.Article -eq 'TShirt'}.Size
Addendum 2020-07-13
Syntax and readability
As mentioned in the comments, using an array of custom objects is straighter and saves typing, if you like to exhaust this further you might even use the ConvertForm-Csv (or the Import-Csv) cmdlet for building the array:
$arr = ConvertFrom-Csv #'
Name,Article,Size
David,TShirt,M
Eduard,Trouwsers,S
'#
Or more readable:
$arr = ConvertFrom-Csv #'
Name, Article, Size
David, TShirt, M
Eduard, Trouwsers, S
'#
Note: values that contain spaces or special characters need to be double quoted
Or use an external cmdlet like ConvertFrom-SourceTable which reads fixed width table formats:
$arr = ConvertFrom-SourceTable '
Name Article Size
David TShirt M
Eduard Trouwsers S
'
Indexing
The disadvantage of using an array of custom objects is that it is slower than a hash table which uses a binary search algorithm.
Note that the advantage of using an array of custom objects is that can easily search for anything else e.g. everybody that wears a TShirt with size M:
$arr | Where Article -eq 'TShirt' | Where Size -eq 'M' | Select Name
To build an binary search index from the array of objects:
$h = #{}
$arr | ForEach-Object {
If (!$h.ContainsKey($_.Name)) { $h[$_.Name] = #{} }
If (!$h[$_.Name].ContainsKey($_.Article)) { $h[$_.Name][$_.Article] = #{} }
$h[$_.Name][$_.Article] = $_ # Or: $h[$_.Name][$_.Article]['Size'] = $_.Size
}
$h.david.tshirt.size
M
Note: referencing a hash table key that doesn't exist in Set-StrictMode will cause an error:
Set-StrictMode -Version 2
$h.John.tshirt.size
PropertyNotFoundException: The property 'John' cannot be found on this object. Verify that the property exists.
Here is a simple multidimensional array of strings.
$psarray = #(
('Line' ,'One' ),
('Line' ,'Two')
)
foreach($item in $psarray)
{
$item[0]
$item[1]
}
Output:
Line
One
Line
Two
Two-dimensional arrays can be defined this way too as jagged array:
$array = New-Object system.Array[][] 5,5
This has the nice feature that
$array[0]
outputs a one-dimensional array, containing $array[0][0] to $array[0][4].
Depending on your situation you might prefer it over $array = New-Object 'object[,]' 5,5.
(I would have commented to CB above, but stackoverflow does not let me yet)
you could also uses System.Collections.ArrayList to make a and array of arrays or whatever you want.
Here is an example:
$resultsArray= New-Object System.Collections.ArrayList
[void] $resultsArray.Add(#(#('$hello'),2,0,0,0,0,0,0,1,1))
[void] $resultsArray.Add(#(#('$test', '$testagain'),3,0,0,1,0,0,0,1,2))
[void] $resultsArray.Add("ERROR")
[void] $resultsArray.Add(#(#('$var', '$result'),5,1,1,0,1,1,0,2,3))
[void] $resultsArray.Add(#(#('$num', '$number'),3,0,0,0,0,0,1,1,2))
One problem, if you would call it a problem, you cannot set a limit. Also, you need to use [void] or the script will get mad.
Using the .net syntax (like CB pointed above)
you also add coherence to your 'tabular' array...
if you define a array...
and you try to store diferent types
Powershell will 'alert' you:
$a = New-Object 'byte[,]' 4,4
$a[0,0] = 111; // OK
$a[0,1] = 1111; // Error
Of course Powershell will 'help' you
in the obvious conversions:
$a = New-Object 'string[,]' 2,2
$a[0,0] = "1111"; // OK
$a[0,1] = 111; // OK also
Another thread pointed here about how to add to a multidimensional array in Powershell. I don't know if there is some reason not to use this method, but it worked for my purposes.
$array = #()
$array += ,#( "1", "test1","a" )
$array += ,#( "2", "test2", "b" )
$array += ,#( "3", "test3", "c" )
Im found pretty cool solvation for making arrays in array.
$GroupArray = #()
foreach ( $Array in $ArrayList ){
$GroupArray += #($Array , $null)
}
$GroupArray = $GroupArray | Where-Object {$_ -ne $null}
Lent from above:
$arr = ConvertFrom-Csv #'
Name,Article,Size
David,TShirt,M
Eduard,Trouwsers,S
'#
Print the $arr:
$arr
Name Article Size
---- ------- ----
David TShirt M
Eduard Trouwsers S
Now select 'David'
$arr.Where({$_.Name -eq "david"})
Name Article Size
---- ------- ----
David TShirt M
Now if you want to know the Size of 'David'
$arr.Where({$_.Name -eq "david"}).size
M
This question already has an answer here:
Powershell: Piping output of pracl command to array
(1 answer)
Closed 1 year ago.
Using Get-ChildItem I have pulled a list of files that meet a criteria, then split a part of the Basename and want to build an array with that part of the name. I can do that successfully, except the array returns on long string. I'd like each part of the array to return on a new line.
Script:
$files = GCI "\\Paths" -Recurse | Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)}
$name = ""
foreach($file in $files){
$file = $file.basename.Split(".")[0]
$array += $file
}
I also tried the following with no luck:
$files = GCI "\\Paths" -Recurse | Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)}
$name = ""
foreach($file in $files){
$file = $file.basename.Split(".")[0]
$array+= $file -split "`n"
}
Current outcome when calling $array:
file01file02file03file04
Desired outcome when calling $array:
file01
file02
file03
file04
The string is returned because $array is not an array. It is typed at assignment and its first assignment is a string. Therefore it keeps appending new values to that string.
You may do the following instead:
$array = foreach($file in $files){
$file.basename.Split(".")[0]
}
When iterated values are output within a foreach statement, that statement output can be captured into a variable. Each value will be an element of an array.
As an aside, the += syntax to add elements to an array is inefficient because a new array is created each time after retrieving all the contents of the current array.
You're already returning an array, so just narrow it down to what you're assigning to your variable.
$files = GCI "\\Paths" -Recurse |
Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)} |
ForEach-Object -Process {
$_.basename.Split(".")[0]
}
Or, just assign a variable to your foreach loop removing the output to an array.:
$arr = foreach (...)
I have a powershell script and a txt database with different number of elements per line.
My txt file is list.txt:
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
My powershell script search.ps1:
#Get file path
$path = Split-Path $script:MyInvocation.MyCommand.Path
$search = #()
Get-Content -LiteralPath "$path\list.txt" | ForEach-Object {
$search += $_
}
So, how to convert each line as a element of array? As this:
$search = #(("10345","doomsday","life","hope","run","stone"),("10346","ride","latest","metal"))
To operate as:
echo $search[0][0]
Here's a concise PSv4+ solution:
$search = (Get-Content -LiteralPath $path\list.txt).ForEach({ , ($_ -split ',') })
The .ForEach() method operates on each line read from the input file by Get-Content.
$_ -split ',' splits each line into an array of strings by separator ,
, (...) wraps this array in an aux. single-item array to ensure that the array is effectively output as a whole, resulting in an array of arrays as the overall output.
Note: Strictly speaking, the .ForEach() method outputs a [System.Collections.ObjectModel.Collection[psobject]] collection rather than a regular PowerShell array ([object[]]), but for all practical purposes the two types act the same.
Note: The .ForEach() method was chosen as a faster alternative to a pipeline with the ForEach-Object (%) cmdlet.
Note that the .ForEach() method requires storing the input collection in memory as a whole first.
A faster and more memory-efficient, though perhaps slightly obscure alternative is to use a switch statement with the -file option:
$search = switch -file $path\list.txt { default { , ($_ -split ',') } }
switch -file processes each line of the specified file.
Since each line should be processed, only a default branch is used, in which the desired splitting is performed.
Use -split. A code snippet you can debug in ISE or VSCode below.
$x1 = #'
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
'#
$data = $x1 -split "`r`n"
$data.Count
$data[0] -split ","
$arr = #()
foreach ($row in $data)
{
$arr += ,($row -split ",")
}
"arr"
$arr
"0,3"
$arr[0][3]
"1,3"
$arr[1][3]
So you can split each line in your file returned from Get-Content and add it to your new array which lets you reference how you wanted...
There are other ways you can use your data depending on your needs.
Assuming you do not want each item quoted, you might consider to not using the -Split operator but just evaluating each line with the Invoke-Expression cmdlet or using a more secure [ScriptBlock] for this:
$Search = Get-Content ".\list.txt" | ForEach-Object {,#(&([ScriptBlock]::Create($_)))}
I have a fairly basic multidimensional array which looks something like this:
2017,123
2017,25
2018,5
2018,60
2017,11
I wish to run a ForEach() loop or similar function to total the numbers in the second element based on the year indicated in the first so that I end up with an output like this:
2017,159
2018,65
How do I best accomplish this?
The following solution is concise, but not fast:
# input array
$arr =
(2017,123),
(2017,25),
(2018,5),
(2018,60),
(2017,11)
# Group the sub-arrays by their 1st element and sum all 2nd elements
# in each resulting group.
$arr | Group-Object -Property { $_[0] } | ForEach-Object {
, ($_.Name, (($_.Group | ForEach-Object { $_[1] } | Measure-Object -Sum).Sum))
}
Assuming your array looks like "$array" this will give you what you need:
$2017total = 0
$2018total = 0
$array = "2017,123",
"2017,25",
"2018,5",
"2018,60",
"2017,11" | % {
if ($_ -match '2017') {
$2017 = ($_ -split ',')[1]
$2017total += $2017
}
else {
$2018 = ($_ -split ',')[1]
$2018total += $2018
}
}
Write-Host "2017,$2017total"
Write-Host "2018,$2018total"
In PowerShell v2, I'm trying to add only unique values to an array. I've tried using an if statement that says, roughly, If (-not $Array -contains 'SomeValue'), then add the value, but this only ever works the first time. I've put a simple code snippet that shows what I'm doing that doesn't work and what I've done as a workaround that does work. Can someone please let me know where my issue is?
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will not work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If (-not $IncorrectArray -contains $Word)
{
$IncorrectArray += $Word
}
}
Write-Host ('IncorrectArray Count: ' + $IncorrectArray.Length)
# This works as expected
$CorrectArray = #()
ForEach ($Word in $Words)
{
If ($CorrectArray -contains $Word)
{
}
Else
{
$CorrectArray += $Word
}
}
Write-Host ('CorrectArray Count: ' + $CorrectArray.Length)
The Result of the first method is an array containing only one value: "Hello". The second Method contains two values: "Hello" & "World". Any help is greatly appreciated.
To fix your code, try -notcontains or at least WRAP your contains-test in parantheses. Atm. your test reads:
If "NOT array"(if array doens't exist) contains word.
This makes no sense. What you want is:
If array does not contain word..
That's written like this:
If (-not ($IncorrectArray -contains $Word))
-notcontains is even better, as #dugas suggested.
The first time around, you evaluate -not against an empty array, which returns true, which evaluates to: ($true -contains 'AnyNonEmptyString') which is true, so it adds to the array. The second time around, you evaluate -not against a non-empty array, which returns false, which evaluates to: ($false -contains 'AnyNonEmptyString') which is false, so it doesn't add to the array.
Try breaking your conditions down to see the problem:
$IncorrectArray = #()
$x = (-not $IncorrectArray) # Returns true
Write-Host "X is $x"
$x -contains 'hello' # Returns true
then add an element to the array:
$IncorrectArray += 'hello'
$x = (-not $IncorrectArray) # Returns false
Write-Host "X is $x"
$x -contains 'hello' # Returns false
See the problem? Your current syntax does not express the logic you desire.
You can use the notcontains operator:
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If ($IncorrectArray -notcontains $Word)
{
$IncorrectArray += $Word
}
}