2

Я пытаюсь сравнить два многомерных массива в PowerShell. В каждом массиве много тысяч элементов - вот небольшой пример. В одном массиве у меня есть:

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

В другом массиве у меня есть:

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

Что мне нужно сделать, так это сравнить LocalOffice, связанный с каждым именем пользователя из $ arrOne, с первыми двумя символами Location, используя совпадающее имя пользователя в $ arrTwo (если оно существует). Если LocalOffice и Location не совпадают, выполните некоторые действия. Мой пример кода выглядит следующим образом:

$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
                }
            }
    }
}

При использовании стандартного вложенного ForEach (см. Выше) обработка этих массивов занимает некоторое время, поскольку каждый массив большой (и это будет частью сценария, который выполняется каждые 30 минут) и чувствителен ко времени. Чтобы надеяться найти мой ответ, у меня есть несколько вопросов по поводу вышеупомянутого:

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?

Спасибо

ОБНОВЛЕНИЕ:

Мы используем этот сценарий для управления нашими локальными объектами Active Directory и MSOL (Microsoft Online - Office 365). Мы используем DirSync для синхронизации AD с Office 365. Хотя в приведенных выше примерах имена изменены для удобства чтения, это основные команды, используемые для сбора данных массива:

[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

Массивы имеют разные размеры (arrTwo буквально в 10 раз больше размера arrOne). Нет гарантии, что объект из arrOne будет существовать в arrTwo.

Я пробовал еще несколько вещей, чтобы решить эту проблему с момента моей первоначальной публикации (особенно с помощью BREAK для выхода из второго цикла). После первоначальной публикации я понял, что могу добиться лучшего улучшения производительности, если смогу «вырваться» из второго цикла ForEach-Object, когда совпадение найдено. Одна вещь, которая замедляет процесс, заключается в том, что PowerShell продолжает проходить через arrTwo даже после того, как совпадение найдено. Я попытался добавить разрыв после того, как совпадение найдено, но я не могу заставить его выйти из цикла arrTwo и вернуться к следующему объекту в коллекции arrOne. Он продолжает ломать (заканчивать) весь скрипт.

    $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
            }
    }

Я пробовал разбить, разбить / продолжить, разбить / пометить, используя foreach вместо foreach-object, do / while и некоторые другие. Пока не повезло

Дополнительный вопрос:

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

еще раз спасибо

4 ответа4

2

Спасибо всем за вашу помощь - они помогли мне найти решение, чтобы решить мою проблему и заставить работу Break/Continue работать должным образом. Теперь производительность более сопоставима. Мне пришлось изменить внутренний цикл ($ arrTwo) с ForEach-Object на ForEach. Это изменило метод запуска цикла.

$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
            }
        }
    }
}
1

Я подозреваю, что ваши данные поступают из Active Directory. А "реальный" программист может иметь лучший подход, но я думаю , что вы могли бы улучшить производительность за счетом первой сортировки массивов, а затем проверить , если ваше значение из Arrone находится на все содержащиеся в arrTwo использования -contains В зависимости от результата вы можете проверить фактические значения из arrTwo. Проверьте эту статью, она также решает проблему производительности сортировки. Также взгляните на командный объект сравнения объектов, чтобы сравнить ваши массивы.

0

Чего вы хотите избежать, так это поиска во втором массиве для каждой строки первого. Лучший способ будет зависеть от характера двух массивов (см. Вопросы, заданные в комментариях). Предполагая, что массивы примерно одинакового размера, вы должны сделать циклический просмотр обоих массивов одновременно (с использованием индекса) и создать некую таблицу, в которой хранятся результаты. Затем прокрутите эту таблицу, чтобы проверить свои результаты (см. Пример ниже). Теперь я сохранил это в двух циклах для ясности, но вы также можете добавить проверку в первый цикл, когда запись в хеш-таблицу завершена, и затем выполнить проверку.

$ht = @{} # to store the results
#note this code could be simpler if the arrays are sorted  or the same length
for($i=0; $i -lt [Math]::Max($arrOne.Length,$arrTwo.Length); $i++){
    if($i -lt $arrOne.Length)
    {
        if($ht[$arrOne[$i].UserName])
        {
            #just modify the null value.
            $ht[$arrOne[$i].UserName].LocalOffice = $arrOne[$i].LocalOffice;
        }
        else
        {
            #create a new entry
            $ht[$arrOne[$i].UserName] = @{"LocalOffice"=$arrOne[$i].LocalOffice; "Location"=$null;}
        }
    }
    if($i -lt $arrTwo.Length)
    {
        if($ht[$arrTwo[$i].UserName])
        {
            $ht[$arrTwo[$i].UserName].Location = $arrTwo[$i].Location;
        }
        else
        {
            #create a new entry
            $ht[$arrTwo[$i].UserName] = @{"Location"=$arrTwo[$i].Location; "LocalOffice"=$null;}
        }
    }
 }

 # now loop through the resulting table
 $ht.Keys | foreach {
    if($ht[$_].LocalOffice -and $ht[$_].Location)
    {
        if($ht[$_].LocalOffice.Substring(0,2) -ne $ht[$_].Location)
        {
            "Problem for $_";
        }
    }
}
0

Я хочу знать, почему @DavidPostill удалил мой ответ. Я не вижу способа отправить ему сообщение напрямую, так что извините за это. Я ответил с кодом и некоторыми комментариями, объясняющими это. Это был уникальный ответ, то есть не дубликат, и он, вероятно, был более эффективным, чем другие опубликованные методы. Если это потому, что этому посту 4 года, обновите свой справочный центр, чтобы он не комментировал старые посты и был полезным. Я наткнулся на этот пост из-за поиска в Google, который искал что-то похожее, и в итоге ответил сам. Ответ снова ниже.


Я знаю, что это 4 года, но я также уверен, что это, вероятно, все еще происходит. Я подумал, что спросить, не будет ли в этом случае полезно использовать хэш-таблицы, чтобы вы могли напрямую запрашивать конкретный результат, а не перебирать всех, надеясь, что что-то совпадет.

Признаюсь, у меня проблемы с следованием вашему коду, так как ваши примеры $ arrOne и $ arrTwo не используют те же свойства, что и ваш финальный код. Т.е. Имя пользователя vs UserPrincipalName, так что терпите с кодом и обновите его соответственно.

Способ, которым я подхожу к этому, состоит в том, что я собрал бы 2 массива. Один из известных вам массивов - это те, которые вы можете обновлять (Office 365), а другой массив ($ arrOne), который вы будете использовать в качестве источника / основных данных. Таким образом, вы должны построить хеш-таблицу для каждого массива и использовать свой массив SMALLER, т.е. $ ArrTwo, как тот, для которого вы делаете ForEach. Только те ценности, которые мы заботимся о совпадении или нет.

Когда я создаю 2 Hashtables, я использую UserPrincipalName в качестве ключа. Т.е. User@company.com будет получен как $ Hash ['User@company.com '], и если вы хотите получить свойство LocalOffice, это будет $ Hash ['User@company.com'].LocalOffice Итак, все, что нам нужно сделать, это перебрать все ключи для меньшей из 2 хеш-таблиц, сравнивая значения с первой хеш-таблицей и обновляя соответственно.

[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

#Create Hash for AD
$hash1 = $null
$hash1 = @{}
foreach ($u in $arrOne)
{
$hash1.add($u.UserPrincipalName,$u)
}

#Create Hash for Office365
$Hash2 = $null
$Hash2 = @{}
foreach ($u2 in $arrTwo)
{
$Hash2.add($u2.UserPrincipalName,$u2)
}


#Itterate through Office365 Keys (UserPrincipalNames).
$Hash2.keys | ForEach {
#Quick check to see if the Hash1 value exists (Is there a UPN in Hash1 that matches Hash2)
If ($hash1[$_])
  {  
    If ($Hash1[$_].LocalOffice.Length -ge 2)
        {
        $strOneOffice = $Hash1[$_].LocalOffice.substring(0,2)
        }
    Else
        {
        $strOneOffice = "US"
        }
  }
  else
{
#Continue as the UPN does not exist in $Hash1, which should be rare.  This should skip to the next entry in Hash2 and start over.
  Continue
  }
if (!($hash2[$_].UsageLocation  -eq $strOneOffice ))
        {
        ## Take action here if they don't match
        write-host "$($_) needs to be updated; OldValue: $($hash2[$_].UsageLocation); NewValue: $strOneOffice"
        }
    Else
        {
        ## Nothing to update here because they already match
        write-host "$($_) does not need to be updated"
        Continue
        }
}

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .