Working with 2D arrays and strings

This topic contains 9 replies, has 5 voices, and was last updated by  Jason DaSilva 1 week, 2 days ago.

  • Author
    Posts
  • #77044

    Jason DaSilva
    Participant

    Hello all, first post. I am fairly new to powershell, though I have been coding one way or another since I was young. I am working on a windows profile data migrator (my company needs some special things done, and I need practice with powershell), and I am looking to grab 2 pieces of information. The profile location paths and the profile sids. No problem there. but I need to store them after a bit of formating. I would prefer to store them in a 2D array, so that I can reference the row when I need specific data.
    So I went on line and looked at a few tutorials on arrays. all seems to work well, until I go multi-D. for instance, this:
    $array = @(1, 2, 30, 50)
    will allow me to run $array[2] and get 30. if I add another level to it like so:
    $array = @(1,2,3), @(10,20,30)
    Some odd things start to happen. First off I would think that $array[0][1] would return the 2nd element of the first row, but it actually returns a blank. To get it to return 2, I have to use $array[0][2]. Also, the double digits are stored singly. When running $array I get:
    1 2 3
    10 20 30
    It appears that the array stores the separators as well as each individual digit (or letters in the string in my practical work) in the array. To me this seems to be less that preferred behavior. Can someone explain this to me or point me in the right direction on documentation on this (Powershell on win10)

  • #77047

    Don Jones
    Keymaster

    So, arrays do work a bit differently. I've honestly not run across someone using multidimensional arrays like this in PowerShell; it offers so many more-robust ways of managing data, I think, that this just hasn't come up for me :).

    For example, I'd probably create an object, which had properties for whatever I wanted to store:

    $array = @()
    $props = @{Username='whatever'
               ProfilePath='this'
               ProfileSID='12334'}
    $obj = New-Object -Type PSObject -Prop $props
    $array += $obj
    

    You can then reference $array[0] to get the first one, and the advantage is you can reference $array[0].ProfileSID to get that property. Your code becomes a lot easier to read and maintain, because nobody has to document or guess what the array structure is meant to look like.

    It also means the entire rest of PowerShell will "understand" your data structure. You could easily...

    $array | Export-CSV file.csv
    $array | ConvertTo-HTML | Out-File report.html
    $array | Format-Table
    $array | Where Name -like '*Don*' | Sort ProfileSID | Format-List
    

    And so on. Whereas, by kind of rolling your own thing via arrays, PowerShell won't have a clue what you're up to, and you'll end up doing a lot more work on your own.

    • #77070

      Chris Bakker
      Participant

      Try this:
      $array = @(@(1,2,3), @(10,20,30))

      im a bit puzzled, on win7 all goes like you expect....

    • #77103

      Jason DaSilva
      Participant

      With that array, try running these lines:
      $array
      $array[0][0]
      $array[0][1]

      for those lines I get these results:
      1 2 3
      10 20 30
      ———–
      1
      ———–

      ———–
      The last line will return a blank space (that is between cells [0][0] and [0][2]
      the first return counts every char (spaces and all) as a cell each. In any case, Don Jones has given me the details and solution I need.

  • #77064

    Jason DaSilva
    Participant

    Ok. I'm good with doing that. It seems odd that you could not just may your object itself a container then and reference the various 'cells'. Just a thought. I will use this method for now, it does what I want and like you said, it reduces the need for some documentation. Thanks for the info. My view of arrays may be a bit old school...

  • #77074

    Simon B
    Participant
    • #77107

      Jason DaSilva
      Participant

      Thanks also Simon B. Though this too works, the method put forward by Don allows easier use and management. Also viewing the object I can see rows and columns with headers (easier when trouble shooting).

  • #77124

    Max Kozlov
    Participant

    seems there something in your profile
    try to launch "powershell -noprofile" and repeat you tests

    $array should display its contents one-on-a-line
    and you see output like "write-host $array"

  • #77149

    Jason DaSilva
    Participant

    Max, I would think so too. Could it be a change at the newer versions of PowerShell? I am using win10.

    @Don Jones:
    found a bit of a kink in the armor on your solution. If I add the $obj to the array, I would have to create a new object each time I wanted to add a value. That seems a bit inefficient, especially if this is done in a for loop. I am also getting an error that the array is of a fixed size.

    Maybe I need to go over the scenario in a bit more detail... Maybe I'm not doing it the best way.
    Here are the Steps I am going through at this point of the code:

    1. my function is passed 2 computer names. Using Get-WmiObject I grab the list of user paths and sids:

    $users1 = Get-WmiObject win32_UserProfile -ComputerName $cName1 -Filter "sid like '%1234567890%'" | select localPath,Sid

    I do this for each computer (filling $users1 and $users2) then I do a compare to grab the users that are the same on both machines:

    $sameUsers = Compare-Object -ReferenceObject $users1 -DifferenceObject $users2 -Property localPath -PassThru -IncludeEqual -ExcludeDifferent

    2. so I have these lists. Initially I would just parse out the path info to get the user's name to populate a dropdown box, so the operator can select a user that has a profile on both machines. Usually, this should only be person, but there will be times when there are more, so I need to track the selected username, and the SID for that user.

    so here I am trying to populate a list of data into an array, so that I can reference it later when needed. I really don't need the fields to be named, I just need to be able to get back at the data, and was hoping to use the IndexOf to get the row, then retrieve the column of data I need.

  • #77169

    Jason DaSilva
    Participant

    Sorry if I multi-posted, but I'm new here and did not notice the 'Due to an uptick in spam posts...' message at first.

    In any case, I guess I was asking for the wrong 'stuff'. I was poking around a bit more and found this page on hashtables:
    https://kevinmarquette.github.io/2016-11-06-powershell-hashtable-everything-you-wanted-to-know-about/#iterating-hashtables

    My solution was to create a nested hashtable. Though they did not show it on the page, I found that you can also define keys with variables, which works out perfect for me. Once I have the data from the win32 output in $sameUsers, I for $i looped through it to grab what I wanted :
    (after defining the hashtable as $profileData = @{} )

        if ($sameUsers.count -gt 0)
            {
            for($i=0;$i -le $sameUsers.count -1; $i++)
                {
                #parse data to get what we need from it and store it in the $profileData array
                $aPath = $sameUsers[$i].localPath
                $someStrings = $aPath.Split("\")
                $aUser = $someStrings[$someStrings.GetUpperBound(0)].ToUpper()
                $aSid = $sameUsers[$i].Sid
                $profileData.$aUser = @{path = $aPath; SID = $aSid}
                $aList.Items.Add($aUser)
                }
            }
    

    I could then easily access the key value ($aList is a combo box) when the combobox value is changed:

        $tempUser =  $profileCombobox.SelectedItem
        $pSIDLabel.Text = $profileData.$tempUser.SID
    

    I'm sure I could have done this without the tempUser var, but this is easier to read. In anycase, this does exactly what I want. Sorry if my description of the problem was off... To be honest, I have never used hashtables before.

You must be logged in to reply to this topic.