自学内容网 自学内容网

使用Chocolatey打包MSI软件包的完整解决方案及技术总结

使用Chocolatey打包MSI软件包的完整解决方案及技术总结

在Windows系统上使用Chocolatey管理软件包是一种高效且自动化的方式,尤其是针对MSI格式的软件包。然而,在实际操作中,我们可能会遇到各种问题,例如检测旧版本、卸载旧版本以及处理多个匹配记录等。本文将详细记录从问题发现到最终解决的全过程,并分享最终的Chocolatey打包脚本,希望能为软件仓库维护人员解决类似问题提供启发。


问题背景

我们希望通过Chocolatey将一个MSI软件包打包成可安装的Chocolatey包,并实现以下功能:

  1. 检测是否存在旧版本。
  2. 如果存在旧版本,先卸载旧版本。
  3. 安装新版本的MSI软件包。
  4. 确保整个过程自动化且无用户干预。

问题与解决过程

1. 检测旧版本耗时过长

问题
起初,我们尝试使用以下命令检测是否存在已安装的软件:

Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Your Software Name*" }

然而,这个命令会触发所有已安装MSI软件的一致性检查,导致系统卡顿甚至长时间无响应。

解决方案
改用注册表查询的方式,通过以下路径快速检索已安装的软件信息:

  • 64位应用程序路径:HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  • 32位应用程序路径:HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall

优化后的查询命令如下:

Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | 
Get-ItemProperty | 
Where-Object { $_.DisplayName -like "*Your Software Name*" }

2. 检测到多个匹配记录

问题
在注册表中查询时,可能会返回多个匹配记录(例如同一软件的不同版本或不同语言包)。这可能导致脚本只处理第一个匹配项,而忽略其他记录。

解决方案
通过遍历所有匹配记录,逐一处理每个已安装的软件。具体实现如下:

$installedSoftwareList = Get-ChildItem -Path $path | 
                         Get-ItemProperty | 
                         Where-Object { $_.DisplayName -like "*Your Software Name*" }

foreach ($installedSoftware in $installedSoftwareList) {
    # 针对每个匹配的软件进行处理
}

3. 卸载旧版本失败

问题
在尝试卸载旧版本时,我们直接使用 UninstallString 作为 Start-Process-FilePath 参数,但由于 UninstallString 包含了路径和参数,导致报错 InvalidOperationException

例如:

MsiExec.exe /X{GUID}

解决方案
UninstallString 拆分为可执行文件路径和参数,然后分别传递给 Start-Process-FilePath-ArgumentList 参数。具体实现如下:

if ($uninstallString -match '^(.*\.exe)(.*)$') {
    $exePath = $matches[1]
    $arguments = $matches[2].Trim()
    $arguments = $arguments -replace '/I', '/X'  # 替换为卸载参数
    $arguments += ' /qn'  # 添加静默卸载参数

    Start-Process -FilePath $exePath -ArgumentList $arguments -Wait -NoNewWindow
}

最终完整的Chocolatey打包脚本

以下是经过优化后的完整脚本,能够检测并卸载旧版本,然后静默安装新版本的MSI软件包:

$ErrorActionPreference = 'Stop'

# 定义软件名称和新版本号
$softwareName = 'Your Software Name'  # 替换为实际的软件名称
$newVersion = '1.2.3'  # 替换为新版本号

# 定义注册表路径
$registryPaths = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
)

# 检查是否存在旧版本并卸载
foreach ($path in $registryPaths) {
    $installedSoftwareList = Get-ChildItem -Path $path | 
                             Get-ItemProperty | 
                             Where-Object { $_.DisplayName -like "*$softwareName*" }

    foreach ($installedSoftware in $installedSoftwareList) {
        $oldVersion = $installedSoftware.DisplayVersion
        Write-Host "Found installed version: $oldVersion"

        if ([version]$oldVersion -lt [version]$newVersion) {
            $uninstallString = $installedSoftware.UninstallString
            if ($uninstallString -match '^(.*\.exe)(.*)$') {
                $exePath = $matches[1]
                $arguments = $matches[2].Trim()
                $arguments = $arguments -replace '/I', '/X'  # 替换为卸载参数
                $arguments += ' /qn'  # 添加静默卸载参数

                Write-Host "Uninstalling version $oldVersion..."
                try {
                    Start-Process -FilePath $exePath -ArgumentList $arguments -Wait -NoNewWindow
                }
                catch {
                    Write-Host "Error uninstalling version $oldVersion: $_"
                    continue  # 继续处理下一个版本
                }
            }
        }
        else {
            Write-Host "Version $oldVersion is up to date. No action needed."
        }
    }
}

# 安装新版本的MSI
$packageArgs = @{
    packageName    = $env:ChocolateyPackageName
    fileType       = 'MSI'
    url            = 'https://example.com/your-software.msi'  # 替换为实际的下载URL
    softwareName   = $softwareName
    checksum       = '1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF'  # 替换为实际的checksum值
    checksumType   = 'sha256'
    silentArgs     = "/qn /norestart ALLUSERS=1"  # 添加ALLUSERS=1以确保所有用户都能访问
    validExitCodes = @(0, 3010, 1641)
}

Install-ChocolateyPackage @packageArgs

总结与思考

  1. 问题分解与逐步解决
    在面对复杂的问题时,将其分解为小问题逐一解决。例如,本案例中我们分别处理了检测、卸载和安装三个步骤。

  2. 选择合适的方法
    遇到性能瓶颈时(如使用 Get-WmiObject),及时切换到更高效的方法(如注册表查询)。

  3. 处理异常情况
    考虑到可能出现多个匹配项或卸载失败等情况,通过循环和异常捕获机制提高脚本的健壮性。

  4. 自动化与可维护性
    脚本设计时注重自动化和通用性,使其能够适应不同的软件和场景需求。

通过这个案例,我们不仅完成了具体任务,还锻炼了分析和解决问题的能力。希望这篇文章能为您在技术实践中提供启发!


原文地址:https://blog.csdn.net/zhlh_xt/article/details/144277163

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!