$array=#("blue","green","black")
[string]$input=Read-host "Input:"
$array[$input]
If I enter the number 1 into the input this gives no output. When I
Write-host $input
I get
System.Collections.ArrayList+ArrayListEnumeratorSimple
What I'm looking to do next is:
$inputlist=#("0")
$inputlist += $array[$input]
But I seem to end up with an array where each element is a single letter. I would like them to be one string in $inputlist[1].
$Input is an automatic variable and shouldn't be used in the way you do. Give your variable a different name and the problem will disappear:
PS C:\> $array = #('blue', 'green', 'black')
PS C:\> $val = Read-host 'Input'
Input: 2
PS C:\> $val
2
PS C:\> $val.GetType().FullName
System.String
PS C:\> $array[$val]
green
Splitting the string into an array of single characters can be handled by casting the string to a character array and then to a string array:
PS C:\> [string[]][char[]]$array[$val]
g
r
e
e
n
You'd still be able to append the characters to an array if you cast the string just to char[] (without casting it to string[] afterwards), but then you'd have an array with mixed types:
PS C:\> $inputList = #('0')
PS C:\> $inputList += [char[]]$array[$val]
PS C:\> $inputList
0
g
r
e
e
n
PS C:\> $inputList[0].GetType().FullName
System.String
PS C:\> $inputList[1].GetType().FullName
System.Char
If you want the entire string as the second element of the array, your existing code should already do that:
PS C:\> $inputList = #('0')
PS C:\> $inputList += $array[$val]
PS C:\> $inputList
0
green
Related
I have a PowerShell script that fails if only 1 string is fed to an array because it splits it into characters when using Get-Unique and/or Sort-Object. However, if multiple values are provided then it works as expected. So for example:
Expected behavior:
PS X:\> $t = #("asd","bcd") | Get-Unique
PS X:\> $t[0]
asd
PS X:\> $t[1]
bcd
Unexpected (with 1 value):
PS X:\> $t = #("asd") | Get-Unique
PS X:\> $t[0]
a
PS X:\> $t[1]
s
PS X:\> $t[2]
d
Could someone explain why this is happening and how to prevent it?
I'd appreciate any input as my searches did not bring any luck.
Thanks
Get-Unique doesn't split anything - it just returns the one string value as is, and as a result, $t now contains a scalar string, not an array:
PS ~> $t = "asd" |Get-Unique
PS ~> $t.GetType().FullName
System.String
PS ~> $t
asd
But as soon as you try to access the string value with the index accessor [], it returns the individual [char] value found at the given index.
If you want to ensure the output from Get-Unique (or Sort-Object or any other command that might return 0, 1, or more objects as output), wrap the pipeline in the array subexpression operator #():
PS ~> $t = #( "asd" | Get-Unique )
PS ~> $t[0]
asd
When working with an array of values, indexof can be used to find the position of the value in the array.
#this returns '1', correctly identifying 'blue' in position '1' of the array
$valueArray = #('cup','blue','orange','bicycle')
[array]::indexof($valueArray,'blue')
I would like to use this command to find the position of a file (image) in an array of objects generated with Get-ChildItem, however the returned position is always '-1' no matter where the object I have called for actually is. Note that image123.jpg is in the middle of the array.
$imageArray = Get-ChildItem "C:\Images"
[array]::indexof($imageArray,'image123.jpg')
I have noticed that if I change the array to filenames only, it works returning the actual position of the filename.
$imageArray = Get-ChildItem "C:\Images" | select -expand Name
[array]::indexof($imagesToReview,'image123.jpg')
Is this just the nature of using indexof or is there a way to find the correct position of the image file in the array without converting?
The easiest solution here is the following:
$imageArray = Get-ChildItem "C:\Images"
[array]::indexof($imageArray.Name,'image123.jpg')
Explanation:
[array]::IndexOf(array array,System.Object value) searches an array object for an object value. If no match is found, it returns the array lower bound minus 1. Since the array's first index is 0, then it returns the result of 0-1.
Get-ChildItem -Path SomePath returns an array of DirectoryInfo and FileInfo objects. Each of those objects has various properties and values. Just using $imageArray to compare to image123.jpg would be comparing a System.IO.FileInfo object to a String object. PowerShell won't automatically convert a FileInfo object into a string while correctly parsing to find your target value.
When you choose to select a property value of each object in the array, you are returning an array of those property values only. Using $imageArray | Select -Expand Name and $imageArray.Name return an array of Name property values. Name contains a string in your example. This means you are comparing a String to a String when using [array]::IndexOf($imageArray.Name,'image123.jpg').
The way that .NET by default compares things is just not as forgiving as PowerShell is!
[array]::IndexOf($array, $reference) will go through the array and return the current index when it encounters an item for which the following is true:
$item.Equals($reference)
... which is NOT necessarily the same as doing
$item -eq $reference
For simple values, like numbers and dates and so on, Equals() works exactly like -eq:
PS C:\> $a = 1
PS C:\> $b = 1
PS C:\> $a.Equals($b) # $true
... which is the reason your first example works as expected!
For more complex objects though, Equals() works a bit differently. Both values MUST refer to the same object, it's not enough that they have similar or even identical values:
PS C:\> $a = New-Object object
PS C:\> $b = New-Object object
PS C:\> $a.Equals($b) # $false
In the example above, $a and $b are similar (if not identical) - they're both empty objects - but they are not the same object.
Similarly, if we test with your input values, they aren't the same either:
PS C:\> $a = Get-Item "C:\"
PS C:\> $b = "C:\"
PS C:\> $a.Equals($b) # $false
One of the reasons they can't be considered the same, as AdminOfThings excellently explains, is type mismatch - but PowerShell's comparison operators can help us here!
You'll notice that this works:
PS C:\> $a = Get-Item "C:\"
PS C:\> $b = "C:\"
PS C:\> $b -eq $a
True
That's because the behavior of -eq depends on the left-hand operand. In the example above, "C:\" is a string, so PowerShell converts $a to a string, and all of a sudden the comparison is more like "C:\".Equals("C:\")!
With this in mind, you could create your own Find-IndexOf function to do $reference -eq $item (or any other comparison mechanism you'd like) with a simple for() loop:
function Find-IndexOf
{
param(
[array]$Array,
[object]$Value
)
for($idx = 0; $idx -lt $Array.Length; $idx++){
if($Value -eq $Array[$idx]){
return $idx
}
}
return -1
}
Now you'd be able to do:
PS C:\> $array = #('','PowerShell is case-insensitive by default')
PS C:\> $value = 'POWERsheLL iS cASe-InSenSItIVe BY deFAuLt'
PS C:\> Find-IndexOf -Array $array -Value $value
1
Or:
PS C:\> $array = Get-ChildItem C:\images
PS C:\> $value = 'C:\images\image123.png'
PS C:\> Find-IndexOf -Array $array -Value $value
5
Adding comparison against a specific property on each of the array items (like the file's Name in your example), we end up with something like this:
function Find-IndexOf
{
param(
[array]$Array,
[object]$Value,
[string]$Property
)
if($Property){
for($idx = 0; $idx -lt $Array.Length; $idx++){
if($Value -eq $Array[$idx].$Property){
return $idx
}
}
}
else {
for($idx = 0; $idx -lt $Array.Length; $idx++){
if($Value -eq $Array[$idx]){
return $idx
}
}
}
return -1
}
Find-IndexOf -Array #(Get-ChildItem C:\images) -Value image123.png -Property Name
The assignment operator = “Sets the value of a variable to the specified value” as said in the reference doc . Not surprisingly, changes on a variable (in my case an array) that has been previously assigned to another variable do not affect the value of this latter one.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before new value of V: $A"
A before new value of V: a b c
PS C:\> $V="e","f"
PS C:\> write-host "A after new value of V: $A"
A after new value of V: a b c
PS C:\>
But when the method clear() is used the behaviour seems different.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
It seems that clear() method applied on $V acts also on $A. As if the assignment were by reference, strange enough, only for this method. In fact, if a new value is assigned to $V after having cleared it, $A is still affected only by the clear.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> $V="e","f"
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
There are possibilities to avoid this effect, although not precisely identical: $A=$V.clone() or use of cmdlet Clear-Variable -name V or $V=$null instead $V.clear() or perhaps others better than these that somebody could suggest.
But my question is:
how to explain the "propagation" of the effect of clear method on $V to the other array $A?
Tests have been done on PS ver.5.1.
Use Clone() method to get true copy of an array.
$V = "a", "b", "c"
$A = $V.Clone()
Write-Host "A before new value of V: $A"
$V = "e","f"
Write-Host "A after new value of V: $A"
A before new value of V: a b c
A after new value of V: a b c
For explanation, read Copying Arrays and Hash Tables:
Copying arrays or hash tables from one variable to another works, but
may produce unexpected results. The reason is that arrays and hash
tables are not stored directly in variables, which always store only a
single value. When you work with arrays and hash tables, you are
dealing with a reference to the array or hash table. So, if you
copy the contents of a variable to another, only the reference will be
copied, not the array or the hash table. That could result in the
following unexpected behavior:
$array1 = 1,2,3
$array2 = $array1
$array2[0] = 99
$array1[0]
99
Although the contents of $array2 were changed in this example, this
affects $array1 as well, because they are both identical. The
variables $array1 and $array2 internally reference the same
storage area. Therefore, you have to create a copy if you want to
copy arrays or hash tables,:
$array1 = 1,2,3
$array2 = $array1.Clone()
$array2[0] = 99
$array1[0]
1
Whenever you add new elements to an array (or a hash table) or remove
existing ones, a copy action takes place automatically in the
background and its results are stored in a new array or hash table.
The following example clearly shows the consequences:
# Create array and store pointer to array in $array2:
$array1 = 1,2,3
$array2 = $array1
# Assign a new element to $array2. A new array is created in the process and stored in $array2:
$array2 += 4
$array2[0]=99
# $array1 continues to point to the old array:
$array1[0]
1
BTW, you can meet the terms Value type, Reference type and Pointer type more often…
For me what you see is normal, I try to explain myself :
$V="a", "b", "c"
$a = $V
now $a contains the same reference as $V. if you clear $V then $a is cleared.
now if you write $V = "b","c", you affect $V with the reference of a new tab. and this reference is not the same that you affect to $a. so now if you clear $V, $a is not cleared.
Am I clear enought ?
I am experimenting on two-dimensional arrays. I want some sort of a matrix, all with the same character. I can define a blank multi-dimensional array with fixed elements, and supply it with a character using loops. However, I can also do #(something)*n to directly define an array already supplied with something.
From what I understood so far, this is how to do it:
> $arr = ,(,'E'*3)*3
These seems alright:
> $arr[1]
E
E
E
> $arr[1][2]
E
But when I try to replace a character somewhere, like $arr[1][2] = 'D', many characters are replaced:
> $arr
E
E
D
E
E
D
E
E
D
Is my array definition wrong? Added: Then, how to correctly define it 'quickly'?
Using the * operator on non-numeric values creates copies of the original value. However, if the item you're copying isn't of a primitive(-ish) type like String or Char, the result will not be a duplicate of that object, but a copy of the object reference. Since all instances will then be pointing to the same object, changing one will change all.
To create distinct instances you need to repeat the array instantiation in a loop, as PetSerAl showed in the comments:
$arr = 1..3 | ForEach-Object { ,(,'E' * 3) }
In this particular case you could also create a "template" array and clone it:
$a0 = ,'E' * 3
$arr = 1..3 | ForEach-Object { ,$a0.Clone() }
Note, however, that cloning an object will not clone nested object references, so the latter is not a viable approach in all scenarios.
Something like this won't work the way you intend (because the references of the nested hashtable objects are still pointing to the same actual hashtables after cloning the array object):
PS C:\> $a0 = ([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})
PS C:\> $arr = 1..2 | ForEach-Object { ,$a0.Clone() }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
F
E
F
But something like this will work:
PS C:\> $arr = 1..2 | ForEach-Object { ,(([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})) }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
E
E
F
I'm trying to create a function that combines a new element with a existing one in an array.
So if I've created an array with 2 indexes and the value in those indexes are "Hello" and I call my function. I need it to take the selected index and combine the two values without overwriting or removing any part of the existing values in the array.
So the array looks like this after the function:
Hello
Hello stackoverflow
instead of:
Hello
Hello
So far this is my code:
Function AddToArray ($Index, $Add)
{
#$MainArray = New-Object System.Collections.ArrayList;
$MainArray[$Index] = "$MainArray $Add";
$MainArray | % {foreach ($Index in $MainArray[$Index])
{
$MainArray[$Index] + $Add;
}
}
The only thing this accomplishes for me is overwriting the value of the selected index but not combining them.
Any help is appreciated!
Kind regards
Dennis Berntsson
You're making this way too complicated. Just access the element by index and add the new string to it:
function AddToArrayElement($MainArray, $Index, $Add) {
$MainArray[$Index] += $Add
}
Example:
PS C:\> function AddToArrayElement($MainArray, $Index, $Add) {
>> $MainArray[$Index] += $Add
>> }
>>
PS C:\> $a = New-Object System.Collections.ArrayList
PS C:\> $a.Add('Hello'); $a.Add('Hello')
0
1
PS C:\> $a
Hello
Hello
PS C:\> AddToArrayElement $a 1 ' StackOverflow'
PS C:\> $a
Hello
Hello StackOverflow