Skip to content

Commit

Permalink
ArpEntry reads QuietUninstallString or UninstallString, and uses Unin…
Browse files Browse the repository at this point in the history
…stallArguments for the uninstall command line
  • Loading branch information
nirbar committed Dec 17, 2023
1 parent 34132d8 commit 5dd0d77
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 29 deletions.
17 changes: 17 additions & 0 deletions src/api/wix/WixToolset.Data/Symbols/WixBundleExePackageSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum WixBundleExePackageAttributes
None = 0,
Bundle = 1,
ArpWin64 = 2,
ArpUseUninstallString = 4,
}

public class WixBundleExePackageSymbol : IntermediateSymbol
Expand Down Expand Up @@ -157,6 +158,22 @@ public bool ArpWin64
}
}

public bool ArpUseUninstallString
{
get { return this.Attributes.HasFlag(WixBundleExePackageAttributes.ArpUseUninstallString); }
set
{
if (value)
{
this.Attributes |= WixBundleExePackageAttributes.ArpUseUninstallString;
}
else
{
this.Attributes &= ~WixBundleExePackageAttributes.ArpUseUninstallString;
}
}
}

public bool Repairable => this.RepairCommand != null;

public bool Uninstallable => this.UninstallCommand != null;
Expand Down
26 changes: 23 additions & 3 deletions src/burn/engine/exeengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ extern "C" HRESULT ExeEngineParsePackageFromXml(
hr = XmlGetYesNoAttribute(pixnExePackage, L"ArpWin64", &pPackage->Exe.fArpWin64);
ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @ArpWin64.");

// @ArpUseUninstallString
hr = XmlGetYesNoAttribute(pixnExePackage, L"ArpUseUninstallString", &pPackage->Exe.fArpUseUninstallString);
ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @ArpWin64.");

// @UninstallArguments
hr = XmlGetAttributeEx(pixnExePackage, L"UninstallArguments", &pPackage->Exe.sczUninstallArguments);
ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @UninstallArguments.");

pPackage->Exe.fUninstallable = TRUE;
}

Expand Down Expand Up @@ -481,7 +489,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
}
else if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action)
{
ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null.");
ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "%hs is null.", pPackage->Exe.fArpUseUninstallString ? "UninstallString" : "QuietUninstallString");

hr = AppParseCommandLine(sczArpUninstallString, &argcArp, &argvArp);
ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString);
Expand Down Expand Up @@ -1122,8 +1130,20 @@ static HRESULT DetectArpEntry(

if (psczQuietUninstallString)
{
hr = RegReadString(hKey, L"QuietUninstallString", psczQuietUninstallString);
ExitOnPathFailure(hr, fExists, "Failed to read QuietUninstallString.");
LPCWSTR sczUninstallStringName = pPackage->Exe.fArpUseUninstallString ? L"UninstallString" : L"QuietUninstallString";

hr = RegReadString(hKey, sczUninstallStringName, psczQuietUninstallString);
ExitOnPathFailure(hr, fExists, "Failed to read %ls.", sczUninstallStringName);

// If the uninstall string is an executable path then ensure it is enclosed in quotes
if (fExists && *psczQuietUninstallString && (L'\"' != **psczQuietUninstallString) && FileExistsEx(*psczQuietUninstallString, nullptr))
{
hr = StrAllocPrefix(psczQuietUninstallString, L"\"", 0);
ExitOnFailure(hr, "Failed to prepend UninstallString with quote.");

hr = StrAllocConcat(psczQuietUninstallString, L"\"", 0);
ExitOnFailure(hr, "Failed to append quote to UninstallString.");
}
}

LExit:
Expand Down
1 change: 1 addition & 0 deletions src/burn/engine/package.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ typedef struct _BURN_PACKAGE
BURN_EXE_DETECTION_TYPE detectionType;

BOOL fArpWin64;
BOOL fArpUseUninstallString;
LPWSTR sczArpKeyPath;
VERUTIL_VERSION* pArpDisplayVersion;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- 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. -->
<Project Sdk="WixToolset.Sdk">
<PropertyGroup>
<OutputType>Bundle</OutputType>
<InstallerPlatform>x64</InstallerPlatform>
<BA>TestBA_x64</BA>
<UpgradeCode>{0A3D66F0-21A8-41B5-BE9A-7A9ABDEC3AB5}</UpgradeCode>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TestBA\TestBAWixlib_x64\testbawixlib_x64.wixproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WixToolset.Bal.wixext" />
<PackageReference Include="WixToolset.NetFx.wixext" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- 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. -->

<?define ArpId = {21E99C0F-E604-439C-97E0-33B9771394BC}?>
<?define ArpKeyPath = HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(var.ArpId)?>
<?define ArpVersion = 1.0.0.0?>

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<PackageGroup Id="BundlePackages">
<ExePackage Id="TestExe" PerMachine="yes"
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;">
<ArpEntry Id="$(var.ArpId)" Version="$(var.ArpVersion)" Win64="yes" UseUninstallString="yes" />

<PayloadGroupRef Id="TestExePayloads_x64" />
</ExePackage>
</PackageGroup>
</Fragment>
</Wix>
22 changes: 22 additions & 0 deletions src/test/burn/WixToolsetTest.BurnE2E/ExePackageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ public void CanInstallAndUninstallPerMachineArpEntryExePackage()
Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"testexe.exe\" /regd HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId}"));
}

[RuntimeFact]
public void CanInstallAndUninstallPerMachineArpEntryWithUninstallStringExePackage()
{
var perMachineArpEntryExePackageBundle = this.CreateBundleInstaller(@"PerMachineArpEntryWithUninstallStringExePackage");
var arpEntryExePackage = this.CreateArpEntryInstaller(perMachineArpEntryExePackageBundle, "TestExe");
var arpId = arpEntryExePackage.ArpId;

arpEntryExePackage.VerifyRegistered(false);

var installLogPath = perMachineArpEntryExePackageBundle.Install();
perMachineArpEntryExePackageBundle.VerifyRegisteredAndInPackageCache();
arpEntryExePackage.VerifyRegistered(true);

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,\\\""));

var uninstallLogPath = perMachineArpEntryExePackageBundle.Uninstall();
perMachineArpEntryExePackageBundle.VerifyUnregisteredAndRemovedFromPackageCache();
arpEntryExePackage.VerifyRegistered(false);

Assert.True(LogVerifier.MessageInLogFile(uninstallLogPath, $"testexe.exe\" /regd HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{arpId}"));
}

[RuntimeFact]
public void CanRecacheAndReinstallPerMachineArpEntryExePackageOnUninstallRollback()
{
Expand Down
10 changes: 10 additions & 0 deletions src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,16 @@ public void Execute()
{
writer.WriteAttributeString("ArpWin64", "yes");
}

if (exePackage.ArpUseUninstallString)
{
writer.WriteAttributeString("ArpUseUninstallString", "yes");
}

if (!String.IsNullOrEmpty(exePackage.UninstallCommand))
{
writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand);
}
break;
case WixBundleExePackageDetectionType.None:
writer.WriteAttributeString("DetectionType", "none");
Expand Down
51 changes: 25 additions & 26 deletions src/wix/WixToolset.Core/Compiler_Bundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1997,10 +1997,11 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
var bundle = YesNoType.NotSet;
var slipstream = YesNoType.NotSet;
var hasPayloadInfo = false;
WixBundleExePackageDetectionType? exeDetectionType = WixBundleExePackageDetectionType.None;
WixBundleExePackageDetectionType exeDetectionType = WixBundleExePackageDetectionType.None;
string arpId = null;
string arpDisplayVersion = null;
var arpWin64 = YesNoType.NotSet;
var arpUseUninstallString = YesNoType.NotSet;

var expectedNetFx4Args = new string[] { "/q", "/norestart" };

Expand Down Expand Up @@ -2117,6 +2118,7 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
case "DetectCondition":
detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu);
exeDetectionType = WixBundleExePackageDetectionType.Condition;
break;
case "Protocol":
protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
Expand Down Expand Up @@ -2183,11 +2185,6 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
this.Core.ParseExtensionAttribute(node, attribute, contextValues);
}

if (packageType == WixBundlePackageType.Exe && (detectCondition != null || uninstallArguments != null))
{
exeDetectionType = WixBundleExePackageDetectionType.Condition;
}

foreach (var child in node.Elements())
{
if (CompilerCore.WixNamespace == child.Name.Namespace)
Expand All @@ -2199,25 +2196,17 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
allowed = packageType == WixBundlePackageType.Exe;
if (allowed)
{
if (exeDetectionType == WixBundleExePackageDetectionType.Arp)
if (exeDetectionType != WixBundleExePackageDetectionType.None)
{
this.Core.Write(ErrorMessages.TooManyChildren(Preprocessor.GetSourceLineNumbers(child), node.Name.LocalName, child.Name.LocalName));
this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "DetectCondition"));
}
else if (!exeDetectionType.HasValue || exeDetectionType.Value == WixBundleExePackageDetectionType.Condition)
if (null != uninstallArguments)
{
exeDetectionType = null;
}
else
{
if (exeDetectionType.Value != WixBundleExePackageDetectionType.None)
{
throw new WixException($"Unexpected WixBundleExePackageDetectionType: {exeDetectionType}");
}

exeDetectionType = WixBundleExePackageDetectionType.Arp;
this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "UninstallArguments"));
}

this.ParseExePackageArpEntryElement(child, out arpId, out arpDisplayVersion, out arpWin64);
exeDetectionType = WixBundleExePackageDetectionType.Arp;
this.ParseExePackageArpEntryElement(child, out arpId, out arpDisplayVersion, out arpWin64, out uninstallArguments, out arpUseUninstallString);
}
break;
case "SlipstreamMsp":
Expand Down Expand Up @@ -2282,6 +2271,11 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
}
}

if (packageType == WixBundlePackageType.Exe && exeDetectionType == WixBundleExePackageDetectionType.None && uninstallArguments != null)
{
exeDetectionType = WixBundleExePackageDetectionType.Condition;
}

if (id.Id == BurnConstants.BundleDefaultBoundaryId)
{
this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
Expand Down Expand Up @@ -2374,10 +2368,6 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
this.Core.Write(WarningMessages.ExePackageDetectInformationRecommended(sourceLineNumbers));
}
}
else
{
this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "ArpEntry", String.IsNullOrEmpty(detectCondition) ? "UninstallArguments" : "DetectCondition"));
}

if (repairArguments == null && repairCondition != null)
{
Expand Down Expand Up @@ -2497,6 +2487,7 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
WixBundleExePackageAttributes exeAttributes = 0;
exeAttributes |= (YesNoType.Yes == bundle) ? WixBundleExePackageAttributes.Bundle : 0;
exeAttributes |= (YesNoType.Yes == arpWin64) ? WixBundleExePackageAttributes.ArpWin64 : 0;
exeAttributes |= (YesNoType.Yes == arpUseUninstallString) ? WixBundleExePackageAttributes.ArpUseUninstallString : 0;

this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id)
{
Expand All @@ -2506,7 +2497,7 @@ private string ParseChainPackage(XElement node, WixBundlePackageType packageType
RepairCommand = repairArguments,
UninstallCommand = uninstallArguments,
ExeProtocol = protocol,
DetectionType = exeDetectionType.Value,
DetectionType = exeDetectionType,
ArpId = arpId,
ArpDisplayVersion = arpDisplayVersion,
});
Expand Down Expand Up @@ -2971,12 +2962,14 @@ private void ParseRemoteRelatedBundleElement(XElement node, string payloadId)
}
}

private void ParseExePackageArpEntryElement(XElement node, out string id, out string version, out YesNoType win64)
private void ParseExePackageArpEntryElement(XElement node, out string id, out string version, out YesNoType win64, out string uninstallArguments, out YesNoType arpUseUninstallString)
{
var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
id = null;
version = null;
win64 = YesNoType.NotSet;
arpUseUninstallString = YesNoType.NotSet;
uninstallArguments = null;

foreach (var attrib in node.Attributes())
{
Expand All @@ -2990,6 +2983,12 @@ private void ParseExePackageArpEntryElement(XElement node, out string id, out st
case "Version":
version = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "AdditionalUninstallArguments":
uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "UseUninstallString":
arpUseUninstallString = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
break;
case "Win64":
win64 = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
break;
Expand Down

0 comments on commit 5dd0d77

Please sign in to comment.