尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

PowerShell实战:三种核心方法精准查询Windows软件版本信息

PowerShell实战:三种核心方法精准查询Windows软件版本信息 1. 从一次“版本困惑”说起为什么需要PowerShell查版本最近在帮一个朋友排查一个自动化脚本的兼容性问题他的脚本在A机器上跑得好好的一到B机器就报错。初步怀疑是某个依赖的第三方工具版本不一致。他当时的第一反应是“我打开那个软件的安装目录找找看有没有readme.txt或者version.txt。”结果找了半天要么文件不存在要么里面的版本号已经过时了。接着他又尝试了图形界面在软件的“关于”对话框里找到了版本号但我们需要在脚本里自动获取这个信息总不能每次都手动点开看吧这个场景相信很多做运维、开发或者系统管理的朋友都遇到过——我们需要一种可靠、可编程、跨软件统一的方式来获取系统中已安装软件的版本信息。这就是PowerShell大显身手的地方。它不仅仅是那个蓝色背景、能执行dir和cd的命令行而是一个强大的脚本环境和对象操作平台。在Windows世界里几乎所有的软件安装信息都会以某种形式在系统中留下“注册痕迹”而PowerShell最擅长的就是通过统一的命令和对象模型把这些分散的信息规整地提取出来。相比于手动查找、依赖图形界面或者编写复杂的注册表查询脚本使用PowerShell命令通常只需一行就能稳定地拿到我们想要的版本号。今天我们就来彻底搞懂在PowerShell中查看软件版本信息的几种核心方法以及它们背后的原理和适用场景让你下次再遇到版本问题时能像查字典一样快速定位。2. 核心武器库Get-Command、Get-WmiObject与注册表查询获取软件版本本质上是在查询软件的“身份信息”。在Windows系统中这些信息通常存储在几个关键位置可执行文件.exe自身的元数据、Windows管理规范WMI/CIM数据库、以及庞大的Windows注册表。PowerShell为我们提供了对应这些数据源的专用“探针”。2.1 第一探针Get-Command 与文件版本信息对于已经加入系统PATH环境变量或者你知道其完整路径的命令行工具或可执行文件Get-Command是你的首选。它的本职工作虽然是获取命令信息但顺带就能把版本信息“捎”出来。基本用法与原理当你运行Get-Command 程序名时PowerShell会去PATH指定的路径中查找该命令。找到后它会创建一个CommandInfo对象。对于外部程序Application这个对象包含一个FileVersionInfo属性它封装了可执行文件资源中的版本信息。让我们以最常用的git为例$gitCmd Get-Command git $gitCmd.FileVersionInfo执行后你会看到一个包含多项信息的对象其中对我们最有用的通常是FileVersion 文件版本号通常由开发者在编译时设置。ProductVersion 产品版本号通常是我们常说的“软件版本”。实战技巧精准提取通常我们只关心版本号。你可以通过管道|将结果传递给Select-Object来筛选(Get-Command git).FileVersionInfo.ProductVersion或者使用更简洁的格式Get-Command git | Select-Object -ExpandProperty FileVersionInfo | Select-Object ProductVersion这里有一个关键点FileVersionInfo本身是一个对象Select-Object -ExpandProperty会把这个对象“展开”然后我们才能继续选择其子属性ProductVersion。适用边界与坑点PATH依赖Get-Command只在PATH路径中查找。如果软件没在PATH里你需要指定完整路径Get-Command “C:\Program Files\MyApp\app.exe”。“命令”而非“软件” 它主要针对可执行命令。对于没有提供命令行入口的纯图形界面软件或者是一个DLL库Get-Command可能无法直接定位。版本信息可能为空 并非所有可执行文件都正确填充了版本资源。有些绿色软件或精简版可能没有这些信息此时FileVersionInfo的相关属性会是$null。2.2 第二探针WMI/CIM 查询已安装程序对于通过Windows InstallerMSI等标准方式安装的软件其信息会被记录在WMIWindows Management Instrumentation的Win32_Product类中或者更推荐的Win32Reg_AddRemovePrograms通过CIM。这是查询“控制面板-程序和功能”列表里那些程序的官方方式。使用Get-WmiObject传统方式较慢Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like *Visual Studio*} | Select-Object Name, Version这个命令会列出所有通过WMI注册的产品并通过Where-Object筛选出名称包含“Visual Studio”的项然后显示名称和版本。重要警告 直接查询Win32_Product类有一个众所周知的副作用它会触发所有MSI安装包的修复性验证。在系统软件很多时这个操作会异常缓慢并且可能在事件日志中产生大量冗余信息。因此不建议在生产环境或频繁执行的脚本中使用此方法。使用Get-CimInstance推荐方式更高效CIMCommon Information Model是WMI的更新、更标准的实现。查询注册表路径来获取程序列表避免了Win32_Product的副作用。# 查询64位系统下的已安装程序主要列表 $UninstallPath “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\” # 查询32位程序在64位系统上的列表如果存在 $UninstallPathWow64 “HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\” # 组合查询并显示 Get-ChildItem -Path $UninstallPath, $UninstallPathWow64 -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName -and $_.DisplayVersion} | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Sort-Object DisplayName这段脚本做了几件事定义了64位和32位程序在注册表中的卸载项路径。Get-ChildItem获取这些路径下的所有子项每个子项代表一个已安装程序。Get-ItemProperty获取每个子项的具体属性值。Where-Object过滤掉那些没有DisplayName和DisplayVersion的项有些是系统组件不显示给用户。Select-Object选择我们关心的字段程序名、版本、出版商和安装日期。Sort-Object按程序名排序方便查找。如何查找特定软件假设我要找“Google Chrome”的版本$chrome Get-ChildItem -Path $UninstallPath, $UninstallPathWow64 -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName -eq “Google Chrome”} if ($chrome) { Write-Host “找到 Chrome: $($chrome.DisplayName), 版本: $($chrome.DisplayVersion)” } else { Write-Host “未找到 Google Chrome 的安装信息。” }2.3 第三探针直接查询注册表如上节CIM查询所示其本质也是读取注册表。有时对于某些特定软件我们可能需要更直接、更定制化地查询注册表。注册表是Windows配置信息的核心仓库很多软件会把自己的安装和版本信息存放在固定的或自定义的注册表路径下。通用软件注册表位置64位系统上的64位软件HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\64位系统上的32位软件HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\当前用户安装的软件HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\使用PowerShell访问注册表PowerShell将注册表视为一个驱动器使用起来像文件系统一样方便。# 切换到注册表路径 cd HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ # 列出所有子项 Get-ChildItem # 查看某个特定程序的属性例如一个随机GUID代表的程序 $appKey Get-Item “.{一些GUID}” $appKey.GetValue(“DisplayName”) $appKey.GetValue(“DisplayVersion”)实战案例查询.NET Framework版本.NET Framework的版本信息存储在另一个特定路径$netPath “HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full” if (Test-Path $netPath) { $netKey Get-ItemProperty -Path $netPath $release $netKey.Release # 根据Release号映射为熟悉的版本名例如 528040 对应 .NET Framework 4.8 $versionMap { 528040 “4.8” 461808 “4.7.2” # ... 其他映射 } if ($versionMap.ContainsKey($release)) { Write-Host “已安装 .NET Framework $($versionMap[$release])” } else { Write-Host “检测到 Release 号: $release请查阅微软官方文档映射版本。” } }这个例子展示了如何读取一个特定的数值Release然后通过查表的方式将其转换为人类可读的版本号。这是处理那些不直接存储“DisplayVersion”的软件信息的常用思路。3. 信息呈现的艺术格式化输出与结果筛选获取到原始数据对象只是第一步如何清晰、美观、有针对性地把信息呈现出来是提升脚本可用性的关键。PowerShell的格式化命令Format-List,Format-Table,Format-Wide和筛选命令Select-Object,Where-Object是我们的画笔。3.1 格式化三剑客List、Table与WideFormat-List(fl): 以列表形式显示对象的所有或指定属性每个属性占一行。非常适合查看一个对象的详细信息。# 查看git命令的完整版本信息 Get-Command git | Format-List FileVersionInfo # 或者查看注册表程序项的多个属性 $app | Format-List DisplayName, DisplayVersion, InstallLocation, PublisherFormat-Table(ft): 以表格形式显示多个对象的指定属性。适合对比和浏览。# 以表格形式列出所有已安装程序中名称包含“Microsoft”且版本不为空的项 Get-ChildItem -Path $UninstallPath, $UninstallPathWow64 -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName -like “*Microsoft*” -and $_.DisplayVersion} | Format-Table DisplayName, DisplayVersion, InstallDate -AutoSize-AutoSize参数会让PowerShell自动调整列宽使表格更紧凑。Format-Wide(fw): 将单个属性的值以多列的形式展开显示非常节省空间适合快速浏览大量项目的名称。# 宽幅显示所有已安装程序的名称 Get-ChildItem -Path $UninstallPath -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName} | Format-Wide DisplayName -Column 33.2 精准筛选Select-Object 与 Where-Object格式化决定了怎么看而筛选决定了看什么。Select-Object(select): 选择要显示的属性。可以创建计算属性甚至获取对象的第一/最后几个。# 只选择名称和版本并创建一个新的“主要版本”计算属性 $apps | Select-Object DisplayName, DisplayVersion, {Name“MajorVersion”; Expression{($_.DisplayVersion -split ‘\.’)[0]}}Where-Object(where或?): 根据条件过滤对象。这是找到目标软件的关键。# 找到所有版本号以“20”开头的程序例如2021, 2022版 $apps | Where-Object {$_.DisplayVersion -like “20*”} # 找到安装时间在2023年之后的程序 $apps | Where-Object { $_.InstallDate -and [datetime]::ParseExact($_.InstallDate, “yyyyMMdd”, $null) -gt (Get-Date “2023-01-01”) }注意InstallDate在注册表中通常是yyyyMMdd格式的字符串需要进行解析才能做日期比较。3.3 输出到文件与后续处理对于脚本化操作我们经常需要将结果保存下来。# 输出到CSV文件方便用Excel打开分析 $apps | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Export-Csv -Path “C:\InstalledSoftware.csv” -NoTypeInformation -Encoding UTF8 # 输出到JSON文件方便其他编程语言读取 $apps | Select-Object DisplayName, DisplayVersion | ConvertTo-Json | Out-File “C:\apps.json” # 简单的文本日志 “ 软件版本检查报告 ” | Out-File “C:\report.txt” $apps | Format-Table -AutoSize | Out-File “C:\report.txt” -Append-NoTypeInformation参数在导出CSV时非常重要它会去掉PowerShell添加的类型信息行让CSV文件更干净。-Encoding UTF8确保中文字符能正确保存。4. 实战演练构建一个通用的软件版本检查脚本现在我们将前面所有的知识点组合起来编写一个实用的、健壮的脚本。这个脚本的目标是给定一个软件名称支持模糊匹配从多个可能的信息源PATH命令、注册表中查找其版本号并以清晰的结构化格式返回结果。4.1 脚本设计与核心逻辑脚本思路如下参数定义 允许用户通过-SoftwareName参数输入要查找的软件名支持部分匹配。多源查询 a.源1PATH命令 尝试将输入作为命令名用Get-Command查找成功则提取文件版本。 b.源2注册表系统 在系统级的卸载注册表项中查找DisplayName包含输入字符串的程序。 c.源3注册表当前用户 在当前用户级的卸载注册表项中查找。结果合并与去重 同一个软件可能在不同源中找到需要合并信息优先使用更具体的版本号。格式化输出 将结果以清晰的表格形式输出到屏幕并可选地导出到文件。4.2 完整脚本代码与逐行解析# .SYNOPSIS 查找指定软件的版本信息。 .DESCRIPTION 从系统PATH命令、注册表系统和当前用户的已安装程序列表中查找指定名称的软件并返回其版本号。 .PARAMETER SoftwareName 要查找的软件名称支持模糊匹配如 “chrome”, “visual studio”。 .PARAMETER ExportPath 可选将结果导出为CSV文件的路径如 “C:\report.csv”。 .EXAMPLE .\Get-SoftwareVersion.ps1 -SoftwareName “chrome” .EXAMPLE .\Get-SoftwareVersion.ps1 “git” -ExportPath “.\versions.csv” # param( [Parameter(Mandatory$true, Position0)] [string]$SoftwareName, [Parameter(Mandatory$false)] [string]$ExportPath ) # 初始化一个数组来存储所有找到的结果 $results () # 1. 尝试从PATH命令中查找 Write-Host “正在从系统PATH中查找命令 ‘$SoftwareName’...” -ForegroundColor Cyan try { $cmd Get-Command -Name $SoftwareName -ErrorAction Stop if ($cmd -and $cmd.CommandType -eq ‘Application’ -and $cmd.FileVersionInfo) { $result [PSCustomObject]{ SoftwareName $cmd.Name DisplayName $cmd.FileVersionInfo.FileDescription Version $cmd.FileVersionInfo.ProductVersion Source “PATH Command” Path $cmd.Source } $results $result Write-Host “ [√] 在PATH中找到: $($cmd.Name) ($($result.Version))” -ForegroundColor Green } } catch { Write-Host “ [ ] 未在PATH中找到命令 ‘$SoftwareName’。” -ForegroundColor Gray } # 2. 定义注册表查询函数 function Search-RegistryUninstall([string]$registryPath, [string]$sourceName) { Write-Host “正在搜索注册表路径: $registryPath ...” -ForegroundColor Cyan if (Test-Path $registryPath) { $foundItems Get-ChildItem -Path $registryPath -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { $_.DisplayName -and $_.DisplayVersion -and ($_.DisplayName -match [System.Text.RegularExpressions.Regex]::Escape($SoftwareName) -or $_.PSChildName -match [System.Text.RegularExpressions.Regex]::Escape($SoftwareName)) } foreach ($item in $foundItems) { $result [PSCustomObject]{ SoftwareName $SoftwareName DisplayName $item.DisplayName Version $item.DisplayVersion Source $sourceName Path $registryPath “\” $item.PSChildName } $results $result Write-Host “ [√] 找到: $($item.DisplayName) ($($item.DisplayVersion))” -ForegroundColor Green } if (-not $foundItems) { Write-Host “ [ ] 在此路径下未找到匹配项。” -ForegroundColor Gray } } else { Write-Host “ [ ] 注册表路径不存在: $registryPath” -ForegroundColor Yellow } } # 3. 搜索系统级和用户级注册表 $registryPaths ( {Path“HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\”; Name“System (64-bit)”}, {Path“HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\”; Name“System (32-bit)”}, {Path“HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\”; Name“Current User”} ) foreach ($reg in $registryPaths) { Search-RegistryUninstall -registryPath $reg.Path -sourceName $reg.Name } # 4. 结果处理与输出 if ($results.Count -eq 0) { Write-Host “n未找到任何与 ‘$SoftwareName’ 相关的软件版本信息。” -ForegroundColor Red exit 1 } else { Write-Host “n” (“” * 60) -ForegroundColor Magenta Write-Host “查询完成共找到 $($results.Count) 个结果” -ForegroundColor Magenta Write-Host (“” * 60) -ForegroundColor Magenta # 去重同一软件可能在不同源中找到取版本号非空且最长的通常更具体 $uniqueResults $results | Group-Object DisplayName | ForEach-Object { $_.Group | Sort-Object {[string]::IsNullOrEmpty($_.Version)} | Select-Object -First 1 } # 格式化输出到屏幕 $uniqueResults | Format-Table -Property ( {Label“软件名称”; Expression{$_.DisplayName}; Width30}, {Label“版本”; Expression{$_.Version}; Width15}, {Label“信息来源”; Expression{$_.Source}; Width20}, {Label“路径/标识”; Expression{$_.Path}; Width50} ) -AutoSize # 5. 可选导出到CSV文件 if ($ExportPath) { try { $uniqueResults | Select-Object DisplayName, Version, Source, Path | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8 Write-Host “n结果已导出至: $ExportPath” -ForegroundColor Cyan } catch { Write-Host “n导出文件失败: $_” -ForegroundColor Red } } }4.3 脚本使用示例与解读基本查找.\Get-SoftwareVersion.ps1 -SoftwareName “python”这个命令会搜索PATH中的python命令并在所有注册表卸载项中查找名称包含“python”的软件。输出会是一个表格列出所有找到的Python发行版如Python 3.11, Anaconda等及其版本和来源。模糊匹配与导出.\Get-SoftwareVersion.ps1 “microsoft visual c” -ExportPath “C:\Temp\vc_versions.csv”这会查找所有名称中包含“microsoft visual c”的运行时库并将去重后的结果保存到CSV文件中。你会发现系统里可能安装了多个不同年份和版本的VC Redistributable。脚本亮点解析错误处理 使用try-catch处理Get-Command可能抛出的“命令未找到”异常避免脚本因单点失败而终止。正则表达式转义 在Where-Object中使用[Regex]::Escape($SoftwareName)对输入进行转义防止用户输入中包含正则特殊字符如.导致匹配错误。结果去重逻辑 使用Group-Object按DisplayName分组然后对每组按版本号是否为空排序取第一个即优先取有版本号的记录。这是一种简单有效的去重策略。进度提示 每个搜索阶段都有颜色化的输出提示让用户清楚脚本正在做什么。灵活的路径处理 同时搜索了64位、32位系统路径和当前用户路径覆盖了绝大多数安装情况。5. 进阶话题特殊场景与疑难杂症处理在实际环境中事情往往不会一帆风顺。你可能会遇到一些“不按常理出牌”的软件或者需要更精细的版本控制逻辑。5.1 处理“绿色软件”与便携式应用绿色软件或便携式应用通常不会向注册表写入卸载信息也不一定在PATH中。对于这类软件最可靠的方法是直接检查其可执行文件或特定版本文件。策略检查文件属性$appPath “D:\Tools\MyPortableApp\App.exe” if (Test-Path $appPath) { $versionInfo (Get-Item $appPath).VersionInfo Write-Host “文件版本: $($versionInfo.FileVersion)” Write-Host “产品版本: $($versionInfo.ProductVersion)” } else { # 尝试查找目录下的其他可能文件如 version.ini, readme.txt $versionFile Get-ChildItem -Path (Split-Path $appPath) -Filter “*version*” -File | Select-Object -First 1 if ($versionFile) { Get-Content $versionFile.FullName | Select-String -Pattern “\d\.\d\.\d” } }5.2 解析复杂的版本字符串有些软件的版本号可能包含字母、日期或构建号如2023.1.0-rc2或3.11.4build.123。我们需要从中提取出可比较的主版本号、次版本号。function Parse-VersionString { param([string]$VersionString) # 使用正则表达式提取数字部分 if ($VersionString -match ‘(\d)\.(\d)\.(\d)’) { $major [int]$matches[1] $minor [int]$matches[2] $build [int]$matches[3] return [PSCustomObject]{ Original $VersionString Major $major Minor $minor Build $build ComparableVersion [version]“$major.$minor.$build” } } elseif ($VersionString -match ‘(\d)\.(\d)’) { $major [int]$matches[1] $minor [int]$matches[2] return [PSCustomObject]{ Original $VersionString Major $major Minor $minor Build $null ComparableVersion [version]“$major.$minor” } } else { Write-Warning “无法解析版本字符串: ‘$VersionString’” return $null } } # 使用示例 $verObj Parse-VersionString -VersionString (Get-Command git).FileVersionInfo.ProductVersion if ($verObj) { Write-Host “主版本: $($verObj.Major), 次版本: $($verObj.Minor)” # 可以进行版本比较 $minRequired [version]“2.30.0” if ($verObj.ComparableVersion -ge $minRequired) { Write-Host “版本符合要求。” } }5.3 远程计算机查询与批量操作PowerShell的强大之处在于其远程处理能力。你可以使用Invoke-Command在远程计算机上执行版本检查脚本。前提条件 确保远程计算机已启用PSRemoting通常在域环境中配置并且你有相应的权限。# 定义一个脚本块包含我们的查询逻辑 $scriptBlock { param($Name) $path “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\” $app Get-ChildItem -Path $path | Get-ItemProperty | Where-Object {$_.DisplayName -like “*$Name*”} | Select-Object -First 1 if ($app) { return “$($app.DisplayName): $($app.DisplayVersion)” } else { return “未找到匹配 ‘$Name’ 的软件。” } } # 在远程计算机Server01上执行 $computerName “Server01” $result Invoke-Command -ComputerName $computerName -ScriptBlock $scriptBlock -ArgumentList “SQL Server” Write-Host “$computerName 上的查询结果: $result”批量查询多台计算机$computers (“WebSrv01”, “WebSrv02”, “DBSrv01”) $softwareToFind “IIS” foreach ($comp in $computers) { try { $remoteResult Invoke-Command -ComputerName $comp -ScriptBlock { Get-WindowsFeature -Name Web-Server | Select-Object DisplayName, Installed } -ErrorAction Stop Write-Host “$comp : $($remoteResult.DisplayName) - 安装状态: $($remoteResult.Installed)” -ForegroundColor Green } catch { Write-Host “$comp : 远程查询失败 - $_” -ForegroundColor Red } }注意远程查询对网络和权限配置有要求且可能受防火墙策略影响。对于非域环境或严格限制的环境可能需要使用其他远程管理工具或代理。5.4 版本检查与自动化部署/合规性验证将版本检查集成到自动化流程中是常见需求。例如在部署脚本中确保目标机器上的.NET Framework版本达到要求。$requiredNetVersion [version]“4.7.2” $netPath “HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full” function Test-NetFrameworkVersion { if (-not (Test-Path $netPath)) { Write-Error “未检测到 .NET Framework 4.x 完整安装。” return $false } $releaseKey Get-ItemProperty -Path $netPath -Name Release $release $releaseKey.Release # 简化版本映射实际使用时需要更完整的映射表 $versionMap { 461808 [version]“4.7.2” 528040 [version]“4.8” 533320 [version]“4.8.1” } $installedVersion $versionMap[$release] if (-not $installedVersion) { Write-Warning “检测到未知的 Release 号: $release请手动验证版本。” return $false } if ($installedVersion -ge $requiredNetVersion) { Write-Host “[通过] .NET Framework 版本: $installedVersion” -ForegroundColor Green return $true } else { Write-Host “[失败] 需要 .NET Framework $requiredNetVersion 或更高当前为 $installedVersion” -ForegroundColor Red return $false } } # 在主部署脚本中调用 if (-not (Test-NetFrameworkVersion)) { # 触发安装流程或退出部署 Write-Host “正在尝试安装所需版本的 .NET Framework...” # ... 调用安装程序的代码 ... }这种模式可以扩展到任何需要特定版本依赖的软件如Java Runtime、VC Redistributable、数据库客户端等是实现环境一致性校验的基石。6. 避坑指南常见错误与最佳实践在长期使用PowerShell进行软件版本管理的实践中我积累了一些容易踩坑的地方和对应的解决方案。6.1 权限问题非管理员权限下的查询失败很多注册表路径特别是HKLM需要管理员权限才能完全读取。如果你的脚本在普通用户权限下运行可能会查不到某些系统级安装的软件。解决方案明确告知用户在脚本开头检查权限并给出提示。# 检查是否是管理员 $isAdmin ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] “Administrator”) if (-not $isAdmin) { Write-Warning “当前未以管理员身份运行。部分系统级安装的软件信息可能无法获取。” # 可以选择只查询HKCU当前用户路径 }结构化异常处理对可能因权限失败的操作使用try-catch。try { $items Get-ChildItem -Path “HKLM:\SOFTWARE\...” -ErrorAction Stop } catch [System.UnauthorizedAccessException] { Write-Warning “访问系统注册表被拒绝。请以管理员身份重新运行脚本。” # 记录到日志或采取降级方案 }6.2 性能优化避免全表扫描与缓存策略当系统中安装了大量软件时遍历所有注册表卸载项可能会比较慢几百到几千毫秒。在需要频繁检查或批量检查多台机器时这会影响效率。优化策略按需查询避免全量如果知道软件的确切名称或部分固定名称尽量在Where-Object中提前过滤而不是先获取所有项再过滤。# 较差的做法先获取所有再过滤 # $allApps Get-ChildItem $path | Get-ItemProperty # $targetApp $allApps | Where-Object {$_.DisplayName -like “*Chrome*”} # 较好的做法在管道中尽早过滤虽然Get-ChildItem本身仍会枚举所有子项但Get-ItemProperty只作用于过滤后的项 $targetApp Get-ChildItem $path | Where-Object {$_.PSChildName -like “*Chrome*”} | Get-ItemProperty注意PSChildName是子项的名称通常是GUID或软件标识有时比DisplayName过滤更快但准确性取决于软件命名规则。实现简单缓存对于相对静态的已安装软件列表可以将结果缓存到临时文件或变量中在一定时间内复用。$cacheFile “$env:TEMP\installed_apps_cache.json” $cacheMaxAgeMinutes 30 function Get-InstalledAppsCached { if (Test-Path $cacheFile) { $cacheInfo Get-Item $cacheFile if (((Get-Date) - $cacheInfo.LastWriteTime).TotalMinutes -lt $cacheMaxAgeMinutes) { Write-Verbose “从缓存读取已安装程序列表...” return Get-Content $cacheFile | ConvertFrom-Json } } Write-Verbose “重新生成缓存...” $apps Get-ChildItem -Path $UninstallPath, $UninstallPathWow64 -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName -and $_.DisplayVersion} | Select-Object DisplayName, DisplayVersion, Publisher $apps | ConvertTo-Json | Out-File $cacheFile return $apps }6.3 版本号比较的陷阱直接使用字符串比较版本号如“2.10” -gt “2.9”会得到错误结果因为字符串比较是逐字符进行的“10”的第一个字符“1”小于“9”。必须使用[version]类型进行转换和比较。# 错误示例 if (“2.10” -gt “2.9”) { Write-Host “正确” } else { Write-Host “错误” } # 输出错误 # 正确示例 $v1 [version]“2.10” $v2 [version]“2.9” if ($v1 -gt $v2) { Write-Host “正确” } else { Write-Host “错误” } # 输出正确 # 处理可能不标准的版本字符串 function SafeCompare-Version { param([string]$verA, [string]$verB) try { $vA [version]$verA $vB [version]$verB return $vA.CompareTo($vB) # 返回 -1, 0, 1 } catch { # 如果转换失败回退到字符串比较并记录警告 Write-Warning “版本号 ‘$verA’ 或 ‘$verB’ 格式不规范使用字符串比较。” return $verA.CompareTo($verB) } }6.4 处理多版本并存的复杂情况有些环境会同时存在同一个软件的多个主要版本例如 Python 2.7 和 Python 3.11或者多个版本的 Java Runtime。策略明确标识与路径区分# 查找所有Python安装 $pythonEntries Get-ChildItem -Path $UninstallPath, $UninstallPathWow64 -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.DisplayName -match “Python”} | Select-Object DisplayName, DisplayVersion, InstallLocation foreach ($py in $pythonEntries) { # 尝试通过安装路径下的python.exe获取更精确的版本 $exePath Join-Path $py.InstallLocation “python.exe” if (Test-Path $exePath) { $fileVer (Get-Command $exePath -ErrorAction SilentlyContinue).FileVersionInfo.ProductVersion Write-Host “$($py.DisplayName) (注册表: $($py.DisplayVersion), 文件: $fileVer) 位于: $($py.InstallLocation)” } else { Write-Host “$($py.DisplayName) (版本: $($py.DisplayVersion))” } }在这种情况下报告结果时最好同时给出注册表版本和实际可执行文件的版本并明确指出安装路径让用户能清晰区分不同实例。掌握这些方法后无论是快速手动检查一个软件的版本还是将其集成到复杂的自动化运维、持续集成/持续部署CI/CD流水线中你都能得心应手。PowerShell提供的这种可编程的、一致的对象化查询方式远比图形界面点击和手动搜索高效和可靠得多。下次当你需要回答“这台机器上到底装了什么版本的XXX”这个问题时希望这些命令和脚本能成为你条件反射般的第一选择。
返回列表