PowerShell中快速对比两数组找重复值并保留原结构问题
PowerShell高效对比AD与Excel服务器列表并保留完整对象结构
问题需求
需要在PowerShell中完成以下操作:
- 从Active Directory(AD)获取服务器名称列表
- 导入含多属性(ServerName、TestServer、DMZServer、PatchWindow、ASA等)的Excel服务器数据
- 找出同时存在于AD和Excel中的服务器,并且保留Excel数据的原有对象结构
- 之前尝试LINQ的
Intersect方法返回空结果,使用foreach循环逐个校验效率过低,寻求高效方案
问题分析
- LINQ Intersect失效原因:
- AD获取的服务器名是纯主机名(如
SERVER01),而Excel处理后的部分服务器名添加了.dmz.com后缀,两者字符串不匹配 - 即使字符串匹配,
Intersect仅返回匹配的字符串值,无法保留Excel中的完整对象属性
- AD获取的服务器名是纯主机名(如
- foreach循环效率低:逐个遍历对比的时间复杂度为O(n*m),数据量较大时性能极差
高效解决方案
使用**哈希集合(HashSet)**存储AD服务器名,利用其O(1)的查找效率,快速过滤Excel中的匹配对象:
优化后的完整脚本
CLS $ErrorActionPreference = 'SilentlyContinue' $ErrorFile = Join-Path -Path (Split-Path $script:MyInvocation.MyCommand.Path) -ChildPath "ERROR.txt" # 配置TLS1.2并确保ImportExcel模块可用 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if (-not (Get-Module -ListAvailable -Name "ImportExcel")) { Install-PackageProvider -Name NuGet -Scope CurrentUser -Force -Confirm:$False Install-Module -Name ImportExcel -Scope CurrentUser -Force -Confirm:$False } Import-Module ImportExcel -Force CLS Get-Date Write-Host " " # 全局变量 $Path = Split-Path $script:MyInvocation.MyCommand.Path # 读取Excel数据并转换为自定义对象 $ExcelFile = (Get-ChildItem -Path "$Path\*.xlsx").FullName $worksheet = (Open-ExcelPackage -Path $ExcelFile).Workbook.Worksheets[1].Name $Servers = Import-Excel -Path $ExcelFile -WorkSheetname $worksheet -StartRow 1 # 转换Excel数据结构并去重 $ServersList = $Servers | ForEach-Object { [pscustomobject]@{ 'ServerName' = $_.Child 'TestServer' = $_.'Test server' 'DMZServer' = $_.DMZ 'PatchWindow' = $_.'Patch Window' 'ASA' = $_.Primary } } | Sort-Object -Property ServerName -Unique # 处理DMZ服务器后缀 $FilteredServers = $ServersList | ForEach-Object { if ($_.DMZServer -eq $true) { $_.ServerName += ".dmz.com" } $_ } # 从AD获取启用的服务器名称,存入HashSet(高效查找) $searcher = [adsisearcher]::new() $searcher.Sort.PropertyName = "name" $searcher.SearchRoot = [adsi]"LDAP://OU=Servers,DC=blah,DC=com" $searcher.PageSize = 100 $searcher.Filter = "(&(objectCategory=computer)(objectClass=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" $searcher.PropertiesToLoad.AddRange(('name','distinguishedname')) # 创建HashSet存储AD服务器名(忽略大小写) $ADServerNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($Computer in $searcher.FindAll()) { $null = $ADServerNames.Add($Computer.Properties.name[0]) } # 高效筛选同时存在于AD和Excel中的服务器对象 $MatchingServers = $FilteredServers | Where-Object { $ADServerNames.Contains($_.ServerName) } # 输出结果或存入变量使用 $MatchingServers
关键优化点
- 哈希集合存储AD服务器名:
- 使用
HashSet<string>存储AD服务器名,查找操作的时间复杂度为O(1),远快于数组遍历 - 指定
OrdinalIgnoreCase忽略大小写,避免因大小写差异导致匹配失败
- 使用
- 管道化处理数据:
- 替换原脚本中
+=数组拼接的方式,改用管道ForEach-Object生成对象,避免频繁创建新数组带来的性能损耗
- 替换原脚本中
- 直接过滤完整对象:
- 通过
Where-Object直接筛选FilteredServers中ServerName存在于哈希集合的对象,直接保留完整的Excel属性结构
- 通过
验证说明
- 若AD中的服务器名是纯主机名,而Excel处理后添加了后缀(如
.dmz.com),需确保AD中是否存在带后缀的名称,或调整逻辑(如去掉Excel中的后缀再对比) - 哈希集合的忽略大小写配置可根据实际环境调整,若环境严格区分大小写则移除
[System.StringComparer]::OrdinalIgnoreCase参数
内容的提问来源于stack exchange,提问作者Koobah84
相关产品推荐
相关产品推荐

