From d5985a1688bc878e42ffd3ce3939fa52303cab16 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 13 May 2022 15:39:40 -0500 Subject: Add option to hosts to always install prereqs. Add PrereqPackage to BundlePackage Implements 4718 --- src/ext/Bal/dnchost/dnchost.cpp | 19 ++ src/ext/Bal/mbahost/mbahost.cpp | 54 ++++- .../test/WixToolsetTest.Bal/BalExtensionFixture.cs | 38 +++- .../TestData/MBA/AlwaysInstallPrereqsBundle.wxs | 12 ++ src/ext/Bal/wixext/BalBurnBackendExtension.cs | 1 + src/ext/Bal/wixext/BalCompiler.cs | 27 ++- src/ext/Bal/wixext/Symbols/BalSymbolDefinitions.cs | 5 + .../wixext/Symbols/WixMbaPrereqOptionsSymbol.cs | 47 +++++ .../WixStandardBootstrapperApplication.cpp | 179 ++++++++++++++-- src/ext/Bal/wixstdba/inc/preqba.h | 1 + src/test/burn/TestBA/TestBA.cs | 7 + .../TestData/PrereqBaTests/BundleA/BundleA.wixproj | 5 +- .../TestData/PrereqBaTests/BundleA/BundleA.wxs | 12 +- .../TestData/PrereqBaTests/BundleB/BundleB.wixproj | 5 +- .../TestData/PrereqBaTests/BundleB/BundleB.wxs | 12 +- .../TestData/PrereqBaTests/BundleC/BundleC.wixproj | 24 +++ .../TestData/PrereqBaTests/BundleC/BundleC.wxs | 30 +++ .../PrereqBaTests/BundleC/bad.runtimeconfig.json | 10 + .../TestData/PrereqBaTests/BundleD/BundleD.wixproj | 24 +++ .../TestData/PrereqBaTests/BundleD/BundleD.wxs | 29 +++ .../burn/TestData/PrereqBaTests/BundleD/bad.config | 17 ++ .../TestData/PrereqBaTests/BundleE/BundleE.wixproj | 22 ++ .../TestData/PrereqBaTests/BundleE/BundleE.wxs | 26 +++ .../PrereqBaTests/PackageC/PackageC.wixproj | 9 + .../PrereqBaTests/PackageF/PackageF.wixproj | 12 -- .../TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp | 79 ++++++++ .../TestData/PrereqBaTests/PrereqBaf/PrereqBaf.def | 6 + .../PrereqBaTests/PrereqBaf/PrereqBaf.vcxproj | 66 ++++++ .../TestData/PrereqBaTests/PrereqBaf/precomp.cpp | 48 +++++ .../TestData/PrereqBaTests/PrereqBaf/precomp.h | 31 +++ .../PrereqBaTests/ReplaceConfig/ReplaceConfig.cpp | 33 +++ .../ReplaceConfig/ReplaceConfig.vcxproj | 63 ++++++ .../PrereqBaTests/ReplaceConfig/precomp.cpp | 3 + .../TestData/PrereqBaTests/ReplaceConfig/precomp.h | 17 ++ src/test/burn/WixTestTools/BundleInstaller.cs | 14 ++ .../burn/WixToolsetTest.BurnE2E/PrereqBaTests.cs | 225 ++++++++++++++++++++- 36 files changed, 1162 insertions(+), 50 deletions(-) create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/MBA/AlwaysInstallPrereqsBundle.wxs create mode 100644 src/ext/Bal/wixext/Symbols/WixMbaPrereqOptionsSymbol.cs create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wixproj create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleC/bad.runtimeconfig.json create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wixproj create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleD/bad.config create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wixproj create mode 100644 src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs create mode 100644 src/test/burn/TestData/PrereqBaTests/PackageC/PackageC.wixproj delete mode 100644 src/test/burn/TestData/PrereqBaTests/PackageF/PackageF.wixproj create mode 100644 src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp create mode 100644 src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.def create mode 100644 src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.vcxproj create mode 100644 src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.cpp create mode 100644 src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.h create mode 100644 src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.cpp create mode 100644 src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.vcxproj create mode 100644 src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.cpp create mode 100644 src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.h diff --git a/src/ext/Bal/dnchost/dnchost.cpp b/src/ext/Bal/dnchost/dnchost.cpp index 6c066f43..36970f83 100644 --- a/src/ext/Bal/dnchost/dnchost.cpp +++ b/src/ext/Bal/dnchost/dnchost.cpp @@ -76,6 +76,16 @@ extern "C" HRESULT WINAPI BootstrapperApplicationCreate( vstate.fInitialized = TRUE; } + if (vstate.prereqData.fAlwaysInstallPrereqs && !vstate.prereqData.fCompleted) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application since it's configured to always run before loading the runtime."); + + hr = CreatePrerequisiteBA(&vstate, pEngine, pArgs, pResults); + BalExitOnFailure(hr, "Failed to create the pre-requisite bootstrapper application."); + + ExitFunction(); + } + if (!vstate.fInitializedRuntime) { hr = LoadRuntime(&vstate); @@ -214,6 +224,15 @@ static HRESULT LoadDncConfiguration( hr = StrAllocConcat(&pState->sczBaFactoryRuntimeConfigPath, L".runtimeconfig.json", 0); BalExitOnFailure(hr, "Failed to concat extension to runtime config path."); + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixMbaPrereqOptions", &pixnHost); + BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to find WixMbaPrereqOptions element in bootstrapper application config."); + + if (fXmlFound) + { + hr = XmlGetAttributeNumber(pixnHost, L"AlwaysInstallPrereqs", reinterpret_cast(&pState->prereqData.fAlwaysInstallPrereqs)); + BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get AlwaysInstallPrereqs value."); + } + pState->type = DNCHOSTTYPE_FDD; hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixDncOptions", &pixnHost); diff --git a/src/ext/Bal/mbahost/mbahost.cpp b/src/ext/Bal/mbahost/mbahost.cpp index 3de77a05..0b89fc58 100644 --- a/src/ext/Bal/mbahost/mbahost.cpp +++ b/src/ext/Bal/mbahost/mbahost.cpp @@ -24,6 +24,10 @@ static HRESULT GetAppDomain( static HRESULT LoadModulePaths( __in MBASTATE* pState ); +static HRESULT LoadMbaConfiguration( + __in MBASTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ); static HRESULT CheckSupportedFrameworks( __in LPCWSTR wzConfigPath ); @@ -96,12 +100,28 @@ extern "C" HRESULT WINAPI BootstrapperApplicationCreate( if (!vstate.fInitialized) { + hr = XmlInitialize(); + BalExitOnFailure(hr, "Failed to initialize XML."); + hr = LoadModulePaths(&vstate); BalExitOnFailure(hr, "Failed to load the module paths."); + hr = LoadMbaConfiguration(&vstate, pArgs); + BalExitOnFailure(hr, "Failed to get the mba configuration."); + vstate.fInitialized = TRUE; } + if (vstate.prereqData.fAlwaysInstallPrereqs && !vstate.prereqData.fCompleted) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application since it's configured to always run before loading the runtime."); + + hr = CreatePrerequisiteBA(&vstate, pEngine, pArgs, pResults); + BalExitOnFailure(hr, "Failed to create the pre-requisite bootstrapper application."); + + ExitFunction(); + } + if (!vstate.fInitializedRuntime) { hr = LoadRuntime(&vstate); @@ -264,6 +284,35 @@ LExit: return hr; } +static HRESULT LoadMbaConfiguration( + __in MBASTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixdManifest = NULL; + IXMLDOMNode* pixnHost = NULL; + BOOL fXmlFound = FALSE; + + hr = XmlLoadDocumentFromFile(pArgs->pCommand->wzBootstrapperApplicationDataPath, &pixdManifest); + BalExitOnFailure(hr, "Failed to load BalManifest '%ls'", pArgs->pCommand->wzBootstrapperApplicationDataPath); + + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixMbaPrereqOptions", &pixnHost); + BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to find WixMbaPrereqOptions element in bootstrapper application config."); + + if (fXmlFound) + { + hr = XmlGetAttributeNumber(pixnHost, L"AlwaysInstallPrereqs", reinterpret_cast(&pState->prereqData.fAlwaysInstallPrereqs)); + BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get AlwaysInstallPrereqs value."); + } + +LExit: + ReleaseObject(pixnHost); + ReleaseObject(pixdManifest); + + return hr; +} + // Checks whether at least one of required supported frameworks is installed via the NETFX registry keys. static HRESULT CheckSupportedFrameworks( __in LPCWSTR wzConfigPath @@ -280,9 +329,6 @@ static HRESULT CheckSupportedFrameworks( DWORD dwFrameworkInstalled = 0; BOOL fUpdatedManifest = FALSE; - hr = XmlInitialize(); - ExitOnFailure(hr, "Failed to initialize XML."); - hr = XmlLoadDocumentFromFile(wzConfigPath, &pixdManifest); ExitOnFailure(hr, "Failed to load bootstrapper config file from path: %ls", wzConfigPath); @@ -342,8 +388,6 @@ LExit: ReleaseObject(pNodeList); ReleaseObject(pixdManifest); - XmlUninitialize(); - return hr; } diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs index 9aea8c1d..43484855 100644 --- a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs +++ b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs @@ -107,7 +107,41 @@ namespace WixToolsetTest.Bal } [Fact] - public void CantBuildUsingMBAWithNoPrereqs() + public void CanBuildUsingMBAWithAlwaysInstallPrereqs() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\MBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "AlwaysInstallPrereqsBundle.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-o", bundleFile, + }); + + compileResult.AssertSuccess(); + + Assert.True(File.Exists(bundleFile)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var wixMbaPrereqOptionsElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixMbaPrereqOptions"); + var wixMbaPrereqOptions = (XmlNode)Assert.Single(wixMbaPrereqOptionsElements); + Assert.Equal("", wixMbaPrereqOptions.GetTestXml()); + } + } + + [Fact] + public void CannotBuildUsingMBAWithNoPrereqs() { using (var fs = new DisposableFileSystem()) { @@ -133,7 +167,7 @@ namespace WixToolsetTest.Bal } [Fact] - public void CantBuildUsingOverridableWrongCase() + public void CannotBuildUsingOverridableWrongCase() { using (var fs = new DisposableFileSystem()) { diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/MBA/AlwaysInstallPrereqsBundle.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/MBA/AlwaysInstallPrereqsBundle.wxs new file mode 100644 index 00000000..685fef7b --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/MBA/AlwaysInstallPrereqsBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/ext/Bal/wixext/BalBurnBackendExtension.cs b/src/ext/Bal/wixext/BalBurnBackendExtension.cs index 3b19ae78..d34c159a 100644 --- a/src/ext/Bal/wixext/BalBurnBackendExtension.cs +++ b/src/ext/Bal/wixext/BalBurnBackendExtension.cs @@ -24,6 +24,7 @@ namespace WixToolset.Bal BalSymbolDefinitions.WixMbaPrereqInformation, BalSymbolDefinitions.WixStdbaOptions, BalSymbolDefinitions.WixStdbaOverridableVariable, + BalSymbolDefinitions.WixMbaPrereqOptions, }; protected override IReadOnlyCollection SymbolDefinitions => BurnSymbolDefinitions; diff --git a/src/ext/Bal/wixext/BalCompiler.cs b/src/ext/Bal/wixext/BalCompiler.cs index 267345e7..1721f252 100644 --- a/src/ext/Bal/wixext/BalCompiler.cs +++ b/src/ext/Bal/wixext/BalCompiler.cs @@ -117,6 +117,7 @@ namespace WixToolset.Bal switch (parentElement.Name.LocalName) { + case "BundlePackage": case "ExePackage": case "MsiPackage": case "MspPackage": @@ -216,7 +217,7 @@ namespace WixToolset.Bal case "PrereqPackage": if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute)) { - if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out prereqInfo)) + if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out _)) { prereqInfo = section.AddSymbol(new WixMbaPrereqInformationSymbol(sourceLineNumbers) { @@ -703,6 +704,7 @@ namespace WixToolset.Bal private void ParseWixManagedBootstrapperApplicationHostElement(Intermediate intermediate, IntermediateSection section, XElement node) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); + bool alwaysInstallPrereqs = false; string logoFile = null; string themeFile = null; string localizationFile = null; @@ -714,6 +716,9 @@ namespace WixToolset.Bal { switch (attrib.Name.LocalName) { + case "AlwaysInstallPrereqs": + alwaysInstallPrereqs = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes; + break; case "LogoFile": logoFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; @@ -792,6 +797,14 @@ namespace WixToolset.Bal } this.CreateBARef(section, sourceLineNumbers, node, baId); + + if (alwaysInstallPrereqs) + { + section.AddSymbol(new WixMbaPrereqOptionsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixMbaPrereqOptions")) + { + AlwaysInstallPrereqs = 1, + }); + } } } @@ -802,6 +815,7 @@ namespace WixToolset.Bal private void ParseWixDotNetCoreBootstrapperApplicationHostElement(Intermediate intermediate, IntermediateSection section, XElement node) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); + bool alwaysInstallPrereqs = false; string logoFile = null; string themeFile = null; string localizationFile = null; @@ -814,6 +828,9 @@ namespace WixToolset.Bal { switch (attrib.Name.LocalName) { + case "AlwaysInstallPrereqs": + alwaysInstallPrereqs = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes; + break; case "LogoFile": logoFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; @@ -903,6 +920,14 @@ namespace WixToolset.Bal } this.CreateBARef(section, sourceLineNumbers, node, baId); + + if (alwaysInstallPrereqs) + { + section.AddSymbol(new WixMbaPrereqOptionsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixMbaPrereqOptions")) + { + AlwaysInstallPrereqs = 1, + }); + } } } diff --git a/src/ext/Bal/wixext/Symbols/BalSymbolDefinitions.cs b/src/ext/Bal/wixext/Symbols/BalSymbolDefinitions.cs index 90865621..9010ce2d 100644 --- a/src/ext/Bal/wixext/Symbols/BalSymbolDefinitions.cs +++ b/src/ext/Bal/wixext/Symbols/BalSymbolDefinitions.cs @@ -16,6 +16,7 @@ namespace WixToolset.Bal WixMbaPrereqInformation, WixStdbaOptions, WixStdbaOverridableVariable, + WixMbaPrereqOptions, } public static partial class BalSymbolDefinitions @@ -60,6 +61,9 @@ namespace WixToolset.Bal case BalSymbolDefinitionType.WixStdbaOverridableVariable: return BalSymbolDefinitions.WixStdbaOverridableVariable; + case BalSymbolDefinitionType.WixMbaPrereqOptions: + return BalSymbolDefinitions.WixMbaPrereqOptions; + default: throw new ArgumentOutOfRangeException(nameof(type)); } @@ -75,6 +79,7 @@ namespace WixToolset.Bal WixMbaPrereqInformation.AddTag(BurnConstants.BootstrapperApplicationDataSymbolDefinitionTag); WixStdbaOptions.AddTag(BurnConstants.BootstrapperApplicationDataSymbolDefinitionTag); WixStdbaOverridableVariable.AddTag(BurnConstants.BootstrapperApplicationDataSymbolDefinitionTag); + WixMbaPrereqOptions.AddTag(BurnConstants.BootstrapperApplicationDataSymbolDefinitionTag); } } } diff --git a/src/ext/Bal/wixext/Symbols/WixMbaPrereqOptionsSymbol.cs b/src/ext/Bal/wixext/Symbols/WixMbaPrereqOptionsSymbol.cs new file mode 100644 index 00000000..66374579 --- /dev/null +++ b/src/ext/Bal/wixext/Symbols/WixMbaPrereqOptionsSymbol.cs @@ -0,0 +1,47 @@ +// 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.Bal +{ + using WixToolset.Data; + using WixToolset.Bal.Symbols; + + public static partial class BalSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixMbaPrereqOptions = new IntermediateSymbolDefinition( + BalSymbolDefinitionType.WixMbaPrereqOptions.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(WixMbaPrereqOptionsSymbolFields.AlwaysInstallPrereqs), IntermediateFieldType.Number), + }, + typeof(WixMbaPrereqOptionsSymbol)); + } +} + +namespace WixToolset.Bal.Symbols +{ + using WixToolset.Data; + + public enum WixMbaPrereqOptionsSymbolFields + { + AlwaysInstallPrereqs, + } + + public class WixMbaPrereqOptionsSymbol : IntermediateSymbol + { + public WixMbaPrereqOptionsSymbol() : base(BalSymbolDefinitions.WixMbaPrereqOptions, null, null) + { + } + + public WixMbaPrereqOptionsSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(BalSymbolDefinitions.WixMbaPrereqOptions, sourceLineNumber, id) + { + } + + public IntermediateField this[WixMbaPrereqOptionsSymbolFields index] => this.Fields[(int)index]; + + public int AlwaysInstallPrereqs + { + get => this.Fields[(int)WixMbaPrereqOptionsSymbolFields.AlwaysInstallPrereqs].AsNumber(); + set => this.Set((int)WixMbaPrereqOptionsSymbolFields.AlwaysInstallPrereqs, value); + } + } +} diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 3774f49c..9c0f9576 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -30,6 +30,8 @@ enum WIXSTDBA_STATE WIXSTDBA_STATE_HELP, WIXSTDBA_STATE_DETECTING, WIXSTDBA_STATE_DETECTED, + WIXSTDBA_STATE_PLANNING_PREREQS, + WIXSTDBA_STATE_PLANNED_PREREQS, WIXSTDBA_STATE_PLANNING, WIXSTDBA_STATE_PLANNED, WIXSTDBA_STATE_APPLYING, @@ -49,6 +51,7 @@ enum WM_WIXSTDBA WM_WIXSTDBA_APPLY_PACKAGES, WM_WIXSTDBA_CHANGE_STATE, WM_WIXSTDBA_SHOW_FAILURE, + WM_WIXSTDBA_PLAN_PREREQS, }; // This enum must be kept in the same order as the vrgwzPageNames array. @@ -217,9 +220,11 @@ public: // IBootstrapperApplication : "A restart is required by the prerequisites but the user delayed it. The bootstrapper application will be reloaded after the computer is restarted."); } } - else if (m_fPrereqInstalled) + else if (m_fPrereqInstalled || m_fPrereqSkipped) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "The prerequisites were successfully installed. The bootstrapper application will be reloaded."); + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, m_fPrereqInstalled + ? "The prerequisites were successfully installed. The bootstrapper application will be reloaded." + : "The prerequisites were already installed. The bootstrapper application will be reloaded."); *pAction = BOOTSTRAPPER_SHUTDOWN_ACTION_RELOAD_BOOTSTRAPPER; m_pPrereqData->fCompleted = TRUE; } @@ -275,13 +280,16 @@ public: // IBootstrapperApplication hr = S_OK; } - // If the UI should be visible, display it now and hide the splash screen - if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) + if (!m_fPreplanPrereqs) { - ::ShowWindow(m_pTheme->hwndParent, SW_SHOW); - } + // If the UI should be visible, display it now and hide the splash screen + if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) + { + ::ShowWindow(m_pTheme->hwndParent, SW_SHOW); + } - m_pEngine->CloseSplashScreen(); + m_pEngine->CloseSplashScreen(); + } return __super::OnDetectBegin(fCached, registrationType, cPackages, pfCancel); } @@ -331,24 +339,37 @@ public: // IBootstrapperApplication if (fEvaluateConditions) { hrStatus = EvaluateConditions(); - - if (FAILED(hrStatus)) - { - fSkipToPlan = FALSE; - } } SetState(WIXSTDBA_STATE_DETECTED, hrStatus); - if (fSkipToPlan) + if (SUCCEEDED(hrStatus)) { - ::PostMessageW(m_hWnd, WM_WIXSTDBA_PLAN_PACKAGES, 0, m_command.action); + if (m_fPreplanPrereqs) + { + ::PostMessageW(m_hWnd, WM_WIXSTDBA_PLAN_PREREQS, 0, BOOTSTRAPPER_ACTION_INSTALL); + } + else if (fSkipToPlan) + { + ::PostMessageW(m_hWnd, WM_WIXSTDBA_PLAN_PACKAGES, 0, m_command.action); + } } return hr; } + virtual STDMETHODIMP OnPlanBegin( + __in DWORD cPackages, + __in BOOL* pfCancel + ) + { + m_fPrereqPackagePlanned = FALSE; + + return __super::OnPlanBegin(cPackages, pfCancel); + } + + virtual STDMETHODIMP OnPlanRelatedBundleType( __in_z LPCWSTR wzBundleId, __in BOOTSTRAPPER_RELATED_BUNDLE_PLAN_TYPE recommendedType, @@ -484,24 +505,97 @@ public: // IBootstrapperApplication } + virtual STDMETHODIMP OnPlannedPackage( + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE execute, + __in BOOTSTRAPPER_ACTION_STATE rollback, + __in BOOL fPlannedCache, + __in BOOL fPlannedUncache + ) + { + if (m_fPrereq && BOOTSTRAPPER_ACTION_STATE_NONE != execute) + { + m_fPrereqPackagePlanned = TRUE; + } + + return __super::OnPlannedPackage(wzPackageId, execute, rollback, fPlannedCache, fPlannedUncache); + } + + virtual STDMETHODIMP OnPlanComplete( __in HRESULT hrStatus ) { HRESULT hr = S_OK; + BOOL fPlannedPrereqs = WIXSTDBA_STATE_PLANNING_PREREQS == m_state; + WIXSTDBA_STATE completedState = WIXSTDBA_STATE_PLANNED; + BOOL fApply = TRUE; - SetState(WIXSTDBA_STATE_PLANNED, hrStatus); + if (fPlannedPrereqs) + { + if (SUCCEEDED(hrStatus) && !m_fPrereqPackagePlanned) + { + // Nothing to do, so close and let the parent BA take over. + m_fPrereqSkipped = TRUE; + SetState(WIXSTDBA_STATE_APPLIED, S_OK); + ExitFunction(); + } + else if (BOOTSTRAPPER_ACTION_HELP == m_command.action) + { + // If prereq packages were planned then the managed BA probably can't be loaded, so show the help from this BA. - if (SUCCEEDED(hrStatus)) + // Need to force the state change since normally moving backwards is prevented. + ::PostMessageW(m_hWnd, WM_WIXSTDBA_CHANGE_STATE, 0, WIXSTDBA_STATE_HELP); + + ::PostMessageW(m_hWnd, WM_WIXSTDBA_SHOW_HELP, 0, 0); + + ExitFunction(); + } + + completedState = WIXSTDBA_STATE_PLANNED_PREREQS; + } + + SetState(completedState, hrStatus); + + if (FAILED(hrStatus)) + { + ExitFunction(); + } + + if (fPlannedPrereqs) + { + // If the UI should be visible, display it now and hide the splash screen + if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) + { + ::ShowWindow(m_pTheme->hwndParent, SW_SHOW); + } + + m_pEngine->CloseSplashScreen(); + + fApply = BOOTSTRAPPER_DISPLAY_FULL > m_command.display || + BOOTSTRAPPER_RESUME_TYPE_REBOOT == m_command.resumeType; + } + + if (fApply) { ::PostMessageW(m_hWnd, WM_WIXSTDBA_APPLY_PACKAGES, 0, 0); } + LExit: + return hr; + } + + + virtual STDMETHODIMP OnApplyBegin( + __in DWORD dwPhaseCount, + __in BOOL* pfCancel + ) + { m_fStartedExecution = FALSE; m_dwCalculatedCacheProgress = 0; m_dwCalculatedExecuteProgress = 0; - return hr; + return __super::OnApplyBegin(dwPhaseCount, pfCancel); } @@ -2149,6 +2243,7 @@ private: BOOL fRet = FALSE; MSG msg = { }; DWORD dwQuit = 0; + WM_WIXSTDBA firstAction = WM_WIXSTDBA_DETECT_PACKAGES; // Initialize COM and theme. hr = ::CoInitialize(NULL); @@ -2169,15 +2264,21 @@ private: if (FAILED(pThis->m_hrFinal)) { pThis->SetState(WIXSTDBA_STATE_FAILED, hr); - ::PostMessageW(pThis->m_hWnd, WM_WIXSTDBA_SHOW_FAILURE, 0, 0); + firstAction = WM_WIXSTDBA_SHOW_FAILURE; } else { // Okay, we're ready for packages now. pThis->SetState(WIXSTDBA_STATE_INITIALIZED, hr); - ::PostMessageW(pThis->m_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->m_command.action ? WM_WIXSTDBA_SHOW_HELP : WM_WIXSTDBA_DETECT_PACKAGES, 0, 0); + + if (!pThis->m_fPreplanPrereqs && BOOTSTRAPPER_ACTION_HELP == pThis->m_command.action) + { + firstAction = WM_WIXSTDBA_SHOW_HELP; + } } + ::PostMessageW(pThis->m_hWnd, firstAction, 0, 0); + // message pump while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) { @@ -2647,7 +2748,7 @@ private: // Calculate the window style based on the theme style and command display value. dwWindowStyle = m_pTheme->dwStyle; - if (BOOTSTRAPPER_DISPLAY_NONE >= m_command.display) + if (BOOTSTRAPPER_DISPLAY_NONE >= m_command.display || m_fPreplanPrereqs) { dwWindowStyle &= ~WS_VISIBLE; } @@ -2850,6 +2951,10 @@ private: pBA->OnShowFailure(); return 0; + case WM_WIXSTDBA_PLAN_PREREQS: + pBA->OnPlanPrereqs(static_cast(lParam)); + return 0; + case WM_THMUTIL_CONTROL_WM_COMMAND: return pBA->OnThemeControlWmCommand(reinterpret_cast(wParam), reinterpret_cast(lParam)); @@ -3140,6 +3245,30 @@ private: } + // + // OnPlanPrereqs - preplan the packages to see if the preqba can be skipped. + // + void OnPlanPrereqs( + __in BOOTSTRAPPER_ACTION action + ) + { + HRESULT hr = S_OK; + + m_plannedAction = action; + + SetState(WIXSTDBA_STATE_PLANNING_PREREQS, hr); + + hr = m_pEngine->Plan(action); + BalExitOnFailure(hr, "Failed to start planning prereq packages."); + + LExit: + if (FAILED(hr)) + { + SetState(WIXSTDBA_STATE_PLANNING_PREREQS, hr); + } + } + + // // OnApply - apply the packages. // @@ -3838,6 +3967,8 @@ LExit: break; case WIXSTDBA_STATE_DETECTED: __fallthrough; + case WIXSTDBA_STATE_PLANNING_PREREQS: __fallthrough; + case WIXSTDBA_STATE_PLANNED_PREREQS: __fallthrough; case WIXSTDBA_STATE_PLANNING: __fallthrough; case WIXSTDBA_STATE_PLANNED: __fallthrough; case WIXSTDBA_STATE_APPLYING: __fallthrough; @@ -3874,6 +4005,8 @@ LExit: break; case WIXSTDBA_STATE_DETECTED: + case WIXSTDBA_STATE_PLANNING_PREREQS: __fallthrough; + case WIXSTDBA_STATE_PLANNED_PREREQS: __fallthrough; switch (m_command.action) { case BOOTSTRAPPER_ACTION_INSTALL: @@ -4124,7 +4257,10 @@ public: m_pPrereqData = pPrereqData; m_fPrereq = NULL != pPrereqData; + m_fPreplanPrereqs = m_fPrereq && m_pPrereqData->fAlwaysInstallPrereqs; + m_fPrereqPackagePlanned = FALSE; m_fPrereqInstalled = FALSE; + m_fPrereqSkipped = FALSE; pEngine->AddRef(); m_pEngine = pEngine; @@ -4405,7 +4541,10 @@ private: PREQBA_DATA* m_pPrereqData; BOOL m_fPrereq; + BOOL m_fPreplanPrereqs; + BOOL m_fPrereqPackagePlanned; BOOL m_fPrereqInstalled; + BOOL m_fPrereqSkipped; ITaskbarList3* m_pTaskbarList; UINT m_uTaskbarButtonCreatedMessage; diff --git a/src/ext/Bal/wixstdba/inc/preqba.h b/src/ext/Bal/wixstdba/inc/preqba.h index 93a547ed..ed339730 100644 --- a/src/ext/Bal/wixstdba/inc/preqba.h +++ b/src/ext/Bal/wixstdba/inc/preqba.h @@ -5,6 +5,7 @@ struct PREQBA_DATA { HRESULT hrHostInitialization; + BOOL fAlwaysInstallPrereqs; BOOL fCompleted; }; diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs index 1548c05b..9ca82377 100644 --- a/src/test/burn/TestBA/TestBA.cs +++ b/src/test/burn/TestBA/TestBA.cs @@ -146,6 +146,13 @@ namespace WixToolset.Test.BA this.wait.WaitOne(); + if (this.action == LaunchAction.Help) + { + this.Log("This is a BA for automated testing"); + this.Engine.Quit(0); + return; + } + this.redetectRemaining = redetectCount; for (int i = -1; i < redetectCount; i++) { diff --git a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wixproj b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wixproj index 0199f91f..b084997c 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wixproj +++ b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wixproj @@ -13,9 +13,12 @@ - + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs index 682f0bd7..6073e09f 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs @@ -1,22 +1,30 @@  - + + + + + + + - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wixproj b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wixproj index d4288d4b..843b382a 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wixproj +++ b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wixproj @@ -13,9 +13,12 @@ - + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs index 603c3aee..b7742582 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs @@ -1,21 +1,29 @@  - + + + + + + + - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wixproj b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wixproj new file mode 100644 index 00000000..81641f66 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wixproj @@ -0,0 +1,24 @@ + + + + Bundle + BrokenDncAlwaysPrereq + {D2763AB7-979B-485C-AE52-DD03C23CCB93} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs new file mode 100644 index 00000000..fe9425f7 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleC/bad.runtimeconfig.json b/src/test/burn/TestData/PrereqBaTests/BundleC/bad.runtimeconfig.json new file mode 100644 index 00000000..07a1a830 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleC/bad.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net5.5", + "rollForward": "Disable", + "framework": { + "name": "Microsoft.WindowsDesktop.App", + "version": "5.5.0" + } + } +} \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wixproj b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wixproj new file mode 100644 index 00000000..314fe2e2 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wixproj @@ -0,0 +1,24 @@ + + + + Bundle + BrokenMbaAlwaysPrereq + {415CA128-60E1-4D16-ACE8-A1D43E98B997} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs new file mode 100644 index 00000000..0e866295 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleD/bad.config b/src/test/burn/TestData/PrereqBaTests/BundleD/bad.config new file mode 100644 index 00000000..1512e59a --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleD/bad.config @@ -0,0 +1,17 @@ + + + + + + + +
+ + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wixproj b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wixproj new file mode 100644 index 00000000..5d271f55 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wixproj @@ -0,0 +1,22 @@ + + + + Bundle + DncAlwaysPrereq + {2F61ECD8-C28B-4FF9-9609-0E9633716CF9} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs new file mode 100644 index 00000000..5f2e6a75 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/PackageC/PackageC.wixproj b/src/test/burn/TestData/PrereqBaTests/PackageC/PackageC.wixproj new file mode 100644 index 00000000..1c8c4fa8 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PackageC/PackageC.wixproj @@ -0,0 +1,9 @@ + + + + {7DEEE928-CD7F-49AD-8000-2ED6339D8A78} + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/PackageF/PackageF.wixproj b/src/test/burn/TestData/PrereqBaTests/PackageF/PackageF.wixproj deleted file mode 100644 index 00ffb7d8..00000000 --- a/src/test/burn/TestData/PrereqBaTests/PackageF/PackageF.wixproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - {7DEEE928-CD7F-49AD-8000-2ED6339D8A78} - - - - - - - - \ No newline at end of file diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp new file mode 100644 index 00000000..35949eb9 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp @@ -0,0 +1,79 @@ +// 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. + +#include "precomp.h" +#include "BalBaseBAFunctions.h" +#include "BalBaseBAFunctionsProc.h" + +class CPrereqBaf : public CBalBaseBAFunctions +{ +public: // IBAFunctions + +public: //IBootstrapperApplication + + virtual STDMETHODIMP OnDetectBegin( + __in BOOL /*fCached*/, + __in BOOTSTRAPPER_REGISTRATION_TYPE /*registrationType*/, + __in DWORD /*cPackages*/, + __inout BOOL* /*pfCancel*/ + ) + { + HRESULT hr = S_OK; + + hr = m_pEngine->SetVariableString(L"BARuntimeDirectory", m_command.wzBootstrapperWorkingFolder, FALSE); + ExitOnFailure(hr, "Failed to set BARuntimeDirectory"); + + LExit: + return hr; + } + +private: + +public: + // + // Constructor - initialize member variables. + // + CPrereqBaf( + __in HMODULE hModule, + __in IBootstrapperEngine* pEngine, + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs + ) : CBalBaseBAFunctions(hModule, pEngine, pArgs) + { + } + + // + // Destructor - release member variables. + // + ~CPrereqBaf() + { + } + +private: +}; + + +HRESULT WINAPI CreateBAFunctions( + __in HMODULE hModule, + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + CPrereqBaf* pBAFunctions = NULL; + IBootstrapperEngine* pEngine = NULL; + + hr = BalInitializeFromCreateArgs(pArgs->pBootstrapperCreateArgs, &pEngine); + ExitOnFailure(hr, "Failed to initialize Bal."); + + pBAFunctions = new CPrereqBaf(hModule, pEngine, pArgs); + ExitOnNull(pBAFunctions, hr, E_OUTOFMEMORY, "Failed to create new CPrereqBaf object."); + + pResults->pfnBAFunctionsProc = BalBaseBAFunctionsProc; + pResults->pvBAFunctionsProcContext = pBAFunctions; + pBAFunctions = NULL; + +LExit: + ReleaseObject(pBAFunctions); + ReleaseObject(pEngine); + + return hr; +} diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.def b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.def new file mode 100644 index 00000000..6e016dad --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.def @@ -0,0 +1,6 @@ +; 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. + + +EXPORTS + BAFunctionsCreate + BAFunctionsDestroy diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.vcxproj b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.vcxproj new file mode 100644 index 00000000..0d8d63be --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.vcxproj @@ -0,0 +1,66 @@ + + + + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + {C0A11DDB-6CCE-44EC-88FD-93910C2916E3} + DynamicLibrary + Unicode + PrereqBaf + PrereqBaf.def + true + + + + + + + comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib;wininet.lib + + + + + Create + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.cpp b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.cpp new file mode 100644 index 00000000..fc9d1177 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.cpp @@ -0,0 +1,48 @@ +// 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. + +#include "precomp.h" + +static HINSTANCE vhInstance = NULL; + +extern "C" BOOL WINAPI DllMain( + IN HINSTANCE hInstance, + IN DWORD dwReason, + IN LPVOID /* pvReserved */ + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ::DisableThreadLibraryCalls(hInstance); + vhInstance = hInstance; + break; + + case DLL_PROCESS_DETACH: + vhInstance = NULL; + break; + } + + return TRUE; +} + +extern "C" HRESULT WINAPI BAFunctionsCreate( + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + + hr = CreateBAFunctions(vhInstance, pArgs, pResults); + BalExitOnFailure(hr, "Failed to create BAFunctions interface."); + +LExit: + return hr; +} + +extern "C" void WINAPI BAFunctionsDestroy( + __in const BA_FUNCTIONS_DESTROY_ARGS* /*pArgs*/, + __inout BA_FUNCTIONS_DESTROY_RESULTS* /*pResults*/ + ) +{ + BalUninitialize(); +} diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.h b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.h new file mode 100644 index 00000000..8320bdd8 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/precomp.h @@ -0,0 +1,31 @@ +#pragma once +// 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. + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dutil.h" +#include "dictutil.h" +#include "fileutil.h" +#include "locutil.h" +#include "pathutil.h" +#include "strutil.h" + +#include "BalBaseBootstrapperApplication.h" +#include "balutil.h" + +#include "BAFunctions.h" +#include "IBAFunctions.h" + +HRESULT WINAPI CreateBAFunctions( + __in HMODULE hModule, + __in const BA_FUNCTIONS_CREATE_ARGS* pArgs, + __inout BA_FUNCTIONS_CREATE_RESULTS* pResults + ); diff --git a/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.cpp b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.cpp new file mode 100644 index 00000000..1fa71bc2 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.cpp @@ -0,0 +1,33 @@ +// 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. + +#include "precomp.h" + +int __cdecl wmain( + __in int argc, + __in LPWSTR argv[] + ) +{ + HRESULT hr = S_OK; + DWORD dwExitCode = 0; + LPCWSTR wzDestinationFile = argc > 1 ? argv[1] : NULL; + LPCWSTR wzGoodFile = argc > 2 ? argv[2] : NULL; + LPCWSTR wzBadFile = argc > 3 ? argv[3] : NULL; + + if (argc != 4) + { + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid args"); + } + + if (!::MoveFileW(wzDestinationFile, wzBadFile)) + { + ExitWithLastError(hr, "Failed to move bad file"); + } + + if (!::MoveFileW(wzGoodFile, wzDestinationFile)) + { + ExitWithLastError(hr, "Failed to move good file"); + } + +LExit: + return FAILED(hr) ? (int)hr : (int)dwExitCode; +} diff --git a/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.vcxproj b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.vcxproj new file mode 100644 index 00000000..c5d7b046 --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/ReplaceConfig.vcxproj @@ -0,0 +1,63 @@ + + + + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + {3D10A07D-3321-4F8E-B884-951F8FB5D636} + Application + Console + Unicode + ReplaceConfig + true + + + + + + + comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib;wininet.lib + + + + + Create + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.cpp b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.cpp @@ -0,0 +1,3 @@ +// 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. + +#include "precomp.h" diff --git a/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.h b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.h new file mode 100644 index 00000000..f4180c2e --- /dev/null +++ b/src/test/burn/TestData/PrereqBaTests/ReplaceConfig/precomp.h @@ -0,0 +1,17 @@ +#pragma once +// 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. + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dutil.h" +#include "fileutil.h" +#include "pathutil.h" +#include "strutil.h" diff --git a/src/test/burn/WixTestTools/BundleInstaller.cs b/src/test/burn/WixTestTools/BundleInstaller.cs index 2b449ebf..0ab02d1b 100644 --- a/src/test/burn/WixTestTools/BundleInstaller.cs +++ b/src/test/burn/WixTestTools/BundleInstaller.cs @@ -26,6 +26,20 @@ namespace WixTestTools public string TestName { get; } + /// + /// Runs the bundle asking for help. + /// + /// Expected exit code, defaults to success. + /// Optional arguments to pass to the tool. + /// Path to the generated log file. + public string Help(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments) + { + var newArgumentList = new List(); + newArgumentList.Add("-help"); + newArgumentList.AddRange(arguments); + return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Custom, newArgumentList.ToArray()); + } + /// /// Installs the bundle with optional arguments. /// diff --git a/src/test/burn/WixToolsetTest.BurnE2E/PrereqBaTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/PrereqBaTests.cs index 52e165b4..d958b454 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/PrereqBaTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/PrereqBaTests.cs @@ -14,6 +14,39 @@ namespace WixToolsetTest.BurnE2E const int E_PREREQBA_INFINITE_LOOP = -2_114_714_646; + /// + /// This bundle purposely provides a .runtimeconfig.json file that requires a version of .NET Core that doesn't exist, + /// with an MSI package to represent the prerequisite package. + /// This verifies that: + /// The preqba doesn't infinitely try to install prereqs. + /// The engine automatically uninstalls the bundle since only permanent packages were installed. + /// + [RuntimeFact] + public void DncAlwaysPreqBaDetectsInfiniteLoop() + { + var packageA = this.CreatePackageInstaller("PackageA"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleC = this.CreateBundleInstaller("BundleC"); + + var packageASourceCodeInstalled = packageA.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleC.Install(E_PREREQBA_INFINITE_LOOP, "CAUSEINFINITELOOP=1"); + + // Part of the test is Install actually completing. + + // Source file should be installed + Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled)); + packageC.VerifyInstalled(false); + + // No non-permanent packages should have ended up installed or cached so it should have unregistered. + bundleC.VerifyUnregisteredAndRemovedFromPackageCache(); + } + /// /// This bundle purposely provides a .runtimeconfig.json file that requires a version of .NET Core that doesn't exist, /// with an MSI package to represent the prerequisite package. @@ -25,7 +58,7 @@ namespace WixToolsetTest.BurnE2E public void DncPreqBaDetectsInfiniteLoop() { var packageA = this.CreatePackageInstaller("PackageA"); - this.CreatePackageInstaller("PackageF"); + var packageC = this.CreatePackageInstaller("PackageC"); var bundleA = this.CreateBundleInstaller("BundleA"); @@ -33,18 +66,131 @@ namespace WixToolsetTest.BurnE2E // Source file should *not* be installed Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}"); + packageC.VerifyInstalled(false); - bundleA.Install(E_PREREQBA_INFINITE_LOOP); + bundleA.Install(E_PREREQBA_INFINITE_LOOP, "CAUSEINFINITELOOP=1"); // Part of the test is Install actually completing. // Source file should be installed Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled)); + packageC.VerifyInstalled(false); // No non-permanent packages should have ended up installed or cached so it should have unregistered. bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); } + /// + /// This bundle purposely provides a .runtimeconfig.json file that requires a version of .NET Core that doesn't exist, + /// with an EXE prereq package to swap it out with a good one. + /// This verifies that: + /// The preqba doesn't infinitely try to install prereqs. + /// The managed BA gets loaded after installing prereqs. + /// + [RuntimeFact] + public void DncAlwaysPreqBaLoadsManagedBaAfterInstallingPrereqs() + { + var packageA = this.CreatePackageInstaller("PackageA"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleC = this.CreateBundleInstaller("BundleC"); + + var packageASourceCodeInstalled = packageA.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleC.Install(); + + // Source file should be installed + Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled)); + packageC.VerifyInstalled(true); + + bundleC.VerifyRegisteredAndInPackageCache(); + + bundleC.Uninstall(); + + bundleC.VerifyUnregisteredAndRemovedFromPackageCache(); + } + + /// + /// This bundle purposely provides a .runtimeconfig.json file that requires a version of .NET Core that doesn't exist, + /// with an EXE prereq package to swap it out with a good one. + /// This verifies that: + /// The preqba doesn't infinitely reload itself after failing to load the managed BA. + /// The managed BA gets loaded after installing prereqs. + /// + [RuntimeFact] + public void DncPreqBaLoadsManagedBaAfterInstallingPrereqs() + { + var packageA = this.CreatePackageInstaller("PackageA"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleA = this.CreateBundleInstaller("BundleA"); + + var packageASourceCodeInstalled = packageA.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleA.Install(); + + // Source file should be installed + Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled)); + packageC.VerifyInstalled(true); + + bundleA.VerifyRegisteredAndInPackageCache(); + + bundleA.Uninstall(); + + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + } + + [RuntimeFact] + public void DncAlwaysPreqBaForwardsHelpToManagedBa() + { + var bundleE = this.CreateBundleInstaller("BundleE"); + + var bundleLog = bundleE.Help(); + + Assert.True(LogVerifier.MessageInLogFile(bundleLog, "This is a BA for automated testing")); + } + + /// + /// This bundle purposely provides a WixToolset.Mba.Host.config file that requires a version of .NET Framework that doesn't exist, + /// with an MSI package to represent the prerequisite package. + /// This verifies that: + /// The preqba doesn't infinitely try to install prereqs. + /// The engine automatically uninstalls the bundle since only permanent packages were installed. + /// + [RuntimeFact] + public void MbaAlwaysPreqBaDetectsInfiniteLoop() + { + var packageB = this.CreatePackageInstaller("PackageB"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleD = this.CreateBundleInstaller("BundleD"); + + var packageBSourceCodeInstalled = packageB.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageBSourceCodeInstalled), $"Package B payload should not be there on test start: {packageBSourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleD.Install(E_PREREQBA_INFINITE_LOOP, "CAUSEINFINITELOOP=1"); + + // Part of the test is Install actually completing. + + // Source file should be installed + Assert.True(File.Exists(packageBSourceCodeInstalled), String.Concat("Should have found Package B payload installed at: ", packageBSourceCodeInstalled)); + packageC.VerifyInstalled(false); + + // No non-permanent packages should have ended up installed or cached so it should have unregistered. + bundleD.VerifyUnregisteredAndRemovedFromPackageCache(); + } + /// /// This bundle purposely provides a WixToolset.Mba.Host.config file that requires a version of .NET Framework that doesn't exist, /// with an MSI package to represent the prerequisite package. @@ -56,7 +202,7 @@ namespace WixToolsetTest.BurnE2E public void MbaPreqBaDetectsInfiniteLoop() { var packageB = this.CreatePackageInstaller("PackageB"); - this.CreatePackageInstaller("PackageF"); + var packageC = this.CreatePackageInstaller("PackageC"); var bundleB = this.CreateBundleInstaller("BundleB"); @@ -64,13 +210,84 @@ namespace WixToolsetTest.BurnE2E // Source file should *not* be installed Assert.False(File.Exists(packageBSourceCodeInstalled), $"Package B payload should not be there on test start: {packageBSourceCodeInstalled}"); + packageC.VerifyInstalled(false); - bundleB.Install(E_PREREQBA_INFINITE_LOOP); + bundleB.Install(E_PREREQBA_INFINITE_LOOP, "CAUSEINFINITELOOP=1"); // Part of the test is Install actually completing. // Source file should be installed Assert.True(File.Exists(packageBSourceCodeInstalled), String.Concat("Should have found Package B payload installed at: ", packageBSourceCodeInstalled)); + packageC.VerifyInstalled(false); + + // No non-permanent packages should have ended up installed or cached so it should have unregistered. + bundleB.VerifyUnregisteredAndRemovedFromPackageCache(); + } + + /// + /// This bundle purposely provides a WixToolset.Mba.Host.config file that requires a version of .NET Framework that doesn't exist, + /// with an EXE prereq package to swap it out with a good one. + /// This verifies that: + /// The preqba doesn't infinitely try to install prereqs. + /// The managed BA gets loaded after installing prereqs. + /// + [RuntimeFact] + public void MbaAlwaysPreqBaLoadsManagedBaAfterInstallingPrereqs() + { + var packageB = this.CreatePackageInstaller("PackageB"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleD = this.CreateBundleInstaller("BundleD"); + + var packageBSourceCodeInstalled = packageB.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageBSourceCodeInstalled), $"Package B payload should not be there on test start: {packageBSourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleD.Install(); + + // Source file should be installed + Assert.True(File.Exists(packageBSourceCodeInstalled), String.Concat("Should have found Package B payload installed at: ", packageBSourceCodeInstalled)); + packageC.VerifyInstalled(true); + + bundleD.VerifyRegisteredAndInPackageCache(); + + bundleD.Uninstall(); + + bundleD.VerifyUnregisteredAndRemovedFromPackageCache(); + } + + /// + /// This bundle purposely provides a WixToolset.Mba.Host.config file that requires a version of .NET Framework that doesn't exist, + /// with an EXE prereq package to swap it out with a good one. + /// This verifies that: + /// The preqba doesn't infinitely reload itself after failing to load the managed BA. + /// The managed BA gets loaded after installing prereqs. + /// + [RuntimeFact] + public void MbaPreqBaLoadsManagedBaAfterInstallingPrereqs() + { + var packageB = this.CreatePackageInstaller("PackageB"); + var packageC = this.CreatePackageInstaller("PackageC"); + + var bundleB = this.CreateBundleInstaller("BundleB"); + + var packageBSourceCodeInstalled = packageB.GetInstalledFilePath("Package.wxs"); + + // Source file should *not* be installed + Assert.False(File.Exists(packageBSourceCodeInstalled), $"Package B payload should not be there on test start: {packageBSourceCodeInstalled}"); + packageC.VerifyInstalled(false); + + bundleB.Install(); + + // Source file should be installed + Assert.True(File.Exists(packageBSourceCodeInstalled), String.Concat("Should have found Package B payload installed at: ", packageBSourceCodeInstalled)); + packageC.VerifyInstalled(true); + + bundleB.VerifyRegisteredAndInPackageCache(); + + bundleB.Uninstall(); // No non-permanent packages should have ended up installed or cached so it should have unregistered. bundleB.VerifyUnregisteredAndRemovedFromPackageCache(); -- cgit v1.2.3-55-g6feb