Array Confusion...

This topic contains 4 replies, has 3 voices, and was last updated by Profile photo of Justin King Justin King 1 year, 5 months ago.

  • Author
  • #25129
    Profile photo of Justin King
    Justin King

    Seems like such a simple topic, yet I have to confess Arrays are driving me crazy in PowerShell. Maybe it's because I'm used to my old DOS/BASIC habits where DIM Array built a discreet "grid" like an excel sheet ... but for whatever reason I find nothing more difficult than actually gathering data I've queried into a nice manner... which seems backwards.

    Because really that's all I want 90% of the time: I get how to query and grab the data ... let me put it in a nice table so I can run some export-csv or convertto-html on it later.

    What's confusing me is the subtle differences between hashtables, arrays, and all their variations that seems poorly documented and rather just assumed (yeah, three Don Jones books later and I still mentally don't grasp what's going on) .

    If I read blogs like, then an Array is rather flat. It's just a bunch of elements, and each element has but a single value. Now lots of people talk about imbedding arrays, or how I can make an array of hashtables, and for the most part I get that, and it's functional. SO for example I play this game often:

    $hash=@{Name="value";Attribute="blah";Attrib2="blah again"}

    This is pretty neat as the new $array basically behaves as expected: I can call individual elements and their values like $array[0].Attribute and get what I'm expecting. It formats like garbage though ... and isn't exactly friendly to things like export-csv.

    But then I do something seemingly simple and something magical happens:

    $array = import-csv ".\somefile.csv"

    Wait, wtf? why is it when I look at this array I see multiple values, like columns in a table? And this one is easily converted by things like exportto-csv?! Why does every article I read on arrays say there's only a single value, but this seems to have multiple? How do I create an empty array in this fashion ... and how would I update/edit it (i tried some simple $array.add() and failed miserably).

    So it seems there has to be a way to simply create a multi-valued array without having to resort to imbedding a hashtable ... but ... well ... how?

  • #25130
    Profile photo of Craig Duff
    Craig Duff

    When you use Import-CSV, it is making an array of objects, custom objects. If you want to use your hash tables to pipe to export-csv, convertto-csv, convertto-html, etc. you must first convert them to objects. Which is easy in powershell. This way requires powershell version 3, but is the simplest way:

    $array = @()
    $hash=@{Name="value";Attribute="blah";Attrib2="blah again"}
    $array += [pscustomobject]$hash
    $array | convertto-csv -NoTypeInformation

    An array is a collection of single objects, not single values. Everything in powershell is an object. Just so happens that an array can have a collection of String objects, or a collection of Hash table objects, or a collection of Array objects.

  • #25132
    Profile photo of Craig Duff
    Craig Duff

    To expand, everything is an object in Powershell. When you pipe an object to ConvertTo-CSv, or whatever, it is looking at the object's properties. The hash table is an object with properties, just not the properties you are expecting. This code may help illustrate:

    $hash=@{Name="value";Attribute="blah";Attrib2="blah again"}
    #The properties of a hash table are, among others Keys and Values
    # Your Keys are Name, Attribute, and Attrib2
    # Your Values are "value","blah" and "blah again"
    $hash | Get-Member -MemberType Properties
    # The properties of your custom object are the Key names from the
    # hash table.
    [pscustomobject]$hash | Get-Member -MemberType Properties
    # If you pipe the $hash directly to Convertto-CSV you'll get
    # column names that relate to the properties of a hash table
    $hash | ConvertTo-Csv -NoTypeInformation
    # If you pipe the custom object to Convertto-CSV you'll get
    # the column names you want
    [pscustomobject]$hash | ConvertTo-Csv -NoTypeInformation
  • #25133
    Profile photo of Rob Simmers
    Rob Simmers

    I'll give this a whack. I'm hoping I don't misspeak, I'm sure Dave or Don will correct me if I'm wrong...

    #Think of arrays indexes as placeholders
    $array = @() #Empty array
    $array += "Jim" #Static string value
    $array += "Sally" #String string value
    #Hashtables have Keys (Name) and a Value, don't know if you worked with vbScript, but similar to a Dictionary object
    #The keys (i.e. "Name") have to be Unique
    $hashtable = @{}
    #.Add is a method for a hashtable, it requires the key Name and Value
    $hashtable.Add("FirstName", "Jim")
    $hashtable.Add("LastName", "Thompson")
    #Another example of a hashtable
    $hashtable2 = @{}
    $hashtable2 += @{FirstName="Jim"}
    $hashtable2 += @{LastName="Thompson"}
    #Another example of a hashtable
    $hashtable3 = @{FirstName="Jim";LastName="Johnson"}
    #Output (All 3 will have the same output):
    #Name                           Value                                                                                                                                                                        
    #----                           -----                                                                                                                                                                        
    #LastName                       Thompson                                                                                                                                                                     
    #FirstName                      Jim      
    #Powershell typically uses a PSObject.  When you run a command to return data, Get-Process, Get-WMIObject, Get-CimInstance, Import-CSV;
    #these commands return a PSObject.  A PSObject is an Array of HashTables.  Each array index contains a hash table with unique named "columns"
    #and a value assigned to that value
    $arrayPSObject = @() #Empty array
    $arrayPSObject += New-Object -TypeName PSObject -Property @{FirstName="Jim";LastName="Thompson"}
    $arrayPSObject += New-Object -TypeName PSObject -Property @{FirstName="Sally";LastName="Smith"}
    $arrayPSObject | Format-Table -AutoSize
    #LastName FirstName
    #-------- ---------
    #Thompson Jim      
    #Smith    Sally
    #To enumerate the items in the array, you don't need to reference them by index. You can use normal for logic:
    $array | foreach{"Processing {0}" -f $_}
    foreach ($item in $array) {"Processing {0}" -f $item}
    #Processing Jim
    #Processing Sally
    #When we enumerate a PSObject, each array index contains a hashtable object, so we need to specify the property to enumerate
    $arrayPSObject | foreach{"Processing {0}" -f $_.FirstName}
    foreach ($item in $arrayPSObject) {"Processing {0}" -f $item.FirstName}
    #Processing Jim
    #Processing Sally
    #HashTables are a bit more of a pain to enumerator through.
    $hashtable.GetEnumerator() | foreach{"Processing {0}" -f $_.Value}
    foreach ($item in $hashtable.GetEnumerator()){"Processing {0}" -f $item.Value}
    #Processing Jim
    #Processing Sally
    #We can also use .GetType() to see what type of an object we're working with
    #IsPublic IsSerial Name                                     BaseType                                                                                                                                         
    #-------- -------- ----                                     --------                                                                                                                                         
    #True     True     Hashtable                                System.Object                                                                                                                                    
    #True     True     Object[]                                 System.Array                                                                                                                                     
    #True     True     Object[]                                 System.Array
    #Note that the PSObject shows the same as an array because it is just an array of objects versus an array of strings.                                                                                                                                     
  • #25134
    Profile photo of Justin King
    Justin King

    Thanks Craig, I actually was able to (finally) do what you're describing, though I went about it slightly differently. I defined a custom object with multiple note properties that contained what I needed.

    I guess the root of the confusion for me is arrays aren't really arrays as I think of them... they are simple lists. It's not the idea that each value is an object that I find confusing, it's that arrays are inherently flat. To me an array should be like an excel sheet ... with rows AND columns. An array is just rows, with objects allowed to have properties that (i)emulate(/i) columns. But if you have different types of objects in the same array ... then that "emulated column" falls apart.

    So to fix that I ended up defining a custom PSObject the fixed noteproperties for each column. As each noteproperty can contain an object ... it results in a array that "behaves" the way I expect it to.

    I guess conceptually I get it ... but can't say I find it a very elegant structure.

You must be logged in to reply to this topic.