我正在尝试在 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
答案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
}
}