Shell – Comparing two multi-dimensional arrays in PowerShell

powershell

I am trying to compare two multi-dimensional arrays in PowerShell. Each array has many thousands of elements – a small example follows. In one array I have:

$arrOne
Username                        LocalOffice
john.doe@domain.com             US-California
need.help@domain.com            IT-Naples
another.example@domain.com      TR-Istanbul
(etc...)

In the other array, I have:

$arrTwo
Username                        Location
john.doe@domain.com             US
need.help@domain.com            US
another.example@domain.com      TR
(etc...)

What I need to do is to compare the LocalOffice associated with each Username from $arrOne with the first two characters of Location using the matching Username in $arrTwo (if it exists). If LocalOffice and Location don’t match then take some actions. My code sample is as follows:

$arrOne | ForEach-Object {
    $strOneName = $_.Username
    If ($_.LocalOffice.Length -ge 2)
        {
        $strOneLocalOffice = $_.LocalOffice.substring(0,2)
        }
    Else
        {
        $strOneLocalOffice = "US"
        }
    $arrTwo | ForEach-Object {
        If ($_.Username -eq $strOneName -eq $True)
            {
            If ($_.Location -eq $strOneLocalOffice -ne $True)
                {
                ## Take action here if they don't match
                write-host $_.Username
                }
            }
    }
}

With the standard nested ForEach (above), it takes some time to process these arrays because each array is large (and this will be part of a script that runs every 30 minutes) and is time sensitive. To hopefully find my answer, I have a few questions about the above:

1)  Is there some other (quicker) method to get the desired results?

2)  Do I have to use ForEach and loop through arrTwo until I find the matching
Username from arrOne or is there some other quicker method to jump right to the
matching Username in arrTwo?

3)  Is there a way to quickly merge (join) these two arrays together so then I
can ForEach once through a single array and just compare individual objects
from the same element?

Thanks

UPDATE:

We use this script to help manage our local Active Directory and MSOL (Microsoft Online – Office 365) objects. We are using DirSync to keep sync our AD with Office 365. Although names are changed in the above examples for readability, these are the basic commands used to gather the array data:

[array]$arrOne = @(Get-ADObject -Filter {(objectClass -eq "User") -And (objectCategory -eq "Person")} -SearchBase “OU=Test,DC=domain,DC=com” -Properties UserPrincipalName,physicalDeliveryOfficeName) | Select-Object UserPrincipalName, physicalDeliveryOfficeName

[array]$arrTwo = @(Get-MsolUser -Synchronized -All) | Where-Object {$_.isLicensed -eq "True"} | Select-Object UserPrincipalName, UsageLocation

The arrays are different sizes (arrTwo is literally 10 times the size of arrOne). There is no guarantee that an object from arrOne will exist in arrTwo.

I’ve tried a few more things to solve this since my original posting (especially using BREAK to exit the second loop). After the original post, I realized I can get the best performance improvement if I can “break” out of the second ForEach-Object loop when a match is found. One thing that is slowing things down is that PowerShell keeps looping through arrTwo even after a match is found. I tried adding break after a match is found but I can’t get it to break out of the arrTwo loop and return to the next object in the arrOne collection. It keeps breaking (ending) the entire script.

    $arrTwo | ForEach-Object {
        If ($_.Username -eq $strOneName -eq $True)
            {
            If ($_.Location -eq $strOneLocalOffice -ne $True)
                {
                ## Take action here if they don't match
                write-host $_.Username
                }
            Break
            }
    }

I tried break, break/continue, break/label, using foreach instead of foreach-object, do/while and some others. No luck yet.

An additional question:

4)  Can break be used to exit a ForEach-Object loop and return it to the “parent”
ForEach-Object?

Thanks again

Best Answer

Thank you all for your help - they helped to lead me to finding a solution to solve my problem and getting Break/Continue to work properly. Now the performance is more comparable. I had to change the inner loop ($arrTwo) from ForEach-Object to ForEach. That changed the method by which the loop is initiated.

$arrOne | ForEach-Object {
    If ($_.LocalOffice.Length -ge 2)
        {
        $strOneOffice = $_.LocalOffice.substring(0,2)
        }
    Else
        {
        $strOneOffice = "US"
        }
    ForEach ($objTwo in $arrTwo) {
    If ($objTwo.Username -eq $_.Username)
        {
        If ($objTwo.UsageLocation -eq $strOneOffice -ne $True)
            {
            ## Take action here if they don't match
            write-host $_.Username "needs to be updated"
            Break
            }
        Else
            {
            ## Nothing to update here because they already match
            write-host $_.Username "does not need to be updated"
            Continue
            }
        }
    }
}
Related Question