ArrayList not returning expected result - powershell

Why does this code not work?
$method = {
[System.Collections.ArrayList]$array;
for ($i=0; $i -le 50; $i++) { $array += $i }
}
Executing the scriptblock with:
&$method
Shows on the console:
1, 2, 3, 4, 5, 6
when it should print 50 numbers?

The Add() method of the ArrayList type emits the index at which a new item was inserted. Cast the expression to [void], pipe it to Out-Null or assign it to $null to supress this output:
[void]$array.Add($i)
# or
$array.Add($i) |Out-Null
# or
$null = $array.Add($i)
Avoid piping to Out-Null if you do this many times, casting or assigning is much faster than piping.

The code you posted shouldn't generate any output at all, unless you already have a variable $array defined in the parent scope. The statement
[System.Collections.ArrayList]$array
casts the value of the variable $array to the type ArrayList and echoes it. If the variable has a value in the parent scope the statement will output the value as an array list, otherwise the output will be null. The subsequent loop will not use that variable, but instead increment a new (local) variable $array. You can verify that by placing a statement $array.GetType().FullName at the end of the scriptblock. You'll get System.Int32, not System.Collections.ArrayList (or System.Object[]) as you might expect.
If you want to instantiate an ArrayList object, add numbers to it, and output that list at the end of the scriptblock, you need to change your code to something like this:
$method = {
[Collections.ArrayList]$array = #()
for ($i=0; $i -le 50; $i++) { $array += $i }
$array
}
Note the assignment operation in the first statement.
Demonstration:
PS C:\> $sb = { [Collections.ArrayList]$a; 0..50 | % { $a += $_ }; $a.GetType().FullName; $a }
PS C:\> &$sb
System.Int32
1275
PS C:\> $sb = { [Collections.ArrayList]$a = #(); 0..50 | % { $a += $_ }; $a.GetType().FullName; $a }
PS C:\> &$sb
System.Collections.ArrayList
0
1
...
49
50

Related

how to use a FOR loop variable to help define another variable

I am new to powershell scripts and not sure how to achieve the below:
$finalArray = #()
$tempArray0 = 'A'
$tempArray1 = 'B'
$tempArray2 = 'C'
FOR (i=0; i -eq 5; i++) {$finalArray += $tempArray[i]}
$finalArray
Output Should be:
A
B
C
If the variable name is itself variable, you'll have to use the Get-Variable cmdlet to retrieve its value:
$finalArray = #()
$tempArray0 = 'A'
$tempArray1 = 'B'
$tempArray2 = 'C'
for($i=0; $i -le 2; $i++) {
$finalArray += (Get-Variable "temparray$i" -ValueOnly)
}
$finalArray
If you want to create variables with variable names, use the New-Variable cmdlet:
$Values = 'A','B','C'
for($i = 0; $i -lt $Values.Count; $i++){
New-Variable -Name "tempvalue$i" -Value $Values[$i]
}
which would result in:
PS C:\> $tempvalue1
B
Although the above will solve the example you've presented, I can think of very few cases where you wouldn't be better of using a [hashtable] instead of variable variable names - they're usually an over-complication, and you'll end up with unnecessary code anyways because you need to calculate the variable names at least twice (during creation and again when reading the value).
From the comments, it sounds like you're trying to generate input for a password generator. This can be simplified grossly, without resorting to variable variable names:
# Create a hashtable and generate the characters
$CharArrays = #{
Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
Numbers = 0..9
}
# Generate some letters for the password
$PasswordChars = $CharArrays['Letters'] |Get-Random -Count 10
# Generate a few digits
$PasswordChars += $CharArrays['Numbers'] |Get-Random -Count 4
# Shuffle them around a bit
$PasswordChars = $PasswordChars |Sort-Object {Get-Random}
# Create your password
$Password = $PasswordChars -join ''

Object variable changes to a string

I have a variable set as an object as follows:
[object] $x = 'abc','def';
If I view what $x is now, I get:
acb
def
Now my problem is when I set $x to $null and then try to rather set $x from a loop using += after reading the file it change $x type to a string and if I view what $x is now it gives me:
abcdef
instead of:
abc
def
How do I go about it to keep the variable as an object rather then a string?
Below is just a sample to get the idea:
[object] $x = 'abc','def';
$x = $null;
for ($i = 0; $i -lt $numberOfColumns; $i++) {
$x += '$_.' + $NamesOfColumns[$i] + '.Trim()';
}
What you do within your code is:
[object] $x = 'abc', 'def'
==> The type of 'abc' and 'def' is [System.String]. Because you comma seperated them PowerShell does automatically create a list. So after executing that line $x is a System.Object[]. Index 0 and 1 contains [System.String].
$x = $null;
==> Now you define $null as the value for $x. So you are removing the value. The type of $x is now undefinded. You can set the value 123 then $x will become type System.Int32. You can redefine a string and so on.
Within your for-loop you use
$x += 'somestring' + $addingSomeStuff + 'otherstring'
==> The result here is that within the first Iteration of the for-loop PowerShell will assign a String to $x. So the type of $x will be [System.String]. In the next iterations the += operator adds additionally content to the value of $x, which is still [System.String]
Don't set $x to $null. Because you'll loose the type information. For more information read about the PowerShell Extended Type System.
The following snippet works. Hope that helps.
############################################
# The following was not part of your post #
# I added it to get it run in general #
$numberOfColumns = 2
$NamesOfColumns = 'Column1', 'Column2'
############################################
[object] $x = 'abc','def';
# don't set $x to $null
# define an empty list instead ;-)
$x = #()
for ($i = 0; $i -lt $numberOfColumns; $i++) {
$x += '$_.' + $NamesOfColumns[$i] + '.Trim()';
}
In the underlying type system (.NET CTS), [object] is the base class for any object, and is the most vague type description you can give any variable - it doesn't convey any specialized meaning at all.
As mentioned in #PatM0's answer, the best solution here is to initialize the variable value with the array subexpression operator (#()) before using += :
$x = #()
If you really want to force a variable to be a collection type, use the array or psobject[] type accelerators:
PS C:\> [array]$x = 'abc','def'
PS C:\> $x = $null
PS C:\> $x += 'abc'
PS C:\> $x += 'def'
PS C:\> $x
abc
def
PS C:\> [psobject[]]$x = 'abc','def'
PS C:\> $x = $null
PS C:\> $x += 'abc'
PS C:\> $x += 'def'
PS C:\> $x
abc
def
Compare with [object]:
PS C:\> [object]$x = 'abc','def'
PS C:\> $x = $null
PS C:\> $x += 'abc'
PS C:\> $x += 'def'
PS C:\> $x
abcdef

Count property of array in PowerShell with pscustomobjects

If the array has only one CustomObjects and the Count property is null. Why?
If use only strings, the Count property is one.
function MyFunction
{
$Objects = #()
$Objects += [pscustomobject]#{Label = "Hallo"}
# $Objects += [pscustomobject]#{Label = "World"}
$Objects
}
$objs = MyFunction
Write-Host "Count: $($objs.Count)"
Output: "Count: " because $objs.Count is null
function MyFunction
{
$Objects = #()
$Objects += [pscustomobject]#{Label = "Hallo"}
$Objects += [pscustomobject]#{Label = "World"}
$Objects
}
$objs = MyFunction
Write-Host "Count: $($objs.Count)"
Output: "Count: 2"
Behavior is different if I add strings
function MyFunction
{
$Objects = #()
$Objects += "Hallo"
# $Objects += [pscustomobject]#{Label = "World"}
$Objects
}
$objs = MyFunction
Write-Host "Count: $($objs.Count)"
Output: "Count: 1"
You can force the function to return an array even if the nested array is constructed of one object.
function MyFunction
{
$Objects = #()
$Objects += [pscustomobject]#{Label = "Hallo"}
# $Objects += [pscustomobject]#{Label = "World"}
return ,$Objects
}
That comma makes an explicit conversion to an array even if the supplied $Objects is already an array of more than one object. This forces a construction of an array with one element. Outside, Powershell does unboxing of a one-item array, so you'll get an array if there was one, so this method works around default Powershell behavior for working with one-item arrays. You experience Count being 1 because in Powershell 3.0 Microsoft fixed one-item arrays by adding Count property to every object so that indexing of one object returns Count as 1, and for $null zero is returned, but the PSCustomObjects are excluded from this as a natural reason declares that they can contain their own Count properties, thus no default Count property is included for a custom object. That's why you don't get Count if there's only one object.
The behavior of , can be seen on the following example:
function hehe {
$a=#()
$a+=2
$a+=4
$b=,$a
write-verbose $b.count # if a variable is assigned ",$a", the value is always 1
return ,$a
}
Output:
PS K:> (hehe).count
VERBOSE: 1
2

Getting length of HashTable in powershell

I'm new to powershell and trying to get the length of a HashTable (to use in a for loop), but I can't seem to get the length of the HashTable to output anything.
$user = #{}
$user[0] = #{}
$user[0]["name"] = "bswinnerton"
$user[0]["car"] = "honda"
$user[1] = #{}
$user[1]["name"] = "jschmoe"
$user[1]["car"] = "mazda"
write-output $user.length #nothing outputs here
for ($i = 0; $i -lt $user.length; $i++)
{
#write-output $user[0]["name"]
}
#{} declares an HashTable whereas #() declares an Array
You can use
$user.count
to find the length of you HashTable.
If you do:
$user | get-member
you can see all the methods and properties of an object.
$user.gettype()
return the type of the object you have.
$user is a hash table, so you should user$user.count instead.
That's not an array but a hashtable. Use .count instead:
write-output $user.count

Referencing Powershell array index produces unexpected results when referenced with string

I am trying to find out why the following occurs if you have
$arr = #("Filename1", "Filename2")
for($i =0; $i -le $arr.Length -1; $i++) {
write-host ".\"$arr[$i]
write-host ".\$arr[$i]"
write-host $arr[$i]
}
So taking just one loop through it produces:
".\ Filename1"
".\ Filename1 Filename2[0]"
"Filename1"
Just referencing the array[index] will produce the correct value, but if I concatenated with a string it places a space between the string and value. When placed within the string I assume it is dumping the entire contents because it is evaluating $array then evaluating $i ending up with
".\ filename1 filename2[index number]"
But if I assign the individual value to a separate variable and concatenate it with a string there is no space? Why is that:
Example:
$name = $arr[$i]
write-host ".\$name"
output = ".\filename1"
which is correct.
You have to do:
write-host ".\$($arr[$i])"
so that it is evaluated as array indexing.
It would be the case with something like accessing properties of an object or key of hash etc within the string:
PS > $a = #{test="A";test2="B"}
PS > write-host "$a.test"
System.Collections.Hashtable.test
PS > write-host "$($a.test)"
A
Another alternative is to use string formatting, especially useful when you have lots of variables / objects in the string:
write-host (".\{0}" -f $arr[$i])
Your code should look like this:
$arr = #("Filename1", "Filename2")
#for($i =0; $i -le $arr.Length-1; $i++) {
for($i =0; $i -le $arr.Length; $i++) {
write-host ".\"$arr[$i]
#write-host ".\$arr[$i]"
write-host ".\$($arr[$i])"
write-host $arr[$i]
}