Wix工具集:在“因为存在其他客户端而禁止卸载组件”后进行完全清理

14

今天我发现我的安装程序不能正确卸载了。也就是说,在控制面板中卸载后,我的应用程序不再显示,但所有文件仍然存在。我查看了日志文件,发现大量的“因为存在另一个客户端而禁止卸载组件”,据我所知这意味着我搞砸了……

那么最好的清理电脑的方法是什么?如何避免将来发生这种情况?这可能是由于之前版本的应用程序没有完全卸载的原因吗?

不幸的是,由于各种原因,使用虚拟机不可行。

信息:出于开发和测试目的,我通常会使用1.0.xxxxx测试并创建安装程序,其中xxxxx通常保持不变。我的upgradecode始终相同。此外,我正在使用heat,尽可能让wix自动生成GUID。另外,我有一个CA用于在安装后显示我的readme文件,还有一个用于执行批处理文件(使用powercfg修改注册表条目)。在卸载时,运行一个可执行文件以导入.reg文件以恢复修改的注册表条目(因为它们将被wix卸载)。


不,那不是错误;那是信息提示。由于您正在安装共享组件的产品,因此在卸载所有产品之前,该组件不应被删除。您可能希望在安装下一个构建生成的产品之前先卸载当前产品。 - Tom Blodget
4个回答

16
我们最近遇到这样一种情况,我们的开发机器不能在卸载时删除所有组件。但是,在其他机器上,WiX安装程序的工作正常。
因此,如果您卸载之前的产品版本不正确,并收到消息“Disallowing uninstallation of component: {GUID} since another client exists”,那么您很可能在注册表中留下了孤立的组件。
使用PowerShell删除那些隐藏的注册表键比使用第三方应用程序更加优雅的解决方法。
$productName = "Path\\YourProductName"  # this should basically match against your previous
# installation path. Make sure that you don't mess with other components used 
# by any other MSI package

$components = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\
$count = 0

foreach ($c in $components) 
{
    foreach($p in $c.Property)
    {
        $propValue = (Get-ItemProperty "Registry::$($c.Name)" -Name "$($p)")."$($p)"
        if ($propValue -match $productName) 
        {
            Write-Output $propValue
            $count++
            Remove-Item "Registry::$($c.Name)" -Recurse
        }
    }
}

Write-Host "$($count) key(s) removed"

如果你想获得更详细的关于消息“禁止卸载…”原因的解释,请看这里


谢谢!我能够修改内容并且解决了我正在构建的WixInstaller应用程序的卸载问题。 - ybull
这个程序可以满足我的需求,但不幸的是在我的机器上运行非常缓慢(大约需要8个小时)。我还遇到了一些非致命错误,因为我的注册表中存在但甚至从regedit中也无法查看的组件,可能是机器问题。 - oats
在使用WiX开发MSI安装程序时,我在我的开发机上进行了多次安装迭代,导致我的注册表处于一种混乱状态。虽然MSI可以运行卸载操作,但却无法删除任何文件。我当时真应该花时间设置一个虚拟机进行测试。最初的脚本出现了一个错误,即“malformed px character escape”,但解决方案在这里:https://stackoverflow.com/questions/66780396/malformed-px-character-escape-in-powershell。当我运行了这个脚本后,它删除了对我的安装文件的引用三次,问题就解决了。 - undefined

12
似乎您需要卸载安装了不需要的组件的功能(或整个产品)。Windows Installer有一个API用于查询组件、功能和产品。WiX Toolset已经包含了一个名为DTF的API包装器,您可以使用它来按组件查询功能。
因此,请使用您最喜欢的.NET脚本运行程序(我的是LINQPad),并运行查询。例如,要找出如何删除"candle.exe":
// using System.Linq;
// using Microsoft.Deployment.WindowsInstaller;

// <ref>"C:\Program Files (x86)\WiX Toolset v3.8\SDK\
         Microsoft.Deployment.WindowsInstaller.dll"</ref>

ComponentInstallation.AllComponents
    .Where(c=>c.State == InstallState.Local)
    .Where(c => c.Path.ToLowerInvariant().EndsWith(@"\candle.exe"))
    .SelectMany(c => c.ClientProducts
        .SelectMany(p => p.Features.Where(f => f.Usage.UseCount > 0)
            .Select(f => new {
                c.Path, 
                f.FeatureName,
                p.LocalPackage,
                p.UserSid, 
                p.ProductCode})))

LINQPad即时共享

LINQPad Output

然后,运行msiexec /x <ProductCode>来删除产品的所有功能,
或者msiexec /i <LocalPackage> REMOVE=<FeatureName>只删除安装该组件的功能。

10
即使这是一个有点陈旧的链接,但发布我的发现可能对其他面临相同情况的人有帮助。如果您在日志文件中找到“禁止卸载组件:{某些GUID},因为存在另一个客户端”,则原因可能是您以前的安装仍然在引用此组件/GUID。此外,如果您冒险尝试手动删除注册表键,则可能无法在regedit中找到GUID。但是,其中可能会有一些隐藏的注册表项。在这种情况下,使用RegSeeker工具http://www.hoverdesk.net/ ,它可以帮助查找隐藏的注册表项。要小心谨慎,因为您要处理注册表。根据GUID,从解决方案/项目中获取组件的名称,并使用此工具的“在注册表中查找”选项找到它。仅验证和删除您认为不需要的条目。诚然,我知道使用以下链接得知这个工具:http://www.daviddeley.com/solutions/msiexec/index.htm

稍作更正:注册表项本身并非隐藏的(可以通过regedit或其他注册表查看/编辑工具正常查看),但它们不包含组件的GUID。相反,GUID值被转换为字节,字节被转换为十六进制字符串,并作为注册表路径的一部分显示。 - Mike Rosoft

0

遵循这个工作流程非常完美:

如何解决“由于存在其他客户端,禁止卸载组件”?

在那里创建了这两个文件之后,在应用下一步之前需要删除它们的BOM。我使用了Notepad ++(编码 > 以UTF-8编码)。

对于每行添加/f选项可以轻松使用Notepad ++的多光标 - 使用SHIFT+ALT+箭头激活它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接