From 39b9a6112c2ff97f31f195749e2142538e47a2eb Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 1 Apr 2022 15:44:34 -0500 Subject: Detect related bundles for BundlePackages. --- .../inc/BootstrapperApplication.h | 17 ++ .../WixToolset.Mba.Core/BootstrapperApplication.cs | 25 ++ src/api/burn/WixToolset.Mba.Core/EventArgs.cs | 152 +++++------ .../IBootstrapperApplication.cs | 14 ++ .../IDefaultBootstrapperApplication.cs | 13 +- src/api/burn/WixToolset.Mba.Core/PackageInfo.cs | 17 +- src/api/burn/balutil/balinfo.cpp | 6 +- src/api/burn/balutil/inc/BAFunctions.h | 1 + src/api/burn/balutil/inc/BalBaseBAFunctions.h | 12 + src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h | 1 + .../balutil/inc/BalBaseBootstrapperApplication.h | 13 + .../inc/BalBaseBootstrapperApplicationProc.h | 12 + .../burn/balutil/inc/IBootstrapperApplication.h | 10 + src/api/burn/balutil/inc/balinfo.h | 1 + .../WixToolset.Data/Symbols/SymbolDefinitions.cs | 4 + .../Symbols/WixBundleBundlePackageSymbol.cs | 8 + .../Symbols/WixBundlePackageRelatedBundleSymbol.cs | 60 +++++ src/burn/engine/bundlepackageengine.cpp | 277 +++++++++++++++++++-- src/burn/engine/bundlepackageengine.h | 15 +- src/burn/engine/core.cpp | 2 +- src/burn/engine/package.h | 19 +- src/burn/engine/registration.cpp | 87 +------ src/burn/engine/relatedbundle.cpp | 50 ++-- src/burn/engine/relatedbundle.h | 3 + src/burn/engine/userexperience.cpp | 36 ++- src/burn/engine/userexperience.h | 8 + .../PlanTest/BundlePackage_Multiple_manifest.xml | 2 +- .../WixStandardBootstrapperApplication.cpp | 11 + .../UpgradeBundlePackageBundle.props | 14 ++ .../UpgradeBundlePackageBundlev1.wixproj | 7 + .../UpgradeBundlePackageBundlev1.wxs | 10 + .../UpgradeBundlePackageBundlev2.wixproj | 7 + .../UpgradeBundlePackageBundlev2.wxs | 10 + .../WixToolsetTest.BurnE2E/BundlePackageTests.cs | 30 +++ .../Bind/GenerateManifestDataFromIRCommand.cs | 1 + .../Bundles/CreateBurnManifestCommand.cs | 12 + .../Bundles/ProcessBundlePackageCommand.cs | 25 ++ .../BundlePackageFixture.cs | 19 +- 38 files changed, 773 insertions(+), 238 deletions(-) create mode 100644 src/api/wix/WixToolset.Data/Symbols/WixBundlePackageRelatedBundleSymbol.cs create mode 100644 src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundle.props create mode 100644 src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wixproj create mode 100644 src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wxs create mode 100644 src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wixproj create mode 100644 src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wxs (limited to 'src') diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h index 943f5ead..fbbd10ee 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h @@ -226,6 +226,7 @@ enum BOOTSTRAPPER_APPLICATION_MESSAGE BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, + BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, }; enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION @@ -793,6 +794,22 @@ struct BA_ONDETECTRELATEDBUNDLE_RESULTS BOOL fCancel; }; +struct BA_ONDETECTRELATEDBUNDLEPACKAGE_ARGS +{ + DWORD cbSize; + LPCWSTR wzPackageId; + LPCWSTR wzBundleId; + BOOTSTRAPPER_RELATION_TYPE relationType; + BOOL fPerMachine; + LPCWSTR wzVersion; +}; + +struct BA_ONDETECTRELATEDBUNDLEPACKAGE_RESULTS +{ + DWORD cbSize; + BOOL fCancel; +}; + struct BA_ONDETECTRELATEDMSIPACKAGE_ARGS { DWORD cbSize; diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs index 5ed064fa..41738bf6 100644 --- a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs @@ -283,6 +283,9 @@ namespace WixToolset.Mba.Core /// public event EventHandler ExecuteProcessCancel; + /// + public event EventHandler DetectRelatedBundlePackage; + /// /// Entry point that is called when the bootstrapper application is ready to run. /// @@ -1385,6 +1388,19 @@ namespace WixToolset.Mba.Core } } + /// + /// Called by the engine, raises the event. + /// + /// Additional arguments for this event. + protected virtual void OnDetectRelatedBundlePackage(DetectRelatedBundlePackageEventArgs args) + { + EventHandler handler = this.DetectRelatedBundlePackage; + if (null != handler) + { + handler(this, args); + } + } + #region IBootstrapperApplication Members int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext) @@ -2144,6 +2160,15 @@ namespace WixToolset.Mba.Core return args.HResult; } + int IBootstrapperApplication.OnDetectRelatedBundlePackage(string wzPackageId, string wzProductCode, RelationType relationType, bool fPerMachine, string wzVersion, ref bool fCancel) + { + DetectRelatedBundlePackageEventArgs args = new DetectRelatedBundlePackageEventArgs(wzPackageId, wzProductCode, relationType, fPerMachine, wzVersion, fCancel); + this.OnDetectRelatedBundlePackage(args); + + fCancel = args.Cancel; + return args.HResult; + } + #endregion } } diff --git a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs index c2c73067..07b14b3d 100644 --- a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs +++ b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs @@ -2030,10 +2030,7 @@ namespace WixToolset.Mba.Core [Serializable] public class LaunchApprovedExeBeginEventArgs : CancellableHResultEventArgs { - /// - /// - /// - /// + /// public LaunchApprovedExeBeginEventArgs(bool cancelRecommendation) : base(cancelRecommendation) { @@ -2046,27 +2043,18 @@ namespace WixToolset.Mba.Core [Serializable] public class LaunchApprovedExeCompleteEventArgs : StatusEventArgs { - private int processId; - - /// - /// - /// - /// - /// + /// public LaunchApprovedExeCompleteEventArgs(int hrStatus, int processId) : base(hrStatus) { - this.processId = processId; + this.ProcessId = processId; } /// /// Gets the ProcessId of the process that was launched. /// This is only valid if the status reports success. /// - public int ProcessId - { - get { return this.processId; } - } + public int ProcessId { get; private set; } } /// @@ -2075,26 +2063,17 @@ namespace WixToolset.Mba.Core [Serializable] public class BeginMsiTransactionBeginEventArgs : CancellableHResultEventArgs { - private string transactionId; - - /// - /// - /// - /// - /// + /// public BeginMsiTransactionBeginEventArgs(string transactionId, bool cancelRecommendation) : base(cancelRecommendation) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2103,26 +2082,17 @@ namespace WixToolset.Mba.Core [Serializable] public class BeginMsiTransactionCompleteEventArgs : StatusEventArgs { - private string transactionId; - - /// - /// - /// - /// - /// + /// public BeginMsiTransactionCompleteEventArgs(string transactionId, int hrStatus) : base(hrStatus) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2131,26 +2101,17 @@ namespace WixToolset.Mba.Core [Serializable] public class CommitMsiTransactionBeginEventArgs : CancellableHResultEventArgs { - private string transactionId; - - /// - /// - /// - /// - /// + /// public CommitMsiTransactionBeginEventArgs(string transactionId, bool cancelRecommendation) : base(cancelRecommendation) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2159,26 +2120,17 @@ namespace WixToolset.Mba.Core [Serializable] public class CommitMsiTransactionCompleteEventArgs : StatusEventArgs { - private string transactionId; - - /// - /// - /// - /// - /// + /// public CommitMsiTransactionCompleteEventArgs(string transactionId, int hrStatus) : base(hrStatus) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2187,24 +2139,16 @@ namespace WixToolset.Mba.Core [Serializable] public class RollbackMsiTransactionBeginEventArgs : HResultEventArgs { - private string transactionId; - - /// - /// - /// - /// + /// public RollbackMsiTransactionBeginEventArgs(string transactionId) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2213,26 +2157,17 @@ namespace WixToolset.Mba.Core [Serializable] public class RollbackMsiTransactionCompleteEventArgs : StatusEventArgs { - private string transactionId; - - /// - /// - /// - /// - /// + /// public RollbackMsiTransactionCompleteEventArgs(string transactionId, int hrStatus) : base(hrStatus) { - this.transactionId = transactionId; + this.TransactionId = transactionId; } /// /// Gets the MSI transaction Id. /// - public string TransactionId - { - get { return this.transactionId; } - } + public string TransactionId { get; private set; } } /// @@ -2524,4 +2459,47 @@ namespace WixToolset.Mba.Core /// public BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION Action { get; set; } } + + /// + /// Event arguments for + /// + [Serializable] + public class DetectRelatedBundlePackageEventArgs : CancellableHResultEventArgs + { + /// + public DetectRelatedBundlePackageEventArgs(string packageId, string productCode, RelationType relationType, bool perMachine, string version, bool cancelRecommendation) + : base(cancelRecommendation) + { + this.PackageId = packageId; + this.ProductCode = productCode; + this.RelationType = relationType; + this.PerMachine = perMachine; + this.Version = version; + } + + /// + /// Gets the identity of the product's package detected. + /// + public string PackageId { get; private set; } + + /// + /// Gets the identity of the related bundle detected. + /// + public string ProductCode { get; private set; } + + /// + /// Gets the relationship type of the related bundle. + /// + public RelationType RelationType { get; private set; } + + /// + /// Gets whether the detected bundle is per machine. + /// + public bool PerMachine { get; private set; } + + /// + /// Gets the version of the related bundle detected. + /// + public string Version { get; private set; } + } } diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs index 7cf0957a..ae642474 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs @@ -1182,6 +1182,20 @@ namespace WixToolset.Mba.Core [MarshalAs(UnmanagedType.I4)] BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, [MarshalAs(UnmanagedType.I4)] ref BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION pAction ); + + /// + /// See . + /// + [PreserveSig] + [return: MarshalAs(UnmanagedType.I4)] + int OnDetectRelatedBundlePackage( + [MarshalAs(UnmanagedType.LPWStr)] string wzPackageId, + [MarshalAs(UnmanagedType.LPWStr)] string wzBundleId, + [MarshalAs(UnmanagedType.U4)] RelationType relationType, + [MarshalAs(UnmanagedType.Bool)] bool fPerMachine, + [MarshalAs(UnmanagedType.LPWStr)] string wzVersion, + [MarshalAs(UnmanagedType.Bool)] ref bool fCancel + ); } /// diff --git a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs index 21d99b32..77089e83 100644 --- a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs @@ -36,15 +36,15 @@ namespace WixToolset.Mba.Core /// /// Fired when the engine has begun acquiring the payload or container. - /// The BA can change the source using - /// or . + /// The BA can change the source using + /// or . /// event EventHandler CacheAcquireBegin; /// /// Fired when the engine has completed the acquisition of the payload or container. - /// The BA can change the source using - /// or . + /// The BA can change the source using + /// or . /// event EventHandler CacheAcquireComplete; @@ -178,6 +178,11 @@ namespace WixToolset.Mba.Core /// event EventHandler DetectRelatedBundle; + /// + /// Fired when a related bundle has been detected for a bundle package. + /// + event EventHandler DetectRelatedBundlePackage; + /// /// Fired when a related MSI package has been detected for a package. /// diff --git a/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs b/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs index 3681a497..39fe4d73 100644 --- a/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs +++ b/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs @@ -56,6 +56,11 @@ namespace WixToolset.Mba.Core /// /// UpdateBundle, + + /// + /// + /// + ChainBundle, } /// @@ -220,7 +225,11 @@ namespace WixToolset.Mba.Core return null; } - if (attributeValue.Equals("Exe", StringComparison.InvariantCulture)) + if (attributeValue.Equals("Bundle", StringComparison.InvariantCulture)) + { + return PackageType.ChainBundle; + } + else if (attributeValue.Equals("Exe", StringComparison.InvariantCulture)) { return PackageType.Exe; } @@ -268,7 +277,7 @@ namespace WixToolset.Mba.Core package.Type = PackageType.UpgradeBundle; break; default: - throw new Exception(string.Format("Unknown related bundle type: {0}", relationType)); + throw new Exception(String.Format("Unknown related bundle type: {0}", relationType)); } return package; @@ -302,7 +311,7 @@ namespace WixToolset.Mba.Core if (!packagesById.TryGetValue(id, out var ipackage)) { - throw new Exception(string.Format("Failed to find package specified in WixBalPackageInfo: {0}", id)); + throw new Exception(String.Format("Failed to find package specified in WixBalPackageInfo: {0}", id)); } var package = (PackageInfo)ipackage; @@ -322,7 +331,7 @@ namespace WixToolset.Mba.Core if (!packagesById.TryGetValue(id, out var ipackage)) { - throw new Exception(string.Format("Failed to find package specified in WixMbaPrereqInformation: {0}", id)); + throw new Exception(String.Format("Failed to find package specified in WixMbaPrereqInformation: {0}", id)); } var package = (PackageInfo)ipackage; diff --git a/src/api/burn/balutil/balinfo.cpp b/src/api/burn/balutil/balinfo.cpp index d9cc9b76..f0eb9904 100644 --- a/src/api/burn/balutil/balinfo.cpp +++ b/src/api/burn/balutil/balinfo.cpp @@ -433,7 +433,11 @@ static HRESULT ParsePackagesFromXml( hr = XmlGetAttributeEx(pNode, L"PackageType", &scz); ExitOnFailure(hr, "Failed to get package type for package."); - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Exe", -1, scz, -1)) + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Bundle", -1, scz, -1)) + { + prgPackages[iPackage].type = BAL_INFO_PACKAGE_TYPE_BUNDLE_CHAIN; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Exe", -1, scz, -1)) { prgPackages[iPackage].type = BAL_INFO_PACKAGE_TYPE_EXE; } diff --git a/src/api/burn/balutil/inc/BAFunctions.h b/src/api/burn/balutil/inc/BAFunctions.h index 158e65b5..9be3f62f 100644 --- a/src/api/burn/balutil/inc/BAFunctions.h +++ b/src/api/burn/balutil/inc/BAFunctions.h @@ -92,6 +92,7 @@ enum BA_FUNCTIONS_MESSAGE BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL = BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, + BA_FUNCTIONS_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, BA_FUNCTIONS_MESSAGE_ONTHEMELOADED = 1024, BA_FUNCTIONS_MESSAGE_WNDPROC, diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctions.h b/src/api/burn/balutil/inc/BalBaseBAFunctions.h index 614d4bcf..6bde05d2 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctions.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctions.h @@ -887,6 +887,18 @@ public: // IBootstrapperApplication return S_OK; } + virtual STDMETHODIMP OnDetectRelatedBundlePackage( + __in_z LPCWSTR /*wzPackageId*/, + __in_z LPCWSTR /*wzBundleId*/, + __in BOOTSTRAPPER_RELATION_TYPE /*relationType*/, + __in BOOL /*fPerMachine*/, + __in LPCWSTR /*wzVersion*/, + __inout BOOL* /*pfCancel*/ + ) + { + return S_OK; + } + public: // IBAFunctions virtual STDMETHODIMP OnPlan( ) diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h index b96a180c..4564ad0c 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h @@ -163,6 +163,7 @@ static HRESULT WINAPI BalBaseBAFunctionsProc( case BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE: case BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE: case BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL: + case BA_FUNCTIONS_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE: hr = BalBaseBootstrapperApplicationProc((BOOTSTRAPPER_APPLICATION_MESSAGE)message, pvArgs, pvResults, pvContext); break; case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED: diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h index 25570ffd..b661c7c9 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h @@ -1087,6 +1087,19 @@ public: // IBootstrapperApplication return S_OK; } + virtual STDMETHODIMP OnDetectRelatedBundlePackage( + __in_z LPCWSTR /*wzPackageId*/, + __in_z LPCWSTR /*wzBundleId*/, + __in BOOTSTRAPPER_RELATION_TYPE /*relationType*/, + __in BOOL /*fPerMachine*/, + __in LPCWSTR /*wzVersion*/, + __inout BOOL* pfCancel + ) + { + *pfCancel |= CheckCanceled(); + return S_OK; + } + public: //CBalBaseBootstrapperApplication virtual STDMETHODIMP Initialize( __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h index b196d183..4ef7bac5 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h @@ -756,6 +756,15 @@ static HRESULT BalBaseBAProcOnApplyDowngrade( return pBA->OnApplyDowngrade(pArgs->hrRecommended, &pResults->hrStatus); } +static HRESULT BalBaseBAProcOnDetectRelatedBundlePackage( + __in IBootstrapperApplication* pBA, + __in BA_ONDETECTRELATEDBUNDLEPACKAGE_ARGS* pArgs, + __inout BA_ONDETECTRELATEDBUNDLEPACKAGE_RESULTS* pResults + ) +{ + return pBA->OnDetectRelatedBundlePackage(pArgs->wzPackageId, pArgs->wzBundleId, pArgs->relationType, pArgs->fPerMachine, pArgs->wzVersion, &pResults->fCancel); +} + /******************************************************************* BalBaseBootstrapperApplicationProc - requires pvContext to be of type IBootstrapperApplication. Provides a default mapping between the new message based BA interface and @@ -1024,6 +1033,9 @@ static HRESULT WINAPI BalBaseBootstrapperApplicationProc( case BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL: hr = BalBaseBAProcOnExecuteProcessCancel(pBA, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE: + hr = BalBaseBAProcOnDetectRelatedBundlePackage(pBA, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; } } diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h index 6174c290..a4840228 100644 --- a/src/api/burn/balutil/inc/IBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h @@ -723,4 +723,14 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction ) = 0; + + // OnDetectRelatedBundlePackage - called when the engine detects a related bundle for a BundlePackage. + STDMETHOD(OnDetectRelatedBundlePackage)( + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOL fPerMachine, + __in_z LPCWSTR wzVersion, + __inout BOOL* pfCancel + ) = 0; }; diff --git a/src/api/burn/balutil/inc/balinfo.h b/src/api/burn/balutil/inc/balinfo.h index 8f61685f..0c7a5b93 100644 --- a/src/api/burn/balutil/inc/balinfo.h +++ b/src/api/burn/balutil/inc/balinfo.h @@ -17,6 +17,7 @@ typedef enum BAL_INFO_PACKAGE_TYPE BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON, BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH, BAL_INFO_PACKAGE_TYPE_BUNDLE_UPDATE, + BAL_INFO_PACKAGE_TYPE_BUNDLE_CHAIN, } BAL_INFO_PACKAGE_TYPE; typedef enum _BAL_INFO_RESTART diff --git a/src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs b/src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs index d4a91343..1fd8ded1 100644 --- a/src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs +++ b/src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs @@ -142,6 +142,7 @@ namespace WixToolset.Data WixBundlePackageCommandLine, WixBundlePackageExitCode, WixBundlePackageGroup, + WixBundlePackageRelatedBundle, WixBundlePatchTargetCode, WixBundlePayload, WixBundlePayloadGroup, @@ -618,6 +619,9 @@ namespace WixToolset.Data case SymbolDefinitionType.WixBundlePackageGroup: return SymbolDefinitions.WixBundlePackageGroup; + case SymbolDefinitionType.WixBundlePackageRelatedBundle: + return SymbolDefinitions.WixBundlePackageRelatedBundle; + case SymbolDefinitionType.WixBundlePatchTargetCode: return SymbolDefinitions.WixBundlePatchTargetCode; diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundleBundlePackageSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundleBundlePackageSymbol.cs index 36b9eb67..dcf59e28 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixBundleBundlePackageSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixBundleBundlePackageSymbol.cs @@ -12,6 +12,7 @@ namespace WixToolset.Data { new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.Attributes), IntermediateFieldType.Number), new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.BundleId), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.Version), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.InstallCommand), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.RepairCommand), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleBundlePackageSymbolFields.UninstallCommand), IntermediateFieldType.String), @@ -28,6 +29,7 @@ namespace WixToolset.Data.Symbols { Attributes, BundleId, + Version, InstallCommand, RepairCommand, UninstallCommand, @@ -65,6 +67,12 @@ namespace WixToolset.Data.Symbols set => this.Set((int)WixBundleBundlePackageSymbolFields.BundleId, value); } + public string Version + { + get => (string)this.Fields[(int)WixBundleBundlePackageSymbolFields.Version]; + set => this.Set((int)WixBundleBundlePackageSymbolFields.Version, value); + } + public string InstallCommand { get => (string)this.Fields[(int)WixBundleBundlePackageSymbolFields.InstallCommand]; diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundlePackageRelatedBundleSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundlePackageRelatedBundleSymbol.cs new file mode 100644 index 00000000..dfb48714 --- /dev/null +++ b/src/api/wix/WixToolset.Data/Symbols/WixBundlePackageRelatedBundleSymbol.cs @@ -0,0 +1,60 @@ +// 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. + +namespace WixToolset.Data +{ + using WixToolset.Data.Symbols; + + public static partial class SymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixBundlePackageRelatedBundle = new IntermediateSymbolDefinition( + SymbolDefinitionType.WixBundlePackageRelatedBundle, + new[] + { + new IntermediateFieldDefinition(nameof(WixBundlePackageRelatedBundleSymbolFields.PackageRef), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundlePackageRelatedBundleSymbolFields.BundleId), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundlePackageRelatedBundleSymbolFields.Action), IntermediateFieldType.Number), + }, + typeof(WixBundlePackageRelatedBundleSymbol)); + } +} + +namespace WixToolset.Data.Symbols +{ + public enum WixBundlePackageRelatedBundleSymbolFields + { + PackageRef, + BundleId, + Action, + } + + public class WixBundlePackageRelatedBundleSymbol : IntermediateSymbol + { + public WixBundlePackageRelatedBundleSymbol() : base(SymbolDefinitions.WixBundlePackageRelatedBundle, null, null) + { + } + + public WixBundlePackageRelatedBundleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(SymbolDefinitions.WixBundlePackageRelatedBundle, sourceLineNumber, id) + { + } + + public IntermediateField this[WixBundlePackageRelatedBundleSymbolFields index] => this.Fields[(int)index]; + + public string PackageRef + { + get => (string)this.Fields[(int)WixBundlePackageRelatedBundleSymbolFields.PackageRef]; + set => this.Set((int)WixBundlePackageRelatedBundleSymbolFields.PackageRef, value); + } + + public string BundleId + { + get => (string)this.Fields[(int)WixBundlePackageRelatedBundleSymbolFields.BundleId]; + set => this.Set((int)WixBundlePackageRelatedBundleSymbolFields.BundleId, value); + } + + public RelatedBundleActionType Action + { + get => (RelatedBundleActionType)this.Fields[(int)WixBundlePackageRelatedBundleSymbolFields.Action].AsNumber(); + set => this.Set((int)WixBundlePackageRelatedBundleSymbolFields.Action, (int)value); + } + } +} diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp index f3badfc1..ef08d417 100644 --- a/src/burn/engine/bundlepackageengine.cpp +++ b/src/burn/engine/bundlepackageengine.cpp @@ -2,6 +2,18 @@ #include "precomp.h" +typedef struct _BUNDLE_QUERY_CONTEXT +{ + BURN_PACKAGE* pPackage; + BURN_USER_EXPERIENCE* pUserExperience; + BOOL fSelfFound; + BOOL fNewerFound; +} BUNDLE_QUERY_CONTEXT; + +static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback( + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, + __in_opt LPVOID pvContext + ); static HRESULT ExecuteBundle( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, @@ -35,6 +47,18 @@ extern "C" HRESULT BundlePackageEngineParsePackageFromXml( hr = XmlGetAttributeEx(pixnBundlePackage, L"BundleId", &pPackage->Bundle.sczBundleId); ExitOnRequiredXmlQueryFailure(hr, "Failed to get @BundleId."); + // @Version + hr = XmlGetAttributeEx(pixnBundlePackage, L"Version", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @Version."); + + hr = VerParseVersion(scz, 0, FALSE, &pPackage->Bundle.pVersion); + ExitOnFailure(hr, "Failed to parse @Version: %ls", scz); + + if (pPackage->Bundle.pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + // @InstallArguments hr = XmlGetAttributeEx(pixnBundlePackage, L"InstallArguments", &pPackage->Bundle.sczInstallArguments); ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @InstallArguments."); @@ -55,6 +79,9 @@ extern "C" HRESULT BundlePackageEngineParsePackageFromXml( hr = XmlGetYesNoAttribute(pixnBundlePackage, L"Win64", &pPackage->Bundle.fWin64); ExitOnRequiredXmlQueryFailure(hr, "Failed to get @Win64."); + hr = BundlePackageEngineParseRelatedCodes(pixnBundlePackage, &pPackage->Bundle.rgsczDetectCodes, &pPackage->Bundle.cDetectCodes, &pPackage->Bundle.rgsczUpgradeCodes, &pPackage->Bundle.cUpgradeCodes, &pPackage->Bundle.rgsczAddonCodes, &pPackage->Bundle.cAddonCodes, &pPackage->Bundle.rgsczPatchCodes, &pPackage->Bundle.cPatchCodes); + ExitOnFailure(hr, "Failed to parse related codes."); + hr = ExeEngineParseExitCodesFromXml(pixnBundlePackage, &pPackage->Bundle.rgExitCodes, &pPackage->Bundle.cExitCodes); ExitOnFailure(hr, "Failed to parse exit codes."); @@ -70,11 +97,101 @@ LExit: return hr; } + +extern "C" HRESULT BundlePackageEngineParseRelatedCodes( + __in IXMLDOMNode* pixnBundle, + __in LPWSTR** prgsczDetectCodes, + __in DWORD* pcDetectCodes, + __in LPWSTR** prgsczUpgradeCodes, + __in DWORD* pcUpgradeCodes, + __in LPWSTR** prgsczAddonCodes, + __in DWORD* pcAddonCodes, + __in LPWSTR** prgsczPatchCodes, + __in DWORD* pcPatchCodes + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnElement = NULL; + LPWSTR sczAction = NULL; + LPWSTR sczId = NULL; + DWORD cElements = 0; + + hr = XmlSelectNodes(pixnBundle, L"RelatedBundle", &pixnNodes); + ExitOnFailure(hr, "Failed to get RelatedBundle nodes"); + + hr = pixnNodes->get_length((long*)&cElements); + ExitOnFailure(hr, "Failed to get RelatedBundle element count."); + + for (DWORD i = 0; i < cElements; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnElement, NULL); + ExitOnFailure(hr, "Failed to get next RelatedBundle element."); + + hr = XmlGetAttributeEx(pixnElement, L"Action", &sczAction); + ExitOnFailure(hr, "Failed to get @Action."); + + hr = XmlGetAttributeEx(pixnElement, L"Id", &sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Detect", -1)) + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(prgsczDetectCodes), *pcDetectCodes, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Detect code array"); + + *prgsczDetectCodes[*pcDetectCodes] = sczId; + sczId = NULL; + *pcDetectCodes += 1; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Upgrade", -1)) + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(prgsczUpgradeCodes), *pcUpgradeCodes, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Upgrade code array"); + + *prgsczUpgradeCodes[*pcUpgradeCodes] = sczId; + sczId = NULL; + *pcUpgradeCodes += 1; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Addon", -1)) + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(prgsczAddonCodes), *pcAddonCodes, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Addon code array"); + + *prgsczAddonCodes[*pcAddonCodes] = sczId; + sczId = NULL; + *pcAddonCodes += 1; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Patch", -1)) + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(prgsczPatchCodes), *pcPatchCodes, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Patch code array"); + + *prgsczPatchCodes[*pcPatchCodes] = sczId; + sczId = NULL; + *pcPatchCodes += 1; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Action: %ls", sczAction); + } + } + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnElement); + ReleaseStr(sczAction); + ReleaseStr(sczId); + + return hr; +} + extern "C" void BundlePackageEnginePackageUninitialize( __in BURN_PACKAGE* pPackage ) { ReleaseStr(pPackage->Bundle.sczBundleId); + ReleaseVerutilVersion(pPackage->Bundle.pVersion); ReleaseStr(pPackage->Bundle.sczRegistrationKey); ReleaseStr(pPackage->Bundle.sczInstallArguments); ReleaseStr(pPackage->Bundle.sczRepairArguments); @@ -92,45 +209,95 @@ extern "C" void BundlePackageEnginePackageUninitialize( MemFree(pPackage->Bundle.rgCommandLineArguments); } + for (DWORD i = 0; i < pPackage->Bundle.cDetectCodes; ++i) + { + ReleaseStr(pPackage->Bundle.rgsczDetectCodes[i]); + } + ReleaseMem(pPackage->Bundle.rgsczDetectCodes); + + for (DWORD i = 0; i < pPackage->Bundle.cUpgradeCodes; ++i) + { + ReleaseStr(pPackage->Bundle.rgsczUpgradeCodes[i]); + } + ReleaseMem(pPackage->Bundle.rgsczUpgradeCodes); + + for (DWORD i = 0; i < pPackage->Bundle.cAddonCodes; ++i) + { + ReleaseStr(pPackage->Bundle.rgsczAddonCodes[i]); + } + ReleaseMem(pPackage->Bundle.rgsczAddonCodes); + + for (DWORD i = 0; i < pPackage->Bundle.cPatchCodes; ++i) + { + ReleaseStr(pPackage->Bundle.rgsczPatchCodes[i]); + } + ReleaseMem(pPackage->Bundle.rgsczPatchCodes); + // clear struct memset(&pPackage->Bundle, 0, sizeof(pPackage->Bundle)); } extern "C" HRESULT BundlePackageEngineDetectPackage( - __in BURN_PACKAGE* pPackage + __in BURN_PACKAGE* pPackage, + __in BURN_REGISTRATION* /*pRegistration*/, + __in BURN_USER_EXPERIENCE* pUserExperience ) { HRESULT hr = S_OK; - HKEY hkRegistration = NULL; - DWORD dwInstalled = 0; - BOOL fDetected = FALSE; - HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - REG_KEY_BITNESS bitness = pPackage->Bundle.fWin64 ? REG_KEY_64BIT : REG_KEY_32BIT; - - // TODO: detect all related bundles, so that the Obsolete state can be detected. - hr = RegOpenEx(hkRoot, pPackage->Bundle.sczRegistrationKey, KEY_QUERY_VALUE, bitness, &hkRegistration); - if (SUCCEEDED(hr)) + BUNDLE_QUERY_CONTEXT queryContext = { }; + + queryContext.pPackage = pPackage; + queryContext.pUserExperience = pUserExperience; + + hr = BundleQueryRelatedBundles( + BUNDLE_INSTALL_CONTEXT_MACHINE, + const_cast(pPackage->Bundle.rgsczDetectCodes), + pPackage->Bundle.cDetectCodes, + const_cast(pPackage->Bundle.rgsczUpgradeCodes), + pPackage->Bundle.cUpgradeCodes, + const_cast(pPackage->Bundle.rgsczAddonCodes), + pPackage->Bundle.cAddonCodes, + const_cast(pPackage->Bundle.rgsczPatchCodes), + pPackage->Bundle.cPatchCodes, + QueryRelatedBundlesCallback, + &queryContext); + ExitOnFailure(hr, "Failed to query per-machine related bundle packages."); + + hr = BundleQueryRelatedBundles( + BUNDLE_INSTALL_CONTEXT_USER, + const_cast(pPackage->Bundle.rgsczDetectCodes), + pPackage->Bundle.cDetectCodes, + const_cast(pPackage->Bundle.rgsczUpgradeCodes), + pPackage->Bundle.cUpgradeCodes, + const_cast(pPackage->Bundle.rgsczAddonCodes), + pPackage->Bundle.cAddonCodes, + const_cast(pPackage->Bundle.rgsczPatchCodes), + pPackage->Bundle.cPatchCodes, + QueryRelatedBundlesCallback, + &queryContext); + ExitOnFailure(hr, "Failed to query per-user related bundle packages."); + + if (queryContext.fNewerFound) { - hr = RegReadNumber(hkRegistration, REGISTRY_BUNDLE_INSTALLED, &dwInstalled); + pPackage->currentState = queryContext.fSelfFound ? BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED : BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; } - - // Not finding the key or value is okay. - if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + else { - hr = S_OK; + pPackage->currentState = queryContext.fSelfFound ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; } - fDetected = (1 == dwInstalled); - - // update detect state - pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; - if (pPackage->fCanAffectRegistration) { pPackage->installRegistrationState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < pPackage->currentState ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; } - ReleaseRegKey(hkRegistration); + // TODO: The bundle is registering itself as a dependent when installed as a chain package, which prevents us from uninstalling it. + //hr = DependencyDetectChainPackage(pPackage, pRegistration); + //ExitOnFailure(hr, "Failed to detect dependencies for BUNDLE package."); + + // TODO: uninstalling compatible Bundles like MsiEngine supports? + +LExit: return hr; } @@ -148,7 +315,8 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( // execute action switch (pPackage->currentState) { - case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: switch (pPackage->requested) { case BOOTSTRAPPER_REQUEST_STATE_PRESENT: @@ -173,6 +341,7 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( } break; + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: switch (pPackage->requested) { @@ -200,7 +369,8 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( { switch (pPackage->currentState) { - case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: switch (pPackage->requested) { case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; @@ -218,6 +388,7 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( } break; + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: switch (pPackage->requested) { @@ -481,6 +652,66 @@ LExit: return; } +static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback( + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + BUNDLE_QUERY_CALLBACK_RESULT result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE; + LPWSTR sczBundleVersion = NULL; + VERUTIL_VERSION* pVersion = NULL; + int nCompare = 0; + BUNDLE_QUERY_CONTEXT* pContext = reinterpret_cast(pvContext); + BURN_PACKAGE* pPackage = pContext->pPackage; + BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType); + BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pPackage->Bundle.sczBundleId, -1)) + { + Assert(BOOTSTRAPPER_RELATION_UPGRADE == relationType); + Assert(pPackage->Bundle.fWin64 == (REG_KEY_64BIT == pBundle->regBitness)); + + pContext->fSelfFound = TRUE; + } + + hr = RegReadString(pBundle->hkBundle, BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION, &sczBundleVersion); + ExitOnFailure(hr, "Failed to read version from registry for related bundle package: %ls", pBundle->wzBundleId); + + hr = VerParseVersion(sczBundleVersion, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to parse related bundle package version: %ls", sczBundleVersion); + + if (pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_RELATED_PACKAGE_INVALID_VERSION, pBundle->wzBundleId, sczBundleVersion); + } + + if (BOOTSTRAPPER_RELATION_UPGRADE == relationType) + { + hr = VerCompareParsedVersions(pPackage->Bundle.pVersion, pVersion, &nCompare); + ExitOnFailure(hr, "Failed to compare related bundle package version: %ls", pVersion->sczVersion); + + if (nCompare < 0) + { + pContext->fNewerFound = TRUE; + } + } + + result = BUNDLE_QUERY_CALLBACK_RESULT_CANCEL; + + // Pass to BA. + hr = UserExperienceOnDetectRelatedBundlePackage(pContext->pUserExperience, pPackage->sczId, pBundle->wzBundleId, relationType, fPerMachine, pVersion); + ExitOnRootFailure(hr, "BA aborted detect related BUNDLE package."); + + result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE; + +LExit: + ReleaseVerutilVersion(pVersion); + ReleaseStr(sczBundleVersion); + + return result; +} + static HRESULT ExecuteBundle( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, diff --git a/src/burn/engine/bundlepackageengine.h b/src/burn/engine/bundlepackageengine.h index 9271ac6a..e245f6ce 100644 --- a/src/burn/engine/bundlepackageengine.h +++ b/src/burn/engine/bundlepackageengine.h @@ -13,11 +13,24 @@ HRESULT BundlePackageEngineParsePackageFromXml( __in IXMLDOMNode* pixnBundlePackage, __in BURN_PACKAGE* pPackage ); +HRESULT BundlePackageEngineParseRelatedCodes( + __in IXMLDOMNode* pixnBundle, + __in LPWSTR** prgsczDetectCodes, + __in DWORD* pcDetectCodes, + __in LPWSTR** prgsczUpgradeCodes, + __in DWORD* pcUpgradeCodes, + __in LPWSTR** prgsczAddonCodes, + __in DWORD* pcAddonCodes, + __in LPWSTR** prgsczPatchCodes, + __in DWORD* pcPatchCodes + ); void BundlePackageEnginePackageUninitialize( __in BURN_PACKAGE* pPackage ); HRESULT BundlePackageEngineDetectPackage( - __in BURN_PACKAGE* pPackage + __in BURN_PACKAGE* pPackage, + __in BURN_REGISTRATION* pRegistration, + __in BURN_USER_EXPERIENCE* pUserExperience ); HRESULT BundlePackageEnginePlanCalculatePackage( __in BURN_PACKAGE* pPackage diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index 3370ad05..0bbf7039 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -2121,7 +2121,7 @@ static HRESULT DetectPackage( switch (pPackage->type) { case BURN_PACKAGE_TYPE_BUNDLE: - hr = BundlePackageEngineDetectPackage(pPackage); + hr = BundlePackageEngineDetectPackage(pPackage, &pEngineState->registration, &pEngineState->userExperience); break; case BURN_PACKAGE_TYPE_EXE: diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h index c13c651b..4021031f 100644 --- a/src/burn/engine/package.h +++ b/src/burn/engine/package.h @@ -303,14 +303,23 @@ typedef struct _BURN_PACKAGE struct { LPWSTR sczBundleId; + VERUTIL_VERSION* pVersion; LPWSTR sczRegistrationKey; LPWSTR sczInstallArguments; LPWSTR sczRepairArguments; LPWSTR sczUninstallArguments; - LPWSTR sczIgnoreDependencies; - LPCWSTR wzAncestors; // points directly into engine state. - LPCWSTR wzEngineWorkingDirectory; // points directly into engine state. + LPWSTR* rgsczDetectCodes; + DWORD cDetectCodes; + + LPWSTR* rgsczUpgradeCodes; + DWORD cUpgradeCodes; + + LPWSTR* rgsczAddonCodes; + DWORD cAddonCodes; + + LPWSTR* rgsczPatchCodes; + DWORD cPatchCodes; BOOL fWin64; BOOL fSupportsBurnProtocol; @@ -320,6 +329,10 @@ typedef struct _BURN_PACKAGE BURN_EXE_COMMAND_LINE_ARGUMENT* rgCommandLineArguments; DWORD cCommandLineArguments; + + LPWSTR sczIgnoreDependencies; + LPCWSTR wzAncestors; // points directly into engine state. + LPCWSTR wzEngineWorkingDirectory; // points directly into engine state. } Bundle; struct { diff --git a/src/burn/engine/registration.cpp b/src/burn/engine/registration.cpp index 78f8eeb1..f2b8383d 100644 --- a/src/burn/engine/registration.cpp +++ b/src/burn/engine/registration.cpp @@ -71,10 +71,6 @@ static HRESULT UpdateResumeMode( __in BOOTSTRAPPER_REGISTRATION_TYPE registrationType, __in BOOL fRestartInitiated ); -static HRESULT ParseRelatedCodes( - __in BURN_REGISTRATION* pRegistration, - __in IXMLDOMNode* pixnBundle - ); static HRESULT FormatUpdateRegistrationKey( __in BURN_REGISTRATION* pRegistration, __out_z LPWSTR* psczKey @@ -143,7 +139,7 @@ extern "C" HRESULT RegistrationParseFromXml( hr = XmlGetAttributeEx(pixnRegistrationNode, L"Tag", &pRegistration->sczTag); ExitOnFailure(hr, "Failed to get @Tag."); - hr = ParseRelatedCodes(pRegistration, pixnBundle); + hr = BundlePackageEngineParseRelatedCodes(pixnBundle, &pRegistration->rgsczDetectCodes, &pRegistration->cDetectCodes, &pRegistration->rgsczUpgradeCodes, &pRegistration->cUpgradeCodes, &pRegistration->rgsczAddonCodes, &pRegistration->cAddonCodes, &pRegistration->rgsczPatchCodes, &pRegistration->cPatchCodes); ExitOnFailure(hr, "Failed to parse related bundles"); // @Version @@ -1395,87 +1391,6 @@ LExit: return hr; } -static HRESULT ParseRelatedCodes( - __in BURN_REGISTRATION* pRegistration, - __in IXMLDOMNode* pixnBundle - ) -{ - HRESULT hr = S_OK; - IXMLDOMNodeList* pixnNodes = NULL; - IXMLDOMNode* pixnElement = NULL; - LPWSTR sczAction = NULL; - LPWSTR sczId = NULL; - DWORD cElements = 0; - - hr = XmlSelectNodes(pixnBundle, L"RelatedBundle", &pixnNodes); - ExitOnFailure(hr, "Failed to get RelatedBundle nodes"); - - hr = pixnNodes->get_length((long*)&cElements); - ExitOnFailure(hr, "Failed to get RelatedBundle element count."); - - for (DWORD i = 0; i < cElements; ++i) - { - hr = XmlNextElement(pixnNodes, &pixnElement, NULL); - ExitOnFailure(hr, "Failed to get next RelatedBundle element."); - - hr = XmlGetAttributeEx(pixnElement, L"Action", &sczAction); - ExitOnFailure(hr, "Failed to get @Action."); - - hr = XmlGetAttributeEx(pixnElement, L"Id", &sczId); - ExitOnFailure(hr, "Failed to get @Id."); - - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Detect", -1)) - { - hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes + 1, sizeof(LPWSTR), 5); - ExitOnFailure(hr, "Failed to resize Detect code array in registration"); - - pRegistration->rgsczDetectCodes[pRegistration->cDetectCodes] = sczId; - sczId = NULL; - ++pRegistration->cDetectCodes; - } - else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Upgrade", -1)) - { - hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes + 1, sizeof(LPWSTR), 5); - ExitOnFailure(hr, "Failed to resize Upgrade code array in registration"); - - pRegistration->rgsczUpgradeCodes[pRegistration->cUpgradeCodes] = sczId; - sczId = NULL; - ++pRegistration->cUpgradeCodes; - } - else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Addon", -1)) - { - hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes + 1, sizeof(LPWSTR), 5); - ExitOnFailure(hr, "Failed to resize Addon code array in registration"); - - pRegistration->rgsczAddonCodes[pRegistration->cAddonCodes] = sczId; - sczId = NULL; - ++pRegistration->cAddonCodes; - } - else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Patch", -1)) - { - hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes + 1, sizeof(LPWSTR), 5); - ExitOnFailure(hr, "Failed to resize Patch code array in registration"); - - pRegistration->rgsczPatchCodes[pRegistration->cPatchCodes] = sczId; - sczId = NULL; - ++pRegistration->cPatchCodes; - } - else - { - hr = E_INVALIDARG; - ExitOnFailure(hr, "Invalid value for @Action: %ls", sczAction); - } - } - -LExit: - ReleaseObject(pixnNodes); - ReleaseObject(pixnElement); - ReleaseStr(sczAction); - ReleaseStr(sczId); - - return hr; -} - static HRESULT FormatUpdateRegistrationKey( __in BURN_REGISTRATION* pRegistration, __out_z LPWSTR* psczKey diff --git a/src/burn/engine/relatedbundle.cpp b/src/burn/engine/relatedbundle.cpp index 58911711..586446b1 100644 --- a/src/burn/engine/relatedbundle.cpp +++ b/src/burn/engine/relatedbundle.cpp @@ -143,6 +143,30 @@ extern "C" void RelatedBundlesSortPlan( qsort_s(pRelatedBundles->rgpPlanSortedRelatedBundles, pRelatedBundles->cRelatedBundles, sizeof(BURN_RELATED_BUNDLE*), CompareRelatedBundlesPlan, NULL); } +extern "C" BOOTSTRAPPER_RELATION_TYPE RelatedBundleConvertRelationType( + __in BUNDLE_RELATION_TYPE relationType + ) +{ + switch (relationType) + { + case BUNDLE_RELATION_DETECT: + return BOOTSTRAPPER_RELATION_DETECT; + case BUNDLE_RELATION_UPGRADE: + return BOOTSTRAPPER_RELATION_UPGRADE; + case BUNDLE_RELATION_ADDON: + return BOOTSTRAPPER_RELATION_ADDON; + case BUNDLE_RELATION_PATCH: + return BOOTSTRAPPER_RELATION_PATCH; + case BUNDLE_RELATION_DEPENDENT_ADDON: + return BOOTSTRAPPER_RELATION_DEPENDENT_ADDON; + case BUNDLE_RELATION_DEPENDENT_PATCH: + return BOOTSTRAPPER_RELATION_DEPENDENT_PATCH; + default: + AssertSz(BUNDLE_RELATION_NONE == relationType, "Unknown BUNDLE_RELATION_TYPE"); + return BOOTSTRAPPER_RELATION_NONE; + } +} + // internal helper functions @@ -248,30 +272,6 @@ LExit: return result; } -static BOOTSTRAPPER_RELATION_TYPE ConvertRelationType( - __in BUNDLE_RELATION_TYPE relationType - ) -{ - switch (relationType) - { - case BUNDLE_RELATION_DETECT: - return BOOTSTRAPPER_RELATION_DETECT; - case BUNDLE_RELATION_UPGRADE: - return BOOTSTRAPPER_RELATION_UPGRADE; - case BUNDLE_RELATION_ADDON: - return BOOTSTRAPPER_RELATION_ADDON; - case BUNDLE_RELATION_PATCH: - return BOOTSTRAPPER_RELATION_PATCH; - case BUNDLE_RELATION_DEPENDENT_ADDON: - return BOOTSTRAPPER_RELATION_DEPENDENT_ADDON; - case BUNDLE_RELATION_DEPENDENT_PATCH: - return BOOTSTRAPPER_RELATION_DEPENDENT_PATCH; - default: - AssertSz(BUNDLE_RELATION_NONE == relationType, "Unknown BUNDLE_RELATION_TYPE"); - return BOOTSTRAPPER_RELATION_NONE; - } -} - static HRESULT LoadIfRelatedBundle( __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, __in BURN_REGISTRATION* pRegistration, @@ -280,7 +280,7 @@ static HRESULT LoadIfRelatedBundle( { HRESULT hr = S_OK; BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext; - BOOTSTRAPPER_RELATION_TYPE relationType = ConvertRelationType(pBundle->relationType); + BOOTSTRAPPER_RELATION_TYPE relationType = RelatedBundleConvertRelationType(pBundle->relationType); BURN_RELATED_BUNDLE* pRelatedBundle = NULL; // If we found our bundle id, it's not a related bundle. diff --git a/src/burn/engine/relatedbundle.h b/src/burn/engine/relatedbundle.h index 24469f3d..e98d0ede 100644 --- a/src/burn/engine/relatedbundle.h +++ b/src/burn/engine/relatedbundle.h @@ -25,6 +25,9 @@ void RelatedBundlesSortDetect( void RelatedBundlesSortPlan( __in BURN_RELATED_BUNDLES* pRelatedBundles ); +BOOTSTRAPPER_RELATION_TYPE RelatedBundleConvertRelationType( + __in BUNDLE_RELATION_TYPE relationType + ); #if defined(__cplusplus) } diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index 06f87363..a1a010d2 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad( args.pCommand = pCommand; args.pfnBootstrapperEngineProc = EngineForApplicationProc; args.pvBootstrapperEngineProcContext = pEngineContext; - args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 17, 0); + args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 31, 0); results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); @@ -1247,6 +1247,40 @@ LExit: return hr; } +EXTERN_C BAAPI UserExperienceOnDetectRelatedBundlePackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTRELATEDBUNDLEPACKAGE_ARGS args = { }; + BA_ONDETECTRELATEDBUNDLEPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzBundleId = wzBundleId; + args.relationType = relationType; + args.fPerMachine = fPerMachine; + args.wzVersion = pVersion->sczVersion; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnDetectRelatedBundlePackage failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnDetectRelatedMsiPackage( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h index de558ad5..90a047ed 100644 --- a/src/burn/engine/userexperience.h +++ b/src/burn/engine/userexperience.h @@ -298,6 +298,14 @@ BAAPI UserExperienceOnDetectRelatedBundle( __in VERUTIL_VERSION* pVersion, __in BOOL fMissingFromCache ); +BAAPI UserExperienceOnDetectRelatedBundlePackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion + ); BAAPI UserExperienceOnDetectRelatedMsiPackage( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml index 6c60085f..0b521faa 100644 --- a/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 444a917f..969aca9d 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -1489,6 +1489,9 @@ public: // IBootstrapperApplication case BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE: OnApplyDowngradeFallback(reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE: + OnDetectRelatedBundlePackageFallback(reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; default: #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "WIXSTDBA: Forwarding unknown BA message: %d", message); @@ -2178,6 +2181,14 @@ private: // privates m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL, pArgs, pResults, m_pvBAFunctionsProcContext); } + void OnDetectRelatedBundlePackageFallback( + __in BA_ONDETECTRELATEDBUNDLEPACKAGE_ARGS* pArgs, + __inout BA_ONDETECTRELATEDBUNDLEPACKAGE_RESULTS* pResults + ) + { + m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, pArgs, pResults, m_pvBAFunctionsProcContext); + } + public: //CBalBaseBootstrapperApplication virtual STDMETHODIMP Initialize( diff --git a/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundle.props b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundle.props new file mode 100644 index 00000000..1c761dfc --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundle.props @@ -0,0 +1,14 @@ + + + + Bundle + hyperlinkLicense + {B9636854-B76B-4171-B63D-7C659F61DE3D} + + + + + + + + diff --git a/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wixproj b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wixproj new file mode 100644 index 00000000..e1e0d601 --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wixproj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wxs b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wxs new file mode 100644 index 00000000..ae70ebdb --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev1/UpgradeBundlePackageBundlev1.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wixproj b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wixproj new file mode 100644 index 00000000..61247a66 --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wixproj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wxs b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wxs new file mode 100644 index 00000000..742881c7 --- /dev/null +++ b/src/test/burn/TestData/BundlePackageTests/UpgradeBundlePackageBundlev2/UpgradeBundlePackageBundlev2.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs index 2e95aedb..1e6cda9c 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/BundlePackageTests.cs @@ -45,7 +45,37 @@ namespace WixToolsetTest.BurnE2E // Source file should *not* be installed Assert.False(File.Exists(packageA32SourceCodeFilePath), $"PackageA payload should have been removed by uninstall from: {packageA32SourceCodeFilePath}"); Assert.False(File.Exists(packageA64SourceCodeFilePath), $"PackageA_x64 payload should have been removed by uninstall from: {packageA64SourceCodeFilePath}"); + } + + [Fact] + public void CanInstallUpgradeBundlePackage() + { + var bundleAv1 = this.CreateBundleInstaller(@"..\UpgradeRelatedBundleTests\BundleAv1"); + var bundleAv2 = this.CreateBundleInstaller(@"..\UpgradeRelatedBundleTests\BundleAv2"); + var upgradeBundlePackageBundlev2 = this.CreateBundleInstaller("UpgradeBundlePackageBundlev2"); + + bundleAv1.Install(); + bundleAv1.VerifyRegisteredAndInPackageCache(); + + upgradeBundlePackageBundlev2.Install(); + upgradeBundlePackageBundlev2.VerifyRegisteredAndInPackageCache(); + bundleAv2.VerifyRegisteredAndInPackageCache(); + bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache(); + } + + [Fact] + public void CanSkipObsoleteBundlePackage() + { + var bundleAv1 = this.CreateBundleInstaller(@"..\UpgradeRelatedBundleTests\BundleAv1"); + var bundleAv2 = this.CreateBundleInstaller(@"..\UpgradeRelatedBundleTests\BundleAv2"); + var upgradeBundlePackageBundlev1 = this.CreateBundleInstaller("UpgradeBundlePackageBundlev1"); + + bundleAv2.Install(); + bundleAv2.VerifyRegisteredAndInPackageCache(); + upgradeBundlePackageBundlev1.Install(); + upgradeBundlePackageBundlev1.VerifyUnregisteredAndRemovedFromPackageCache(); + bundleAv1.VerifyUnregisteredAndRemovedFromPackageCache(); } } } diff --git a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 6b37580e..9d214211 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -82,6 +82,7 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.WixBundlePackageCommandLine: case SymbolDefinitionType.WixBundlePackageExitCode: case SymbolDefinitionType.WixBundlePackageGroup: + case SymbolDefinitionType.WixBundlePackageRelatedBundle: case SymbolDefinitionType.WixBundlePatchTargetCode: case SymbolDefinitionType.WixBundlePayload: case SymbolDefinitionType.WixBundlePayloadGroup: diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 84923998..c0e54300 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -319,6 +319,7 @@ namespace WixToolset.Core.Burn.Bundles var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var relatedBundlesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); @@ -384,6 +385,7 @@ namespace WixToolset.Core.Burn.Bundles if (package.SpecificPackageSymbol is WixBundleBundlePackageSymbol bundlePackage) // BUNDLE { writer.WriteAttributeString("BundleId", bundlePackage.BundleId); + writer.WriteAttributeString("Version", bundlePackage.Version); writer.WriteAttributeString("InstallArguments", bundlePackage.InstallCommand); writer.WriteAttributeString("UninstallArguments", bundlePackage.UninstallCommand); writer.WriteAttributeString("RepairArguments", bundlePackage.RepairCommand); @@ -536,6 +538,16 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } + var packageRelatedBundles = relatedBundlesByPackage[package.PackageId]; + + foreach (var relatedBundle in packageRelatedBundles) + { + writer.WriteStartElement("RelatedBundle"); + writer.WriteAttributeString("Id", relatedBundle.BundleId); + writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); + writer.WriteEndElement(); + } + var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; foreach (var related in packageRelatedPackages) diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessBundlePackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessBundlePackageCommand.cs index fdb07a56..e8c68faa 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessBundlePackageCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessBundlePackageCommand.cs @@ -102,6 +102,8 @@ namespace WixToolset.Core.Burn.Bundles var version = registrationElement.GetAttribute("Version"); packagePayload.Version = version; + bundlePackage.Version = version; + this.Facade.PackageSymbol.Version = version; if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) { @@ -125,6 +127,8 @@ namespace WixToolset.Core.Burn.Bundles this.ProcessPackages(document, namespaceManager); + this.ProcessRelatedBundles(document, namespaceManager); + // TODO: Add payloads? } } @@ -148,5 +152,26 @@ namespace WixToolset.Core.Burn.Bundles this.Facade.PackageSymbol.InstallSize = packageInstallSize; } + + private void ProcessRelatedBundles(XmlDocument document, XmlNamespaceManager namespaceManager) + { + foreach (XmlElement relatedBundleElement in document.SelectNodes("/burn:BurnManifest/burn:RelatedBundle", namespaceManager)) + { + var id = relatedBundleElement.GetAttribute("Id"); + + if (!Enum.TryParse(relatedBundleElement.GetAttribute("Action"), out RelatedBundleActionType action)) + { + // TODO: warning + continue; + } + + this.Section.AddSymbol(new WixBundlePackageRelatedBundleSymbol + { + PackageRef = this.Facade.PackageId, + BundleId = id, + Action = action, + }); + } + } } } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundlePackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundlePackageFixture.cs index 268920a6..6ccddbb7 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/BundlePackageFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundlePackageFixture.cs @@ -95,7 +95,11 @@ namespace WixToolsetTest.CoreIntegration .ToArray(); WixAssert.CompareLineByLine(new string[] { - $"", + $"" + + "" + + "" + + "" + + "", }, bundlePackages); var registrations = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") @@ -108,6 +112,19 @@ namespace WixToolsetTest.CoreIntegration "" + "" }, registrations); + + ignoreAttributesByElementName = new Dictionary> + { + { "WixPackageProperties", new List { "DownloadSize", "PackageSize" } }, + }; + var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, packageElements); } } } -- cgit v1.2.3-55-g6feb