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 "$(var.ArpKeyPath),DisplayVersion,String,$(var.ArpVersion)" /regw "$(var.ArpKeyPath),UninstallString,String,\"[WixBundleExecutePackageCacheFolder]testexe.exe\" /regd \"$(var.ArpKeyPath)\""">
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;