如何编写PowerShell脚本实现AD主机Ping检测及软件清单导出
AD域主机连通性检测与软件清单PowerShell实现方案
你现有代码的核心问题是未对Ping检测通过的在线主机做结果持久化收集,同时WMI查询段缺少输入源、存在性能隐患,以下是打通全流程的实现方式:
1. 连通性检测与在线主机TXT输出修正
原有逻辑仅做控制台颜色输出,没有存储在线主机条目,因此无法导出文件。修正点如下:
- 初始化数组专门存储在线主机的DNS名称
- 移除AD查询中冗余的
-Properties *参数(仅需DNSHostName属性即可,大幅提升查询速度) - 给
Test-Connection增加2秒超时参数,避免离线主机长时间阻塞脚本 - 检测完成后将在线主机列表导出为TXT文件
对应代码段:
Import-Module ActiveDirectory # 配置项 $targetOU = "OU=EnterOUstructurehere" # 替换为实际目标OU路径 $onlineHostsSavePath = "C:\Users\steven.e.hendricks\OneDrive - US Army\Desktop\Software Inventory\computers.txt" $onlineHosts = @() # 初始化数组存储在线主机 Get-ADComputer -Filter * -SearchBase $targetOU | ForEach-Object { $isOnline = Test-Connection -ComputerName $_.DNSHostName -Count 1 -BufferSize 16 -Quiet -TimeoutSeconds 2 if ($isOnline) { Write-Host -ForegroundColor Green $_.DNSHostName $onlineHosts += $_.DNSHostName # 在线主机加入数组 } else { Write-Host -ForegroundColor Red $_.DNSHostName } } # 导出在线主机列表到TXT $onlineHosts | Out-File -FilePath $onlineHostsSavePath -Encoding utf8
2. 软件清单查询逻辑修正
你原有软件查询代码存在两个问题:一是ForEach-Object没有接输入源无法运行,二是Win32_Product类存在已知性能问题(调用时会触发所有MSI软件的重配置,速度极慢且可能引发软件异常)。
两种实现方案可选:
方案A:沿用计划的WMI查询方式(仅识别MSI安装的软件)
读取第一步生成的computers.txt,逐台查询,增加错误捕获避免单台主机连接失败中断整个脚本:
# 配置项 $softwareCsvSavePath = "C:\Users\steven.e.hendricks\OneDrive - US Army\Desktop\Software Inventory\SoftwareInventoryfile.csv" # 先清空已有结果文件,避免重复追加 if (Test-Path $softwareCsvSavePath) { Remove-Item $softwareCsvSavePath } Get-Content $onlineHostsSavePath | ForEach-Object { $currentHost = $_ try { Get-WmiObject -Class Win32_Product -ComputerName $currentHost -ErrorAction Stop | Select-Object @{n='主机名';e={$currentHost}}, Name, Version | Sort-Object Name | Export-Csv $softwareCsvSavePath -Append -NoTypeInformation -Encoding utf8 } catch { Write-Host -ForegroundColor Yellow "主机 $currentHost WMI连接失败,跳过:$($_.Exception.Message)" } }
方案B:注册表查询方式(推荐,识别所有安装的软件,速度快10倍以上)
通过远程注册表读取主机的卸载列表,覆盖EXE、MSI等所有安装方式的软件,不会触发Win32_Product的异常问题:
$softwareCsvSavePath = "C:\Users\steven.e.hendricks\OneDrive - US Army\Desktop\Software Inventory\SoftwareInventoryfile.csv" if (Test-Path $softwareCsvSavePath) { Remove-Item $softwareCsvSavePath } Get-Content $onlineHostsSavePath | ForEach-Object { $currentHost = $_ $softwareList = @() try { # 定义远程注册表卸载项路径 $uninstallPaths = @( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall" ) $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $currentHost) foreach ($path in $uninstallPaths) { $key = $reg.OpenSubKey($path) if ($key) { $key.GetSubKeyNames() | ForEach-Object { $subKey = $key.OpenSubKey($_) $softName = $subKey.GetValue("DisplayName") $softVer = $subKey.GetValue("DisplayVersion") if ($softName -and $softName -notmatch "更新|KB\d+|Security Update") { # 过滤系统更新 $softwareList += [PSCustomObject]@{ 主机名 = $currentHost Name = $softName Version = $softVer } } $subKey.Close() } $key.Close() } } $reg.Close() $softwareList | Sort-Object Name | Export-Csv $softwareCsvSavePath -Append -NoTypeInformation -Encoding utf8 Write-Host -ForegroundColor Green "主机 $currentHost 软件清单获取完成" } catch { Write-Host -ForegroundColor Yellow "主机 $currentHost 连接失败,跳过:$($_.Exception.Message)" } }
运行注意事项
- 运行脚本的账号需要具备AD读取权限,以及目标主机的本地管理员权限
- 目标主机防火墙需要放通WMI、远程注册表对应的流量,否则会出现连接失败
- 如果不需要保留在线主机的TXT文件,可以直接在内存中传递
$onlineHosts数组做后续查询,不需要写文件再读取
内容的提问来源于stack exchange,提问作者Steven Hendricks
相关产品推荐
相关产品推荐

