Count property of array in PowerShell with pscustomobjects - powershell

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

Related

ArrayList not returning expected result

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

Powershell array of arrays [duplicate]

This question already has answers here:
Powershell create array of arrays
(3 answers)
Closed 5 years ago.
This is building $ret into a long 1 dimensional array rather than an array of arrays. I need it to be an array that is populated with $subret objects. Thanks.
$ret = #()
foreach ($item in $items){
$subret = #()
$subRet = $item.Name , $item.Value
$ret += $subret
}
there might be other ways but arraylist normally works for me, in this case I would do:
$ret = New-Object System.Collections.ArrayList
and then
$ret.add($subret)
The suspected preexisting duplicate question is indeed a duplicate:
Given that + with an array as the LHS concatenates arrays, you must nest the RHS with the unary form of , (the array-construction operator) if it is an array that should be added as a single element:
# Sample input
$items = [pscustomobject] #{ Name = 'n1'; Value = 'v1'},
[pscustomobject] #{ Name = 'n2'; Value = 'v2'}
$ret = #() # create an empty *array*
foreach ($item in $items) {
$subret = $item.Name, $item.Value # use of "," implicitly creates an array
$ret += , $subret # unary "," creates a 1-item array
}
# Show result
$ret.Count; '---'; $ret[0]; '---'; $ret[1]
This yields:
2
---
n1
v1
---
n2
v2
The reason the use of [System.Collections.ArrayList] with its .Add() method worked too - a method that is generally preferable when building large arrays - is that .Add() only accepts a single object as the item to add, irrespective of whether that object is a scalar or an array:
# Sample input
$items = [pscustomobject] #{ Name = 'n1'; Value = 'v1'},
[pscustomobject] #{ Name = 'n2'; Value = 'v2'}
$ret = New-Object System.Collections.ArrayList # create an *array list*
foreach ($item in $items) {
$subret = $item.Name, $item.Value
# .Add() appends whatever object you pass it - even an array - as a *single* element.
# Note the need for $null = to suppress output of .Add()'s return value.
$null = $ret.Add($subret)
}
# Produce sample output
$ret.Count; '---'; $ret[0]; '---'; $ret[1]
The output is the same as above.
Edit
It is more convoluted to create an array of tuples than fill an array with PsObjects containing Name Value as the two properties.
Select the properties you want from $item then add them to the array
$item = $item | select Name, Value
$arr = #()
$arr += $item
You can reference the values in this array by doing this
foreach($obj in $arr)
{
$name = $obj.Name
$value = $obj.Value
# Do actions with the values
}

PowerShell Function within Array Comprehension

I am parsing all logins for a Windows system ($system). I'm trying to get a tuple/array of domain, user, session id.
$logged_on_users = Get-WmiObject Win32_LoggedOnUser -ComputerName $system
I'm then calling the type's function to pull out two variables (Antecedent and Depedent) within each value:
$all_user_sessions = #()
foreach ($x in $logged_on_users){
$t = #()
$t += $x.GetPropertyValue("Antecedent").tostring()
$t += $x.GetPropertyValue("Dependent").tostring()
$all_user_sessions += $t
}
What is the syntax for a one-line Array Comprehension (if it's even possible)? I have:
$all_user_sessions = ($logged_on_users | % { #($_.GetPropertyValue("Antecedent").tostring(), $_.GetPropertyValue("Dependent").tostring() ) })
When I add
foreach ($s in $all_user_sessions){
echo $s.GetType().FullName
}
The type is a String, not an Array.
This is going to sound a little weird, but you need to add a comma before the array in your ForEach-Object loop:
$all_user_sessions = ($logged_on_users | % { ,#($_.GetPropertyValue("Antecedent").tostring(), $_.GetPropertyValue("Dependent").tostring() ) })
As your code is written you're adding everything to a single array. Placing the comma like that will create an array (actually an object) of each index.

Creating dynamic variable array names and then adding object to them

What I'm trying to do is create array variable names dynamically, and then with a loop, add the object to its relevant array based on the hash table value being equal to the counter variable.
$hshSite = #{} # Values like this CO,1 NE,2 IA,3
$counter = $hshSite.count
For($i = $counter; $i -gt 0; $i--) {
New-Variable -Name "arr$i" -Value #()
}
If $counter = 3, I would create arrays $arr1, $arr2, $arr3
$csv = Import-CSV....
ForEach ($x in $csv) {
#if $hshSite.Name = $x.location (ie CO), look up hash value (1),
and add the object to $arr1. If $hshSite.Name = NE, add to $arr2
I tried creating the dynamic arrays with New-Variable, but having issues trying to add to those arrays. Is it possible to concatenate 2 variables names into a single variable name? So taking $arr + $i to form $arr1 and $arr2 and $arr3, and then I can essentially just do $arr0 += $_
The end goal is to group things based on CO, NE, IA for further sorting/grouping/processing. And I'm open to other ideas of getting this accomplished. Thanks for your help!
Just make your hash table values the arrays, and accumulate the values to them directly:
$Sites = 'CO','NE','IA'
$hshSite = #{}
Foreach ($Site in $Sites){$hshSite[$Site] = #()}
ForEach ($x in $csv)
{
$hshSite[$x.location] += <whatever it is your adding>
}
If there's a lot of entries in the csv, you might consider creating those values as arraylists instead of arrays.
$Sites = 'CO','NE','IA'
$hshSite = #{}
Foreach ($Site in $Sites){ $hshSite[$Site] = New-Object Collections.Arraylist }
ForEach ($x in $csv)
{
$hshSite[$x.location].add('<whatever it is your adding>') > $nul
}
You could quite easily do add items to a dynamically named array variable using the Get-Variable cmdlet. Similar to the following:
$MyArrayVariable123 = #()
$VariableNamePrefix = "MyArrayVariable"
$VariableNameNumber = "123"
$DynamicallyRetrievedVariable = Get-Variable -Name ($VariableNamePrefix + $VariableNameNumber)
$DynamicallyRetrievedVariable.Value += "added item"
After running the above code the $MyArrayVariable123 variable would be an array holding the single string added item.

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