@joebigelow / wix / commits / 6f4fda58

ArpEntry reads QuietUninstallString or UninstallString, and uses UninstallArguments for the uninstall command line

Nir Bar committed Nov 20, 2023 at 18:00 UTC 6f4fda58d9303c889024e20ee39b1374ed0f02e1
8 files changed +135 -29
src/api/wix/WixToolset.Data/Symbols/WixBundleExePackageSymbol.cs
+17
@@ -57,6 +57,7 @@ namespace WixToolset.Data.Symbols
57 None = 0,
58 Bundle = 1,
59 ArpWin64 = 2,
60 + ArpUseUninstallString = 4,
61 }
62
63 public class WixBundleExePackageSymbol : IntermediateSymbol
@@ -157,6 +158,22 @@ namespace WixToolset.Data.Symbols
158 }
159 }
160
161 + public bool ArpUseUninstallString
162 + {
163 + get { return this.Attributes.HasFlag(WixBundleExePackageAttributes.ArpUseUninstallString); }
164 + set
165 + {
166 + if (value)
167 + {
168 + this.Attributes |= WixBundleExePackageAttributes.ArpUseUninstallString;
169 + }
170 + else
171 + {
172 + this.Attributes &= ~WixBundleExePackageAttributes.ArpUseUninstallString;
173 + }
174 + }
175 + }
176 +
177 public bool Repairable => this.RepairCommand != null;
178
179 public bool Uninstallable => this.UninstallCommand != null;
src/burn/engine/exeengine.cpp
+23 -3
@@ -81,6 +81,14 @@ extern "C" HRESULT ExeEngineParsePackageFromXml(
81 hr = XmlGetYesNoAttribute(pixnExePackage, L"ArpWin64", &pPackage->Exe.fArpWin64);
82 ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @ArpWin64.");
83
84 + // @ArpUseUninstallString
85 + hr = XmlGetYesNoAttribute(pixnExePackage, L"ArpUseUninstallString", &pPackage->Exe.fArpUseUninstallString);
86 + ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @ArpWin64.");
87 +
88 + // @UninstallArguments
89 + hr = XmlGetAttributeEx(pixnExePackage, L"UninstallArguments", &pPackage->Exe.sczUninstallArguments);
90 + ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @UninstallArguments.");
91 +
92 pPackage->Exe.fUninstallable = TRUE;
93 }
94
@@ -481,7 +489,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
489 }
490 else if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action)
491 {
484 - ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null.");
492 + ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "%hs is null.", pPackage->Exe.fArpUseUninstallString ? "UninstallString" : "QuietUninstallString");
493
494 hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp);
495 ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString);
@@ -1122,8 +1130,20 @@ static HRESULT DetectArpEntry(
1130
1131 if (psczQuietUninstallString)
1132 {
1125 - hr = RegReadString(hKey, L"QuietUninstallString", psczQuietUninstallString);
1126 - ExitOnPathFailure(hr, fExists, "Failed to read QuietUninstallString.");
1133 + LPCWSTR sczUninstallStringName = pPackage->Exe.fArpUseUninstallString ? L"UninstallString" : L"QuietUninstallString";
1134 +
1135 + hr = RegReadString(hKey, sczUninstallStringName, psczQuietUninstallString);
1136 + ExitOnPathFailure(hr, fExists, "Failed to read %ls.", sczUninstallStringName);
1137 +
1138 + // If the uninstall string is an executable path then ensure it is enclosed in quotes
1139 + if (fExists && *psczQuietUninstallString && (L'\"' != **psczQuietUninstallString) && FileExistsEx(*psczQuietUninstallString, nullptr))
1140 + {
1141 + hr = StrAllocPrefix(psczQuietUninstallString, L"\"", 0);
1142 + ExitOnFailure(hr, "Failed to prepend UninstallString with quote.");
1143 +
1144 + hr = StrAllocConcat(psczQuietUninstallString, L"\"", 0);
1145 + ExitOnFailure(hr, "Failed to append quote to UninstallString.");
1146 + }
1147 }
1148
1149 LExit:
src/burn/engine/package.h
+1
@@ -362,6 +362,7 @@ typedef struct _BURN_PACKAGE
362 BURN_EXE_DETECTION_TYPE detectionType;
363
364 BOOL fArpWin64;
365 + BOOL fArpUseUninstallString;
366 LPWSTR sczArpKeyPath;
367 VERUTIL_VERSION* pArpDisplayVersion;
368
src/test/burn/TestData/ExePackageTests/PerMachineArpEntryWithUninstallStringExePackage/PerMachineArpEntryWithUninstallStringExePackage.wixproj new
+19
@@ -0,0 +1,19 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <OutputType>Bundle</OutputType>
5 + <InstallerPlatform>x64</InstallerPlatform>
6 + <BA>TestBA_x64</BA>
7 + <UpgradeCode>{0A3D66F0-21A8-41B5-BE9A-7A9ABDEC3AB5}</UpgradeCode>
8 + </PropertyGroup>
9 + <ItemGroup>
10 + <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
11 + </ItemGroup>
12 + <ItemGroup>
13 + <ProjectReference Include="..\..\TestBA\TestBAWixlib_x64\testbawixlib_x64.wixproj" />
14 + </ItemGroup>
15 + <ItemGroup>
16 + <PackageReference Include="WixToolset.Bal.wixext" />
17 + <PackageReference Include="WixToolset.NetFx.wixext" />
18 + </ItemGroup>
19 +</Project>
src/test/burn/TestData/ExePackageTests/PerMachineArpEntryWithUninstallStringExePackage/PerMachineArpEntryWithUninstallStringExePackage.wxs new
+18
@@ -0,0 +1,18 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +<?define ArpId = {21E99C0F-E604-439C-97E0-33B9771394BC}?>
4 +<?define ArpKeyPath = HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(var.ArpId)?>
5 +<?define ArpVersion = 1.0.0.0?>
6 +
7 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
8 + <Fragment>
9 + <PackageGroup Id="BundlePackages">
10 + <ExePackage Id="TestExe" PerMachine="yes"
11 + InstallArguments="/regw &quot;$(var.ArpKeyPath),DisplayVersion,String,$(var.ArpVersion)&quot; /regw &quot;$(var.ArpKeyPath),UninstallString,String,\&quot;[WixBundleExecutePackageCacheFolder]testexe.exe\&quot; /regd \&quot;$(var.ArpKeyPath)\&quot;&quot;">
12 + <ArpEntry Id="$(var.ArpId)" Version="$(var.ArpVersion)" Win64="yes" UseUninstallString="yes" />
13 +
14 + <PayloadGroupRef Id="TestExePayloads_x64" />
15 + </ExePackage>
16 + </PackageGroup>
17 + </Fragment>
18 +</Wix>
src/test/burn/WixToolsetTest.BurnE2E/ExePackageTests.cs
+22
@@ -33,6 +33,28 @@ namespace WixToolsetTest.BurnE2E
33 Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"testexe.exe\" /regd HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId}"));
34 }
35
36 + [RuntimeFact]
37 + public void CanInstallAndUninstallPerMachineArpEntryWithUninstallStringExePackage()
38 + {
39 + var perMachineArpEntryExePackageBundle = this.CreateBundleInstaller(@"PerMachineArpEntryWithUninstallStringExePackage");
40 + var arpEntryExePackage = this.CreateArpEntryInstaller(perMachineArpEntryExePackageBundle, "TestExe");
41 + var arpId = arpEntryExePackage.ArpId;
42 +
43 + arpEntryExePackage.VerifyRegistered(false);
44 +
45 + var installLogPath = perMachineArpEntryExePackageBundle.Install();
46 + perMachineArpEntryExePackageBundle.VerifyRegisteredAndInPackageCache();
47 + arpEntryExePackage.VerifyRegistered(true);
48 +
49 + Assert.True(LogVerifier.MessageInLogFile(installLogPath, $"TestExe.exe\" /regw \"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId},DisplayVersion,String,1.0.0.0\" /regw \"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId},UninstallString,String,\\\""));
50 +
51 + var uninstallLogPath = perMachineArpEntryExePackageBundle.Uninstall();
52 + perMachineArpEntryExePackageBundle.VerifyUnregisteredAndRemovedFromPackageCache();
53 + arpEntryExePackage.VerifyRegistered(false);
54 +
55 + Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"testexe.exe\" /regd HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId}"));
56 + }
57 +
58 [RuntimeFact]
59 public void CanRecacheAndReinstallPerMachineArpEntryExePackageOnUninstallRollback()
60 {
src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+10
@@ -436,6 +436,16 @@ namespace WixToolset.Core.Burn.Bundles
436 {
437 writer.WriteAttributeString("ArpWin64", "yes");
438 }
439 +
440 + if (exePackage.ArpUseUninstallString)
441 + {
442 + writer.WriteAttributeString("ArpUseUninstallString", "yes");
443 + }
444 +
445 + if (!String.IsNullOrEmpty(exePackage.UninstallCommand))
446 + {
447 + writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand);
448 + }
449 break;
450 case WixBundleExePackageDetectionType.None:
451 writer.WriteAttributeString("DetectionType", "none");
src/wix/WixToolset.Core/Compiler_Bundle.cs
+25 -26
@@ -1997,10 +1997,11 @@ namespace WixToolset.Core
1997 var bundle = YesNoType.NotSet;
1998 var slipstream = YesNoType.NotSet;
1999 var hasPayloadInfo = false;
2000 - WixBundleExePackageDetectionType? exeDetectionType = WixBundleExePackageDetectionType.None;
2000 + WixBundleExePackageDetectionType exeDetectionType = WixBundleExePackageDetectionType.None;
2001 string arpId = null;
2002 string arpDisplayVersion = null;
2003 var arpWin64 = YesNoType.NotSet;
2004 + var arpUseUninstallString = YesNoType.NotSet;
2005
2006 var expectedNetFx4Args = new string[] { "/q", "/norestart" };
2007
@@ -2117,6 +2118,7 @@ namespace WixToolset.Core
2118 case "DetectCondition":
2119 detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2120 allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu);
2121 + exeDetectionType = WixBundleExePackageDetectionType.Condition;
2122 break;
2123 case "Protocol":
2124 protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
@@ -2183,11 +2185,6 @@ namespace WixToolset.Core
2185 this.Core.ParseExtensionAttribute(node, attribute, contextValues);
2186 }
2187
2186 - if (packageType == WixBundlePackageType.Exe && (detectCondition != null || uninstallArguments != null))
2187 - {
2188 - exeDetectionType = WixBundleExePackageDetectionType.Condition;
2189 - }
2190 -
2188 foreach (var child in node.Elements())
2189 {
2190 if (CompilerCore.WixNamespace == child.Name.Namespace)
@@ -2199,25 +2196,17 @@ namespace WixToolset.Core
2196 allowed = packageType == WixBundlePackageType.Exe;
2197 if (allowed)
2198 {
2202 - if (exeDetectionType == WixBundleExePackageDetectionType.Arp)
2199 + if (exeDetectionType != WixBundleExePackageDetectionType.None)
2200 {
2204 - this.Core.Write(ErrorMessages.TooManyChildren(Preprocessor.GetSourceLineNumbers(child), node.Name.LocalName, child.Name.LocalName));
2201 + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "DetectCondition"));
2202 }
2206 - else if (!exeDetectionType.HasValue || exeDetectionType.Value == WixBundleExePackageDetectionType.Condition)
2203 + if (null != uninstallArguments)
2204 {
2208 - exeDetectionType = null;
2209 - }
2210 - else
2211 - {
2212 - if (exeDetectionType.Value != WixBundleExePackageDetectionType.None)
2213 - {
2214 - throw new WixException($"Unexpected WixBundleExePackageDetectionType: {exeDetectionType}");
2215 - }
2216 -
2217 - exeDetectionType = WixBundleExePackageDetectionType.Arp;
2205 + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "UninstallArguments"));
2206 }
2207
2220 - this.ParseExePackageArpEntryElement(child, out arpId, out arpDisplayVersion, out arpWin64);
2208 + exeDetectionType = WixBundleExePackageDetectionType.Arp;
2209 + this.ParseExePackageArpEntryElement(child, out arpId, out arpDisplayVersion, out arpWin64, out uninstallArguments, out arpUseUninstallString);
2210 }
2211 break;
2212 case "SlipstreamMsp":
@@ -2282,6 +2271,11 @@ namespace WixToolset.Core
2271 }
2272 }
2273
2274 + if (packageType == WixBundlePackageType.Exe && exeDetectionType == WixBundleExePackageDetectionType.None && uninstallArguments != null)
2275 + {
2276 + exeDetectionType = WixBundleExePackageDetectionType.Condition;
2277 + }
2278 +
2279 if (id.Id == BurnConstants.BundleDefaultBoundaryId)
2280 {
2281 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
@@ -2374,10 +2368,6 @@ namespace WixToolset.Core
2368 this.Core.Write(WarningMessages.ExePackageDetectInformationRecommended(sourceLineNumbers));
2369 }
2370 }
2377 - else
2378 - {
2379 - this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "ArpEntry", String.IsNullOrEmpty(detectCondition) ? "UninstallArguments" : "DetectCondition"));
2380 - }
2371
2372 if (repairArguments == null && repairCondition != null)
2373 {
@@ -2497,6 +2487,7 @@ namespace WixToolset.Core
2487 WixBundleExePackageAttributes exeAttributes = 0;
2488 exeAttributes |= (YesNoType.Yes == bundle) ? WixBundleExePackageAttributes.Bundle : 0;
2489 exeAttributes |= (YesNoType.Yes == arpWin64) ? WixBundleExePackageAttributes.ArpWin64 : 0;
2490 + exeAttributes |= (YesNoType.Yes == arpUseUninstallString) ? WixBundleExePackageAttributes.ArpUseUninstallString : 0;
2491
2492 this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id)
2493 {
@@ -2506,7 +2497,7 @@ namespace WixToolset.Core
2497 RepairCommand = repairArguments,
2498 UninstallCommand = uninstallArguments,
2499 ExeProtocol = protocol,
2509 - DetectionType = exeDetectionType.Value,
2500 + DetectionType = exeDetectionType,
2501 ArpId = arpId,
2502 ArpDisplayVersion = arpDisplayVersion,
2503 });
@@ -2971,12 +2962,14 @@ namespace WixToolset.Core
2962 }
2963 }
2964
2974 - private void ParseExePackageArpEntryElement(XElement node, out string id, out string version, out YesNoType win64)
2965 + private void ParseExePackageArpEntryElement(XElement node, out string id, out string version, out YesNoType win64, out string uninstallArguments, out YesNoType arpUseUninstallString)
2966 {
2967 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2968 id = null;
2969 version = null;
2970 win64 = YesNoType.NotSet;
2971 + arpUseUninstallString = YesNoType.NotSet;
2972 + uninstallArguments = null;
2973
2974 foreach (var attrib in node.Attributes())
2975 {
@@ -2990,6 +2983,12 @@ namespace WixToolset.Core
2983 case "Version":
2984 version = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2985 break;
2986 + case "AdditionalUninstallArguments":
2987 + uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2988 + break;
2989 + case "UseUninstallString":
2990 + arpUseUninstallString = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2991 + break;
2992 case "Win64":
2993 win64 = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2994 break;