在 PowerShell 中比较两个多维数组

在 PowerShell 中比较两个多维数组

我正在尝试在 PowerShell 中比较两个多维数组。每个数组都有数千个元素 - 以下是一个小示例。在一个数组中,我有:

$arrOne
Username                        LocalOffice
[email protected]             US-California
[email protected]            IT-Naples
[email protected]      TR-Istanbul
(etc...)

在另一个数组中,我有:

$arrTwo
Username                        Location
[email protected]             US
[email protected]            US
[email protected]      TR
(etc...)

我需要做的是比较当地办事处与每一个相关用户名$arrOne前两个字符为地点使用匹配用户名$arrTwo(如果存在)。如果当地办事处地点不匹配则采取一些措施。我的代码示例如下:

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

使用标准嵌套每一个(上面),处理这些数组需要一些时间,因为每个数组都很大(这将是每 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 的大小实际上是 arrOne 的 10 倍)。无法保证 arrOne 中的对象会存在于 arrTwo 中。

自从我最初发布帖子以来,我尝试了其他一些方法来解决这个问题(特别是使用 BREAK 退出第二个循环)。在最初的帖子之后,我意识到如果在找到匹配项时可以“中断”第二个 ForEach-Object 循环,我可以获得最佳的性能改进。一个导致速度变慢的原因是,即使找到匹配项,PowerShell 仍会继续循环遍历 arrTwo。我尝试在找到匹配项后添加 break,但我无法让它中断 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
            }
    }

我尝试过 break、break/continue、break/label、使用 foreach 代替 foreach-object、do/while 和其他一些方法。但还是没成功。

附加问题:

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

再次感谢

答案1

感谢大家的帮助 - 他们帮助我找到解决问题的方法,并中断/继续才能正常工作。现在性能更具可比性。我不得不将内部循环 ($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
            }
        }
    }
}

答案2

我怀疑您的数据来自 Active Directory。“真正的”程序员可能有更好的方法,但我认为您可以通过首先对数组进行排序,然后使用检查 arrOne 中的值是否包含在 arrTwo 中来提高性能-contains。根据结果,您可以检查 arrTwo 中的实际值。检查本文,它还解决了排序性能问题。另请查看比较对象命令行来比较你的数组。

答案3

您要避免的是在第二个数组中搜索第一个数组的每一行。最好的方法取决于 2 个数组的性质(请参阅评论中提出的问题)。假设数组的大小大致相同,您要做的就是同时循环遍历两个数组(使用索引)并构建某种存储结果的表。然后循环遍历该表以检查您的结果(参见下面的示例)。现在我将其保留为 2 个循环以便清晰,但您也可以在第一个循环中添加检查,每当哈希表的条目完成时,然后进行检查。

$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 $_";
        }
    }
}

答案4

我想知道为什么@DavidPostill删除了我的回答。我没有找到直接给他发消息的方法,对此深表歉意。我用代码和一些评论来解释。这是一个独特的答案,即不是重复的,而且可能比其他发布的方法更有效。如果是因为这篇文章已有 4 年历史,请更新您的帮助中心,不要评论旧帖子并提供帮助。我在谷歌上搜索类似内容时偶然发现了这篇文章,最后自己回答了。答案如下。


我知道这已经是 4 年前的事了,但我也确信这种情况可能仍在发生。我想问一下,在这种情况下使用哈希表是否有益,这样您就可以直接查询特定结果,而不是遍历所有人希望找到匹配项。

我承认,我很难理解您的代码,因为您的 $arrOne 和 $arrTwo 示例使用的属性与您的最终代码建议的不一样。例如,用户名与 UserPrincipalName,因此请耐心等待代码并进行相应的更新。

我采用的方法是收集 2 个数组。您知道一个数组是您可能要更新的数组 (Office 365),另一个数组 ($arrOne) 是您要用作源/主数据的数组。因此,您将为每个数组构建一个哈希表,并使用较小的数组,即 $ArrTwo 作为您要对其执行 ForEach 的数组。我们只关心这些值是否匹配。

当我创建 2 个 Hashtable 时,我使用 UserPrincipalName 作为键。即[电子邮件保护]将被检索为 $Hash['[电子邮件保护]'] 并且如果您想要 LocalOffice 属性,那么它将是 $Hash['[电子邮件保护]'].LocalOffice 因此,我们所要做的就是循环遍历两个哈希表中较小的一个的所有键,将值与第一个哈希表进行比较,并进行相应的更新。

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

相关内容