From 913b6238417dceeb8440315e4669990756d17655 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 19 Jul 2022 15:17:10 -0500 Subject: Add WixInternalUIBootstrapperApplication as a new built-in BA. Implements 6835 --- src/ext/Bal/Bal.wixext.sln | 18 + src/ext/Bal/bal.cmd | 8 - src/ext/Bal/dnchost/dnchost.cpp | 8 +- src/ext/Bal/mbahost/mbahost.cpp | 8 +- .../test/WixToolsetTest.Bal/BalExtensionFixture.cs | 6 + .../test/WixToolsetTest.Bal/InternalUIBAFixture.cs | 474 +++++++++++ .../TestData/WixIuiBa/AllPrereqPackages.wxs | 13 + .../WixIuiBa/ImplicitNonMsiPrimaryPackage.wxs | 13 + .../TestData/WixIuiBa/ImplicitPrimaryPackage.wxs | 13 + ...mplicitPrimaryPackageEnableFeatureSelection.wxs | 13 + .../TestData/WixIuiBa/IuibaWarnings.wxs | 13 + .../WixIuiBa/MultipleDefaultPrimaryPackages.wxs | 13 + .../MultipleNonPermanentNonPrimaryPackages.wxs | 13 + .../TestData/WixIuiBa/NoDefaultPrimaryPackage.wxs | 13 + .../TestData/WixIuiBa/NonMsiPrimaryPackage.wxs | 13 + .../WixIuiBa/NonPermanentPrereqPackage.wxs | 13 + .../TestData/WixIuiBa/PermanentPrimaryPackage.wxs | 13 + .../PrimaryPackageEnableFeatureSelection.wxs | 13 + .../TestData/WixIuiBa/PrimaryPrereqPackage.wxs | 13 + .../TestData/WixIuiBa/SinglePrimaryPackage.wxs | 13 + src/ext/Bal/wixext/BalBurnBackendExtension.cs | 294 ++++++- src/ext/Bal/wixext/BalCompiler.cs | 269 ++++-- src/ext/Bal/wixext/BalErrors.cs | 48 ++ src/ext/Bal/wixext/BalWarnings.cs | 24 + .../Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs | 17 + .../WixInternalUIBootstrapperApplication.cpp | 918 +++++++++++++++++++++ .../WixInternalUIBootstrapperApplication.h | 18 + src/ext/Bal/wixiuiba/precomp.cpp | 3 + src/ext/Bal/wixiuiba/precomp.h | 30 + src/ext/Bal/wixiuiba/wixiuiba.cpp | 192 +++++ src/ext/Bal/wixiuiba/wixiuiba.def | 6 + src/ext/Bal/wixiuiba/wixiuiba.h | 13 + src/ext/Bal/wixiuiba/wixiuiba.vcxproj | 72 ++ src/ext/Bal/wixlib/BalExtension_platform.wxi | 14 + src/ext/Bal/wixlib/bal.wixproj | 3 + src/ext/Bal/wixlib/wixiuiba.wxs | 12 + src/ext/Bal/wixstdba/Resources/iuipreq.thm | 67 ++ src/ext/Bal/wixstdba/Resources/iuipreq.wxl | 34 + .../WixStandardBootstrapperApplication.cpp | 41 +- src/ext/Bal/wixstdba/inc/preqba.h | 4 +- 40 files changed, 2675 insertions(+), 108 deletions(-) create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/InternalUIBAFixture.cs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/AllPrereqPackages.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitNonMsiPrimaryPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackageEnableFeatureSelection.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/IuibaWarnings.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleDefaultPrimaryPackages.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleNonPermanentNonPrimaryPackages.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NoDefaultPrimaryPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonMsiPrimaryPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonPermanentPrereqPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PermanentPrimaryPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPackageEnableFeatureSelection.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPrereqPackage.wxs create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/SinglePrimaryPackage.wxs create mode 100644 src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.cpp create mode 100644 src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.h create mode 100644 src/ext/Bal/wixiuiba/precomp.cpp create mode 100644 src/ext/Bal/wixiuiba/precomp.h create mode 100644 src/ext/Bal/wixiuiba/wixiuiba.cpp create mode 100644 src/ext/Bal/wixiuiba/wixiuiba.def create mode 100644 src/ext/Bal/wixiuiba/wixiuiba.h create mode 100644 src/ext/Bal/wixiuiba/wixiuiba.vcxproj create mode 100644 src/ext/Bal/wixlib/wixiuiba.wxs create mode 100644 src/ext/Bal/wixstdba/Resources/iuipreq.thm create mode 100644 src/ext/Bal/wixstdba/Resources/iuipreq.wxl (limited to 'src/ext') diff --git a/src/ext/Bal/Bal.wixext.sln b/src/ext/Bal/Bal.wixext.sln index 9f77d79e..be7149f4 100644 --- a/src/ext/Bal/Bal.wixext.sln +++ b/src/ext/Bal/Bal.wixext.sln @@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bafunctions", "Samples\bafu EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mbahost", "mbahost\mbahost.vcxproj", "{12C87C77-3547-44F8-8134-29BC915CB19D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wixiuiba", "wixiuiba\wixiuiba.vcxproj", "{0F73E566-925C-448D-99CB-3A7F5DF399C8}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wixstdba", "wixstdba\wixstdba.vcxproj", "{41085A22-E6AA-4E8B-AB1B-DDEE0DC89DFA}" EndProject Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "bal", "wixlib\bal.wixproj", "{3444D952-F21C-496F-AB6B-56435BFD0787}" @@ -83,6 +85,22 @@ Global {12C87C77-3547-44F8-8134-29BC915CB19D}.Release|x64.Build.0 = Release|x64 {12C87C77-3547-44F8-8134-29BC915CB19D}.Release|x86.ActiveCfg = Release|Win32 {12C87C77-3547-44F8-8134-29BC915CB19D}.Release|x86.Build.0 = Release|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|Any CPU.Build.0 = Debug|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|ARM64.Build.0 = Debug|ARM64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|x64.ActiveCfg = Debug|x64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|x64.Build.0 = Debug|x64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|x86.ActiveCfg = Debug|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Debug|x86.Build.0 = Debug|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|Any CPU.ActiveCfg = Release|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|Any CPU.Build.0 = Release|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|ARM64.ActiveCfg = Release|ARM64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|ARM64.Build.0 = Release|ARM64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|x64.ActiveCfg = Release|x64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|x64.Build.0 = Release|x64 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|x86.ActiveCfg = Release|Win32 + {0F73E566-925C-448D-99CB-3A7F5DF399C8}.Release|x86.Build.0 = Release|Win32 {41085A22-E6AA-4E8B-AB1B-DDEE0DC89DFA}.Debug|Any CPU.ActiveCfg = Debug|Win32 {41085A22-E6AA-4E8B-AB1B-DDEE0DC89DFA}.Debug|Any CPU.Build.0 = Debug|Win32 {41085A22-E6AA-4E8B-AB1B-DDEE0DC89DFA}.Debug|ARM64.ActiveCfg = Debug|ARM64 diff --git a/src/ext/Bal/bal.cmd b/src/ext/Bal/bal.cmd index 7a005cfe..aedf9b64 100644 --- a/src/ext/Bal/bal.cmd +++ b/src/ext/Bal/bal.cmd @@ -14,14 +14,6 @@ nuget restore dnchost\packages.config || exit /b msbuild -t:Restore -p:Configuration=%_C% || exit /b :: Build -msbuild -p:Configuration=%_C%;Platform=x86 dnchost\dnchost.vcxproj || exit /b -msbuild -p:Configuration=%_C%;Platform=x64 dnchost\dnchost.vcxproj || exit /b -msbuild -p:Configuration=%_C%;Platform=ARM64 dnchost\dnchost.vcxproj || exit /b - -msbuild -p:Configuration=%_C%;Platform=x86 mbahost\mbahost.vcxproj || exit /b -msbuild -p:Configuration=%_C%;Platform=x64 mbahost\mbahost.vcxproj || exit /b -msbuild -p:Configuration=%_C%;Platform=ARM64 mbahost\mbahost.vcxproj || exit /b - msbuild -p:Configuration=%_C%;Platform=x86 test\examples\TestEngine\Example.TestEngine.vcxproj || exit /b msbuild -p:Configuration=%_C%;Platform=x64 test\examples\TestEngine\Example.TestEngine.vcxproj || exit /b msbuild -p:Configuration=%_C%;Platform=ARM64 test\examples\TestEngine\Example.TestEngine.vcxproj || exit /b diff --git a/src/ext/Bal/dnchost/dnchost.cpp b/src/ext/Bal/dnchost/dnchost.cpp index cdf204fb..8faf292c 100644 --- a/src/ext/Bal/dnchost/dnchost.cpp +++ b/src/ext/Bal/dnchost/dnchost.cpp @@ -110,18 +110,18 @@ extern "C" HRESULT WINAPI BootstrapperApplicationCreate( { if (DNCHOSTTYPE_SCD == vstate.type) { - vstate.prereqData.hrHostInitialization = E_DNCHOST_SCD_RUNTIME_FAILURE; + vstate.prereqData.hrFatalError = E_DNCHOST_SCD_RUNTIME_FAILURE; BalLogError(hr, "The self-contained .NET Core runtime failed to load. This is an unrecoverable error."); } else if (vstate.prereqData.fCompleted) { hr = E_PREREQBA_INFINITE_LOOP; BalLogError(hr, "The prerequisites were already installed. The bootstrapper application will not be reloaded to prevent an infinite loop."); - vstate.prereqData.hrHostInitialization = hr; + vstate.prereqData.hrFatalError = hr; } else { - vstate.prereqData.hrHostInitialization = S_OK; + vstate.prereqData.hrFatalError = S_OK; } BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application because .NET Core host could not be loaded, error: 0x%08x.", hr); @@ -233,6 +233,8 @@ static HRESULT LoadDncConfiguration( BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get AlwaysInstallPrereqs value."); } + pState->prereqData.fPerformHelp = !pState->prereqData.fAlwaysInstallPrereqs; + 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 0b89fc58..5edbe376 100644 --- a/src/ext/Bal/mbahost/mbahost.cpp +++ b/src/ext/Bal/mbahost/mbahost.cpp @@ -144,17 +144,17 @@ extern "C" HRESULT WINAPI BootstrapperApplicationCreate( if (E_MBAHOST_NET452_ON_WIN7RTM == hr) { BalLogError(hr, "The Burn engine cannot run with an MBA under the .NET 4 CLR on Windows 7 RTM with .NET 4.5.2 (or greater) installed."); - vstate.prereqData.hrHostInitialization = hr; + vstate.prereqData.hrFatalError = hr; } else if (vstate.prereqData.fCompleted) { hr = E_PREREQBA_INFINITE_LOOP; BalLogError(hr, "The prerequisites were already installed. The bootstrapper application will not be reloaded to prevent an infinite loop."); - vstate.prereqData.hrHostInitialization = hr; + vstate.prereqData.hrFatalError = hr; } else { - vstate.prereqData.hrHostInitialization = S_OK; + vstate.prereqData.hrFatalError = S_OK; } BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application because managed host could not be loaded, error: 0x%08x.", hr); @@ -306,6 +306,8 @@ static HRESULT LoadMbaConfiguration( BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get AlwaysInstallPrereqs value."); } + pState->prereqData.fPerformHelp = !pState->prereqData.fAlwaysInstallPrereqs; + LExit: ReleaseObject(pixnHost); ReleaseObject(pixdManifest); diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs index d8197467..79e96316 100644 --- a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs +++ b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs @@ -143,6 +143,12 @@ namespace WixToolsetTest.Bal { "", }, wixMbaPrereqOptionsElements); + + var wixMbaPrereqInformationElements = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixMbaPrereqInformation"); + WixAssert.CompareLineByLine(new[] + { + "", + }, wixMbaPrereqInformationElements); } } diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/InternalUIBAFixture.cs b/src/ext/Bal/test/WixToolsetTest.Bal/InternalUIBAFixture.cs new file mode 100644 index 00000000..dba78da4 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/InternalUIBAFixture.cs @@ -0,0 +1,474 @@ +// 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 WixToolsetTest.Bal +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class InternalUIBAFixture + { + [Fact] + public void CanBuildUsingWixIuiBa() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + 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, "SinglePrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + + Assert.True(File.Exists(bundleFile)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var balPackageInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixBalPackageInfo"); + WixAssert.CompareLineByLine(new string[] + { + "", + }, balPackageInfos); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.thm"))); + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.wxl"))); + } + } + + [Fact] + public void CanBuildUsingWixIuiBaWithImplicitPrimaryPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + 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, "ImplicitPrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + + Assert.True(File.Exists(bundleFile)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var balPackageInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixBalPackageInfo"); + WixAssert.CompareLineByLine(new string[] + { + "", + }, balPackageInfos); + + var mbaPrereqInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixMbaPrereqInformation"); + WixAssert.CompareLineByLine(new[] + { + "", + }, mbaPrereqInfos); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.thm"))); + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.wxl"))); + } + } + + [Fact] + public void CanBuildUsingWixIuiBaWithWarnings() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var compileResult = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(bundleSourceFolder, "IuiBaWarnings.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "WixInternalUIBootstrapperApplication does not support the value of 'force' for Cache on prereq packages. Prereq packages are only cached when they need to be installed.", + "WixInternalUIBootstrapperApplication ignores InstallCondition for the primary package so that the MSI UI is always shown.", + "WixInternalUIBootstrapperApplication ignores DisplayInternalUICondition for the primary package so that the MSI UI is always shown.", + "When using WixInternalUIBootstrapperApplication, all prereq packages should be before the primary package in the chain. The prereq packages are always installed before the primary package.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + compileResult.AssertSuccess(); + + Assert.True(File.Exists(bundleFile)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var balPackageInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixBalPackageInfo"); + WixAssert.CompareLineByLine(new string[] + { + "", + }, balPackageInfos); + + var mbaPrereqInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixMbaPrereqInformation"); + WixAssert.CompareLineByLine(new[] + { + "", + }, mbaPrereqInfos); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.thm"))); + Assert.True(File.Exists(Path.Combine(baFolderPath, "mbapreq.wxl"))); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithAllPrereqPackages() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "AllPrereqPackages.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, there must be one package with bal:PrimaryPackageType=\"default\".", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6808, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithImplicitNonMsiPrimaryPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "ImplicitNonMsiPrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, packages must either be non-permanent and have the bal:PrimaryPackageType attribute, or be permanent and have the bal:PrereqPackage attribute set to 'yes'.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6811, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithImplicitPrimaryPackageEnableFeatureSelection() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "ImplicitPrimaryPackageEnableFeatureSelection.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, packages must either be non-permanent and have the bal:PrimaryPackageType attribute, or be permanent and have the bal:PrereqPackage attribute set to 'yes'.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6811, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithMultipleNonPermanentNonPrimaryPackages() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "MultipleNonPermanentNonPrimaryPackages.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, packages must either be non-permanent and have the bal:PrimaryPackageType attribute, or be permanent and have the bal:PrereqPackage attribute set to 'yes'.", + "When using WixInternalUIBootstrapperApplication, packages must either be non-permanent and have the bal:PrimaryPackageType attribute, or be permanent and have the bal:PrereqPackage attribute set to 'yes'.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6811, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithMultiplePrimaryPackagesOfSameType() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "MultipleDefaultPrimaryPackages.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "There may only be one package in the bundle with PrimaryPackageType of 'default'.", + "The location of the package related to the previous error.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6810, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithNoDefaultPrimaryPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "NoDefaultPrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, there must be one package with bal:PrimaryPackageType=\"default\".", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6808, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithNonMsiPrimaryPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "NonMsiPrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, each primary package must be an MsiPackage.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6814, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithNonPermanentPrereqPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "NonPermanentPrereqPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication and bal:PrereqPackage is set to 'yes', the package must be permanent.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6812, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithPermanentPrimaryPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "PermanentPrimaryPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, packages with the bal:PrimaryPackageType attribute must not be permanent.", + "When using WixInternalUIBootstrapperApplication, there must be one package with bal:PrimaryPackageType=\"default\".", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6808, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithPrimaryPackageEnableFeatureSelection() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "PrimaryPackageEnableFeatureSelection.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", TestData.Get(@"TestData\WixStdBa\Data"), + "-o", bundleFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "When using WixInternalUIBootstrapperApplication, primary packages must not have feature selection enabled because it interferes with the user selecting feature through the MSI UI.", + "When using WixInternalUIBootstrapperApplication, there must be one package with bal:PrimaryPackageType=\"default\".", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(6808, compileResult.ExitCode); + } + } + + [Fact] + public void CannotBuildUsingWixIuiBaWithPrimaryPrereqPackage() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var wixlibFile = Path.Combine(baseFolder, "bin", "test.wixlib"); + var bundleSourceFolder = TestData.Get(@"TestData\WixIuiBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "PrimaryPrereqPackage.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibFile, + }); + + WixAssert.CompareLineByLine(new[] + { + "The MsiPackage/@PrereqPackage attribute's value, 'yes', cannot be specified with attribute PrimaryPackageType present.", + }, compileResult.Messages.Select(m => m.ToString()).ToArray()); + + Assert.Equal(193, compileResult.ExitCode); + } + } + } +} diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/AllPrereqPackages.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/AllPrereqPackages.wxs new file mode 100644 index 00000000..17f1ee77 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/AllPrereqPackages.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitNonMsiPrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitNonMsiPrimaryPackage.wxs new file mode 100644 index 00000000..ca1f9358 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitNonMsiPrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackage.wxs new file mode 100644 index 00000000..16a99e92 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackageEnableFeatureSelection.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackageEnableFeatureSelection.wxs new file mode 100644 index 00000000..85b9df65 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/ImplicitPrimaryPackageEnableFeatureSelection.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/IuibaWarnings.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/IuibaWarnings.wxs new file mode 100644 index 00000000..2cf9787d --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/IuibaWarnings.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleDefaultPrimaryPackages.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleDefaultPrimaryPackages.wxs new file mode 100644 index 00000000..11736fbb --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleDefaultPrimaryPackages.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleNonPermanentNonPrimaryPackages.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleNonPermanentNonPrimaryPackages.wxs new file mode 100644 index 00000000..c5b923df --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/MultipleNonPermanentNonPrimaryPackages.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NoDefaultPrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NoDefaultPrimaryPackage.wxs new file mode 100644 index 00000000..7f7528d0 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NoDefaultPrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonMsiPrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonMsiPrimaryPackage.wxs new file mode 100644 index 00000000..a6f93bcb --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonMsiPrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonPermanentPrereqPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonPermanentPrereqPackage.wxs new file mode 100644 index 00000000..a60943b0 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/NonPermanentPrereqPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PermanentPrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PermanentPrimaryPackage.wxs new file mode 100644 index 00000000..43caaf86 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PermanentPrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPackageEnableFeatureSelection.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPackageEnableFeatureSelection.wxs new file mode 100644 index 00000000..4f1c40dd --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPackageEnableFeatureSelection.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPrereqPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPrereqPackage.wxs new file mode 100644 index 00000000..bdb8c470 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/PrimaryPrereqPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/SinglePrimaryPackage.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/SinglePrimaryPackage.wxs new file mode 100644 index 00000000..1e9a87c2 --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/WixIuiBa/SinglePrimaryPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/wixext/BalBurnBackendExtension.cs b/src/ext/Bal/wixext/BalBurnBackendExtension.cs index d34c159a..6f615796 100644 --- a/src/ext/Bal/wixext/BalBurnBackendExtension.cs +++ b/src/ext/Bal/wixext/BalBurnBackendExtension.cs @@ -5,6 +5,8 @@ namespace WixToolset.Bal using System; using System.Collections.Generic; using System.Linq; + using System.Text; + using System.Xml; using WixToolset.Bal.Symbols; using WixToolset.Data; using WixToolset.Data.Burn; @@ -29,12 +31,48 @@ namespace WixToolset.Bal protected override IReadOnlyCollection SymbolDefinitions => BurnSymbolDefinitions; + public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol) + { + if (symbol is WixBalPackageInfoSymbol balPackageInfoSymbol) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're creating valid XML. + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb)) + { + writer.WriteStartElement(symbol.Definition.Name, BurnConstants.BootstrapperApplicationDataNamespace); + + writer.WriteAttributeString("PackageId", balPackageInfoSymbol.PackageId); + + if (balPackageInfoSymbol.DisplayInternalUICondition != null) + { + writer.WriteAttributeString("DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition); + } + + if (balPackageInfoSymbol.PrimaryPackageType != BalPrimaryPackageType.None) + { + writer.WriteAttributeString("PrimaryPackageType", balPackageInfoSymbol.PrimaryPackageType.ToString().ToLower()); + } + + writer.WriteEndElement(); + } + + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + + return true; + } + else + { + return base.TryProcessSymbol(section, symbol); + } + } + public override void SymbolsFinalized(IntermediateSection section) { base.SymbolsFinalized(section); this.VerifyBalConditions(section); - this.VerifyBalPackageInfos(section); + this.VerifyDisplayInternalUICondition(section); this.VerifyOverridableVariables(section); var baSymbol = section.Symbols.OfType().SingleOrDefault(); @@ -44,24 +82,31 @@ namespace WixToolset.Bal return; } + var isIuiBA = baId.StartsWith("WixInternalUIBootstrapperApplication"); var isStdBA = baId.StartsWith("WixStandardBootstrapperApplication"); var isMBA = baId.StartsWith("WixManagedBootstrapperApplicationHost"); var isDNC = baId.StartsWith("WixDotNetCoreBootstrapperApplicationHost"); var isSCD = isDNC && this.VerifySCD(section); + if (isIuiBA) + { + // This needs to happen before VerifyPrereqPackages because it can add prereq packages. + this.VerifyPrimaryPackages(section); + } + if (isDNC) { this.FinalizeBAFactorySymbol(section); } - if (isStdBA || isMBA || isDNC) + if (isIuiBA || isStdBA || isMBA || isDNC) { this.VerifyBAFunctions(section); } - if (isMBA || (isDNC && !isSCD)) + if (isIuiBA || isMBA || (isDNC && !isSCD)) { - this.VerifyPrereqPackages(section, isDNC); + this.VerifyPrereqPackages(section, isDNC, isIuiBA); } } @@ -133,12 +178,241 @@ namespace WixToolset.Bal } } - private void VerifyBalPackageInfos(IntermediateSection section) + private void VerifyDisplayInternalUICondition(IntermediateSection section) + { + foreach (var balPackageInfoSymbol in section.Symbols.OfType().ToList()) + { + if (balPackageInfoSymbol.DisplayInternalUICondition != null) + { + this.BackendHelper.ValidateBundleCondition(balPackageInfoSymbol.SourceLineNumbers, "*Package", "bal:DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition, BundleConditionPhase.Plan); + } + } + } + + private void VerifyPrimaryPackages(IntermediateSection section) + { + WixBalPackageInfoSymbol defaultPrimaryPackage = null; + WixBalPackageInfoSymbol x86PrimaryPackage = null; + WixBalPackageInfoSymbol x64PrimaryPackage = null; + WixBalPackageInfoSymbol arm64PrimaryPackage = null; + var nonPermanentNonPrimaryPackages = new List(); + + var balPackageInfoSymbolsByPackageId = section.Symbols.OfType().ToDictionary(x => x.PackageId); + var mbaPrereqInfoSymbolsByPackageId = section.Symbols.OfType().ToDictionary(x => x.PackageId); + var msiPackageSymbolsByPackageId = section.Symbols.OfType().ToDictionary(x => x.Id.Id); + var packageSymbols = section.Symbols.OfType().ToList(); + foreach (var packageSymbol in packageSymbols) + { + var packageId = packageSymbol.Id?.Id; + var isPrereq = false; + var primaryPackageType = BalPrimaryPackageType.None; + + if (mbaPrereqInfoSymbolsByPackageId.TryGetValue(packageId, out var _)) + { + isPrereq = true; + } + + if (balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfoSymbol)) + { + primaryPackageType = balPackageInfoSymbol.PrimaryPackageType; + } + + if (packageSymbol.Permanent) + { + if (primaryPackageType != BalPrimaryPackageType.None) + { + this.Messaging.Write(BalErrors.IuibaPermanentPrimaryPackageType(packageSymbol.SourceLineNumbers)); + } + else + { + if (!isPrereq) + { + var prereqInfoSymbol = section.AddSymbol(new WixMbaPrereqInformationSymbol(packageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Global, packageId)) + { + PackageId = packageId, + }); + + mbaPrereqInfoSymbolsByPackageId.Add(packageId, prereqInfoSymbol); + } + + this.VerifyIuibaPrereqPackage(packageSymbol); + } + } + else + { + if (isPrereq) + { + if (primaryPackageType == BalPrimaryPackageType.None) + { + this.Messaging.Write(BalErrors.IuibaNonPermanentPrereqPackage(packageSymbol.SourceLineNumbers)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute( + packageSymbol.SourceLineNumbers, + packageSymbol.Type + "Package", + "PrereqPackage", + "yes", + "PrimaryPackageType")); + } + } + else if (primaryPackageType == BalPrimaryPackageType.None) + { + nonPermanentNonPrimaryPackages.Add(packageSymbol); + } + else if (packageSymbol.Type != WixBundlePackageType.Msi) + { + this.Messaging.Write(BalErrors.IuibaNonMsiPrimaryPackage(packageSymbol.SourceLineNumbers)); + } + else if (!msiPackageSymbolsByPackageId.TryGetValue(packageId, out var msiPackageSymbol)) + { + throw new WixException($"Missing WixBundleMsiPackageSymbol for package '{packageId}'"); + } + else if (msiPackageSymbol.EnableFeatureSelection) + { + this.Messaging.Write(BalErrors.IuibaPrimaryPackageEnableFeatureSelection(packageSymbol.SourceLineNumbers)); + } + else + { + if (primaryPackageType == BalPrimaryPackageType.Default) + { + if (defaultPrimaryPackage == null) + { + defaultPrimaryPackage = balPackageInfoSymbol; + } + else + { + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "default")); + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(defaultPrimaryPackage.SourceLineNumbers)); + } + } + else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.X86) + { + if (x86PrimaryPackage == null) + { + x86PrimaryPackage = balPackageInfoSymbol; + } + else + { + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "x86")); + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(x86PrimaryPackage.SourceLineNumbers)); + } + } + else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.X64) + { + if (x64PrimaryPackage == null) + { + x64PrimaryPackage = balPackageInfoSymbol; + } + else + { + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "x64")); + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(x64PrimaryPackage.SourceLineNumbers)); + } + } + else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.ARM64) + { + if (arm64PrimaryPackage == null) + { + arm64PrimaryPackage = balPackageInfoSymbol; + } + else + { + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "arm64")); + this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(arm64PrimaryPackage.SourceLineNumbers)); + } + } + else + { + throw new NotImplementedException(); + } + + this.VerifyIuibaPrimaryPackage(packageSymbol, balPackageInfoSymbol); + } + } + } + + if (defaultPrimaryPackage == null && nonPermanentNonPrimaryPackages.Count == 1) + { + var packageSymbol = nonPermanentNonPrimaryPackages[0]; + + if (packageSymbol.Type == WixBundlePackageType.Msi) + { + var packageId = packageSymbol.Id?.Id; + var msiPackageSymbol = section.Symbols.OfType() + .SingleOrDefault(x => x.Id.Id == packageId); + if (!msiPackageSymbol.EnableFeatureSelection) + { + if (!balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfoSymbol)) + { + balPackageInfoSymbol = section.AddSymbol(new WixBalPackageInfoSymbol(packageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Global, packageId)) + { + PackageId = packageId, + }); + + balPackageInfoSymbolsByPackageId.Add(packageId, balPackageInfoSymbol); + } + + balPackageInfoSymbol.PrimaryPackageType = BalPrimaryPackageType.Default; + defaultPrimaryPackage = balPackageInfoSymbol; + nonPermanentNonPrimaryPackages.RemoveAt(0); + + this.VerifyIuibaPrimaryPackage(packageSymbol, balPackageInfoSymbol); + } + } + } + + if (nonPermanentNonPrimaryPackages.Count > 0) + { + foreach (var packageSymbol in nonPermanentNonPrimaryPackages) + { + this.Messaging.Write(BalErrors.IuibaNonPermanentNonPrimaryPackage(packageSymbol.SourceLineNumbers)); + } + } + else if (defaultPrimaryPackage == null) + { + this.Messaging.Write(BalErrors.MissingIUIPrimaryPackage()); + } + else + { + var foundPrimaryPackage = false; + var chainPackageGroupSymbols = section.Symbols.OfType() + .Where(x => x.ChildType == ComplexReferenceChildType.Package && + x.ParentType == ComplexReferenceParentType.PackageGroup && + x.ParentId == BurnConstants.BundleChainPackageGroupId); + foreach (var chainPackageGroupSymbol in chainPackageGroupSymbols) + { + var packageId = chainPackageGroupSymbol.ChildId; + if (balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfo) && balPackageInfo.PrimaryPackageType != BalPrimaryPackageType.None) + { + foundPrimaryPackage = true; + } + else if (foundPrimaryPackage && mbaPrereqInfoSymbolsByPackageId.TryGetValue(packageId, out var mbaPrereqInformationSymbol)) + { + this.Messaging.Write(BalWarnings.IuibaPrereqPackageAfterPrimaryPackage(chainPackageGroupSymbol.SourceLineNumbers)); + } + } + } + } + + private void VerifyIuibaPrereqPackage(WixBundlePackageSymbol packageSymbol) + { + if (packageSymbol.Cache == BundleCacheType.Force) + { + this.Messaging.Write(BalWarnings.IuibaForceCachePrereq(packageSymbol.SourceLineNumbers)); + } + } + + private void VerifyIuibaPrimaryPackage(WixBundlePackageSymbol packageSymbol, WixBalPackageInfoSymbol balPackageInfoSymbol) { - var balPackageInfoSymbols = section.Symbols.OfType().ToList(); - foreach (var balPackageInfoSymbol in balPackageInfoSymbols) + if (packageSymbol.InstallCondition != null) + { + this.Messaging.Write(BalWarnings.IuibaPrimaryPackageInstallCondition(packageSymbol.SourceLineNumbers)); + } + + if (balPackageInfoSymbol.DisplayInternalUICondition != null) { - this.BackendHelper.ValidateBundleCondition(balPackageInfoSymbol.SourceLineNumbers, "*Package", "bal:DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition, BundleConditionPhase.Plan); + this.Messaging.Write(BalWarnings.IuibaPrimaryPackageDisplayInternalUICondition(packageSymbol.SourceLineNumbers)); } } @@ -161,10 +435,10 @@ namespace WixToolset.Bal } } - private void VerifyPrereqPackages(IntermediateSection section, bool isDNC) + private void VerifyPrereqPackages(IntermediateSection section, bool isDNC, bool isIuiBA) { var prereqInfoSymbols = section.Symbols.OfType().ToList(); - if (prereqInfoSymbols.Count == 0) + if (!isIuiBA && prereqInfoSymbols.Count == 0) { var message = isDNC ? BalErrors.MissingDNCPrereq() : BalErrors.MissingMBAPrereq(); this.Messaging.Write(message); diff --git a/src/ext/Bal/wixext/BalCompiler.cs b/src/ext/Bal/wixext/BalCompiler.cs index 1721f252..bc2ba861 100644 --- a/src/ext/Bal/wixext/BalCompiler.cs +++ b/src/ext/Bal/wixext/BalCompiler.cs @@ -16,7 +16,8 @@ namespace WixToolset.Bal /// public sealed class BalCompiler : BaseCompilerExtension { - private readonly Dictionary prereqInfoSymbolsByPackageId; + private readonly Dictionary packageInfoSymbolsByPackageId = new Dictionary(); + private readonly Dictionary prereqInfoSymbolsByPackageId = new Dictionary(); private enum WixDotNetCoreBootstrapperApplicationHostTheme { @@ -32,6 +33,13 @@ namespace WixToolset.Bal Standard, } + private enum WixInternalUIBootstrapperApplicationTheme + { + Unknown, + None, + Standard, + } + private enum WixStandardBootstrapperApplicationTheme { Unknown, @@ -43,14 +51,6 @@ namespace WixToolset.Bal RtfLicense, } - /// - /// Instantiate a new BalCompiler. - /// - public BalCompiler() - { - this.prereqInfoSymbolsByPackageId = new Dictionary(); - } - public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/bal"; /// @@ -83,6 +83,9 @@ namespace WixToolset.Bal case "BootstrapperApplication": switch (element.Name.LocalName) { + case "WixInternalUIBootstrapperApplication": + this.ParseWixInternalUIBootstrapperApplicationElement(intermediate, section, element); + break; case "WixStandardBootstrapperApplication": this.ParseWixStandardBootstrapperApplicationElement(intermediate, section, element); break; @@ -113,7 +116,6 @@ namespace WixToolset.Bal public override void ParseAttribute(Intermediate intermediate, IntermediateSection section, XElement parentElement, XAttribute attribute, IDictionary context) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(parentElement); - WixMbaPrereqInformationSymbol prereqInfo; switch (parentElement.Name.LocalName) { @@ -137,42 +139,63 @@ namespace WixToolset.Bal case "MsiPackage": case "MspPackage": var displayInternalUICondition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attribute); - section.AddSymbol(new WixBalPackageInfoSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, packageId)) - { - PackageId = packageId, - DisplayInternalUICondition = displayInternalUICondition, - }); + var packageInfo = this.GetBalPackageInfoSymbol(section, sourceLineNumbers, packageId); + packageInfo.DisplayInternalUICondition = displayInternalUICondition; break; default: this.ParseHelper.UnexpectedAttribute(parentElement, attribute); break; } break; - case "PrereqLicenseFile": - - if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out prereqInfo)) + case "PrimaryPackageType": + { + var primaryPackageType = BalPrimaryPackageType.None; + var primaryPackageTypeValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attribute); + switch (primaryPackageTypeValue) { - // at the time the extension attribute is parsed, the compiler might not yet have - // parsed the PrereqPackage attribute, so we need to get it directly from the parent element. - var prereqPackage = parentElement.Attribute(this.Namespace + "PrereqPackage"); - - if (null != prereqPackage && YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, prereqPackage)) - { - prereqInfo = section.AddSymbol(new WixMbaPrereqInformationSymbol(sourceLineNumbers) - { - PackageId = packageId, - }); - - this.prereqInfoSymbolsByPackageId.Add(packageId, prereqInfo); - } - else - { - this.Messaging.Write(BalErrors.AttributeRequiresPrereqPackage(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseFile")); + case "default": + primaryPackageType = BalPrimaryPackageType.Default; + break; + case "x86": + primaryPackageType = BalPrimaryPackageType.X86; + break; + case "x64": + primaryPackageType = BalPrimaryPackageType.X64; + break; + case "arm64": + primaryPackageType = BalPrimaryPackageType.ARM64; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, "PrimaryPackageType", primaryPackageTypeValue, "default", "x86", "x64", "arm64")); break; - } } - if (null != prereqInfo.LicenseUrl) + // at the time the extension attribute is parsed, the compiler might not yet have + // parsed the PrereqPackage attribute, so we need to get it directly from the parent element. + var prereqPackage = parentElement.Attribute(this.Namespace + "PrereqPackage"); + var prereqInfo = this.GetMbaPrereqInformationSymbol(section, sourceLineNumbers, prereqPackage, packageId); + if (prereqInfo != null) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, parentElement.Name.LocalName, "PrereqPackage", "yes", "PrimaryPackageType")); + } + else + { + var packageInfo = this.GetBalPackageInfoSymbol(section, sourceLineNumbers, packageId); + packageInfo.PrimaryPackageType = primaryPackageType; + } + break; + } + case "PrereqLicenseFile": + { + // at the time the extension attribute is parsed, the compiler might not yet have + // parsed the PrereqPackage attribute, so we need to get it directly from the parent element. + var prereqPackage = parentElement.Attribute(this.Namespace + "PrereqPackage"); + var prereqInfo = this.GetMbaPrereqInformationSymbol(section, sourceLineNumbers, prereqPackage, packageId); + if (prereqInfo == null) + { + this.Messaging.Write(BalErrors.AttributeRequiresPrereqPackage(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseFile")); + } + else if (null != prereqInfo.LicenseUrl) { this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseFile", "PrereqLicenseUrl")); } @@ -181,31 +204,19 @@ namespace WixToolset.Bal prereqInfo.LicenseFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attribute); } break; + } case "PrereqLicenseUrl": + { + // at the time the extension attribute is parsed, the compiler might not yet have + // parsed the PrereqPackage attribute, so we need to get it directly from the parent element. + var prereqPackage = parentElement.Attribute(this.Namespace + "PrereqPackage"); + var prereqInfo = this.GetMbaPrereqInformationSymbol(section, sourceLineNumbers, prereqPackage, packageId); - if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out prereqInfo)) + if (prereqInfo == null) { - // at the time the extension attribute is parsed, the compiler might not yet have - // parsed the PrereqPackage attribute, so we need to get it directly from the parent element. - var prereqPackage = parentElement.Attribute(this.Namespace + "PrereqPackage"); - - if (null != prereqPackage && YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, prereqPackage)) - { - prereqInfo = section.AddSymbol(new WixMbaPrereqInformationSymbol(sourceLineNumbers) - { - PackageId = packageId, - }); - - this.prereqInfoSymbolsByPackageId.Add(packageId, prereqInfo); - } - else - { - this.Messaging.Write(BalErrors.AttributeRequiresPrereqPackage(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseUrl")); - break; - } + this.Messaging.Write(BalErrors.AttributeRequiresPrereqPackage(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseUrl")); } - - if (null != prereqInfo.LicenseFile) + else if (null != prereqInfo.LicenseFile) { this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, parentElement.Name.LocalName, "PrereqLicenseUrl", "PrereqLicenseFile")); } @@ -214,19 +225,9 @@ namespace WixToolset.Bal prereqInfo.LicenseUrl = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attribute); } break; + } case "PrereqPackage": - if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute)) - { - if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out _)) - { - prereqInfo = section.AddSymbol(new WixMbaPrereqInformationSymbol(sourceLineNumbers) - { - PackageId = packageId, - }); - - this.prereqInfoSymbolsByPackageId.Add(packageId, prereqInfo); - } - } + this.GetMbaPrereqInformationSymbol(section, sourceLineNumbers, attribute, packageId); break; default: this.ParseHelper.UnexpectedAttribute(parentElement, attribute); @@ -300,6 +301,41 @@ namespace WixToolset.Bal } } + private WixBalPackageInfoSymbol GetBalPackageInfoSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string packageId) + { + if (!this.packageInfoSymbolsByPackageId.TryGetValue(packageId, out var packageInfo)) + { + packageInfo = section.AddSymbol(new WixBalPackageInfoSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, packageId)) + { + PackageId = packageId, + }); + + this.packageInfoSymbolsByPackageId.Add(packageId, packageInfo); + } + + return packageInfo; + } + + private WixMbaPrereqInformationSymbol GetMbaPrereqInformationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute prereqAttribute, string packageId) + { + WixMbaPrereqInformationSymbol prereqInfo = null; + + if (prereqAttribute != null && YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, prereqAttribute)) + { + if (!this.prereqInfoSymbolsByPackageId.TryGetValue(packageId, out _)) + { + prereqInfo = section.AddSymbol(new WixMbaPrereqInformationSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, packageId)) + { + PackageId = packageId, + }); + + this.prereqInfoSymbolsByPackageId.Add(packageId, prereqInfo); + } + } + + return prereqInfo; + } + /// /// Parses a Condition element for Bundles. /// @@ -418,6 +454,101 @@ namespace WixToolset.Bal } } + private void ParseWixInternalUIBootstrapperApplicationElement(Intermediate intermediate, IntermediateSection section, XElement node) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); + WixInternalUIBootstrapperApplicationTheme? theme = null; + string themeFile = null; + string logoFile = null; + string localizationFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "LogoFile": + logoFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ThemeFile": + themeFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "LocalizationFile": + localizationFile = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Theme": + var themeValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (themeValue) + { + case "none": + theme = WixInternalUIBootstrapperApplicationTheme.None; + break; + case "standard": + theme = WixInternalUIBootstrapperApplicationTheme.Standard; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Theme", themeValue, "none", "standard")); + theme = WixInternalUIBootstrapperApplicationTheme.Unknown; + break; + } + break; + default: + this.ParseHelper.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, node); + + if (!theme.HasValue) + { + theme = WixInternalUIBootstrapperApplicationTheme.Standard; + } + + if (!this.Messaging.EncounteredError) + { + if (!String.IsNullOrEmpty(logoFile)) + { + section.AddSymbol(new WixVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixIuibaLogo")) + { + Value = logoFile, + }); + } + + if (!String.IsNullOrEmpty(themeFile)) + { + section.AddSymbol(new WixVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixIuibaThemeXml")) + { + Value = themeFile, + }); + } + + if (!String.IsNullOrEmpty(localizationFile)) + { + section.AddSymbol(new WixVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixIuibaThemeWxl")) + { + Value = localizationFile, + }); + } + + var baId = "WixInternalUIBootstrapperApplication"; + switch (theme) + { + case WixInternalUIBootstrapperApplicationTheme.Standard: + baId = "WixInternalUIBootstrapperApplication.Standard"; + break; + } + + this.CreateBARef(section, sourceLineNumbers, node, baId); + } + } + /// /// Parses a WixStandardBootstrapperApplication element for Bundles. /// diff --git a/src/ext/Bal/wixext/BalErrors.cs b/src/ext/Bal/wixext/BalErrors.cs index e9f68b24..a7a00a4b 100644 --- a/src/ext/Bal/wixext/BalErrors.cs +++ b/src/ext/Bal/wixext/BalErrors.cs @@ -18,11 +18,41 @@ namespace WixToolset.Bal return Message(sourceLineNumbers, Ids.BAFunctionsPayloadRequiredInUXContainer, "The BAFunctions DLL Payload element must be located inside the BootstrapperApplication container."); } + public static Message IuibaNonMsiPrimaryPackage(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaNonMsiPrimaryPackage, "When using WixInternalUIBootstrapperApplication, each primary package must be an MsiPackage."); + } + + public static Message IuibaNonPermanentNonPrimaryPackage(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaNonPermanentNonPrimaryPackage, "When using WixInternalUIBootstrapperApplication, packages must either be non-permanent and have the bal:PrimaryPackageType attribute, or be permanent and have the bal:PrereqPackage attribute set to 'yes'."); + } + + public static Message IuibaNonPermanentPrereqPackage(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaNonPermanentPrereqPackage, "When using WixInternalUIBootstrapperApplication and bal:PrereqPackage is set to 'yes', the package must be permanent."); + } + + public static Message IuibaPermanentPrimaryPackageType(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPermanentPrimaryPackageType, "When using WixInternalUIBootstrapperApplication, packages with the bal:PrimaryPackageType attribute must not be permanent."); + } + + public static Message IuibaPrimaryPackageEnableFeatureSelection(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageEnableFeatureSelection, "When using WixInternalUIBootstrapperApplication, primary packages must not have feature selection enabled because it interferes with the user selecting feature through the MSI UI."); + } + public static Message MissingDNCPrereq() { return Message(null, Ids.MissingDNCPrereq, "There must be at least one PrereqPackage when using the DotNetCoreBootstrapperApplicationHost with SelfContainedDeployment set to \"no\"."); } + public static Message MissingIUIPrimaryPackage() + { + return Message(null, Ids.MissingIUIPrimaryPackage, "When using WixInternalUIBootstrapperApplication, there must be one package with bal:PrimaryPackageType=\"default\"."); + } + public static Message MissingMBAPrereq() { return Message(null, Ids.MissingMBAPrereq, "There must be at least one PrereqPackage when using the ManagedBootstrapperApplicationHost.\nThis is typically done by using the WixNetFxExtension and referencing one of the NetFxAsPrereq package groups."); @@ -38,6 +68,16 @@ namespace WixToolset.Bal return Message(sourceLineNumbers, Ids.MultiplePrereqLicenses, "There may only be one package in the bundle that has either the PrereqLicenseFile attribute or the PrereqLicenseUrl attribute."); } + public static Message MultiplePrimaryPackageType(SourceLineNumber sourceLineNumbers, string primaryPackageType) + { + return Message(sourceLineNumbers, Ids.MultiplePrimaryPackageType, "There may only be one package in the bundle with PrimaryPackageType of '{0}'.", primaryPackageType); + } + + public static Message MultiplePrimaryPackageType2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.MultiplePrimaryPackageType2, "The location of the package related to the previous error."); + } + public static Message NonUpperCaseOverridableVariable(SourceLineNumber sourceLineNumbers, string name, string expectedName) { return Message(sourceLineNumbers, Ids.NonUpperCaseOverridableVariable, "Overridable variable '{0}' must be '{1}' with Bundle/@CommandLineVariables value 'upperCase'.", name, expectedName); @@ -62,6 +102,14 @@ namespace WixToolset.Bal BAFunctionsPayloadRequiredInUXContainer = 6805, MissingDNCPrereq = 6806, NonUpperCaseOverridableVariable = 6807, + MissingIUIPrimaryPackage = 6808, + MultiplePrimaryPackageType = 6809, + MultiplePrimaryPackageType2 = 6810, + IuibaNonPermanentNonPrimaryPackage = 6811, + IuibaNonPermanentPrereqPackage = 6812, + IuibaPermanentPrimaryPackageType = 6813, + IuibaNonMsiPrimaryPackage = 6814, + IuibaPrimaryPackageEnableFeatureSelection = 6815, } } } diff --git a/src/ext/Bal/wixext/BalWarnings.cs b/src/ext/Bal/wixext/BalWarnings.cs index 18b25062..96e7a523 100644 --- a/src/ext/Bal/wixext/BalWarnings.cs +++ b/src/ext/Bal/wixext/BalWarnings.cs @@ -8,6 +8,26 @@ namespace WixToolset.Bal public static class BalWarnings { + public static Message IuibaForceCachePrereq(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaForceCachePrereq, "WixInternalUIBootstrapperApplication does not support the value of 'force' for Cache on prereq packages. Prereq packages are only cached when they need to be installed."); + } + + public static Message IuibaPrereqPackageAfterPrimaryPackage(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPrereqPackageAfterPrimaryPackage, "When using WixInternalUIBootstrapperApplication, all prereq packages should be before the primary package in the chain. The prereq packages are always installed before the primary package."); + } + + public static Message IuibaPrimaryPackageDisplayInternalUICondition(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageDisplayInternalUICondition, "WixInternalUIBootstrapperApplication ignores DisplayInternalUICondition for the primary package so that the MSI UI is always shown."); + } + + public static Message IuibaPrimaryPackageInstallCondition(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageInstallCondition, "WixInternalUIBootstrapperApplication ignores InstallCondition for the primary package so that the MSI UI is always shown."); + } + public static Message UnmarkedBAFunctionsDLL(SourceLineNumber sourceLineNumbers) { return Message(sourceLineNumbers, Ids.UnmarkedBAFunctionsDLL, "WixStandardBootstrapperApplication doesn't automatically load BAFunctions.dll. Use the bal:BAFunctions attribute to indicate that it should be loaded."); @@ -26,6 +46,10 @@ namespace WixToolset.Bal public enum Ids { UnmarkedBAFunctionsDLL = 6501, + IuibaForceCachePrereq = 6502, + IuibaPrimaryPackageInstallCondition = 6503, + IuibaPrimaryPackageDisplayInternalUICondition = 6504, + IuibaPrereqPackageAfterPrimaryPackage = 6505, } } } diff --git a/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs b/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs index b09cb191..08d4ce4e 100644 --- a/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs +++ b/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs @@ -13,6 +13,7 @@ namespace WixToolset.Bal { new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.PackageId), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.DisplayInternalUICondition), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.PrimaryPackageType), IntermediateFieldType.Number), }, typeof(WixBalPackageInfoSymbol)); } @@ -26,6 +27,16 @@ namespace WixToolset.Bal.Symbols { PackageId, DisplayInternalUICondition, + PrimaryPackageType, + } + + public enum BalPrimaryPackageType + { + None, + Default, + X86, + X64, + ARM64, } public class WixBalPackageInfoSymbol : IntermediateSymbol @@ -51,5 +62,11 @@ namespace WixToolset.Bal.Symbols get => this.Fields[(int)WixBalPackageInfoSymbolFields.DisplayInternalUICondition].AsString(); set => this.Set((int)WixBalPackageInfoSymbolFields.DisplayInternalUICondition, value); } + + public BalPrimaryPackageType PrimaryPackageType + { + get => (BalPrimaryPackageType)this.Fields[(int)WixBalPackageInfoSymbolFields.PrimaryPackageType].AsNumber(); + set => this.Set((int)WixBalPackageInfoSymbolFields.PrimaryPackageType, (int)value); + } } } diff --git a/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.cpp b/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.cpp new file mode 100644 index 00000000..dbb97366 --- /dev/null +++ b/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.cpp @@ -0,0 +1,918 @@ +// 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 "BalBaseBootstrapperApplicationProc.h" +#include "BalBaseBootstrapperApplication.h" + +static const LPCWSTR WIXIUIBA_WINDOW_CLASS = L"WixInternalUIBA"; + +enum WM_WIXIUIBA +{ + WM_WIXIUIBA_DETECT_PACKAGES = WM_APP + 100, + WM_WIXIUIBA_PLAN_PACKAGES, + WM_WIXIUIBA_APPLY_PACKAGES, + WM_WIXIUIBA_DETECT_FOR_CLEANUP, + WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP, +}; + + +class CWixInternalUIBootstrapperApplication : public CBalBaseBootstrapperApplication +{ +public: // IBootstrapperApplication + virtual STDMETHODIMP OnStartup() + { + HRESULT hr = S_OK; + DWORD dwUIThreadId = 0; + + // create UI thread + m_hUiThread = ::CreateThread(NULL, 0, UiThreadProc, this, 0, &dwUIThreadId); + if (!m_hUiThread) + { + BalExitWithLastError(hr, "Failed to create UI thread."); + } + + LExit: + return hr; + } + + + virtual STDMETHODIMP OnShutdown( + __inout BOOTSTRAPPER_SHUTDOWN_ACTION* pAction + ) + { + // wait for UI thread to terminate + if (m_hUiThread) + { + ::WaitForSingleObject(m_hUiThread, INFINITE); + ReleaseHandle(m_hUiThread); + } + + if (m_fFailedToLoadPackage) + { + Assert(FAILED(m_hrFinal)); + m_pPrereqData->hrFatalError = m_hrFinal; + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load primary package as the BA. The bootstrapper application will be reloaded to show the error."); + *pAction = BOOTSTRAPPER_SHUTDOWN_ACTION_RELOAD_BOOTSTRAPPER; + } + + return S_OK; + } + + + virtual STDMETHODIMP OnDetectPackageComplete( + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached + ) + { + BAL_INFO_PACKAGE* pPackage = NULL; + + if (SUCCEEDED(hrStatus) && SUCCEEDED(BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage)) && + BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT == pPackage->primaryPackageType) + { + BOOL fInstalled = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < state; + + // Maybe modify the action state if the primary package is or is not already installed. + if (fInstalled && BOOTSTRAPPER_ACTION_INSTALL == m_command.action) + { + m_command.action = BOOTSTRAPPER_ACTION_MODIFY; + } + else if (!fInstalled && (BOOTSTRAPPER_ACTION_MODIFY == m_command.action || BOOTSTRAPPER_ACTION_REPAIR == m_command.action)) + { + m_command.action = BOOTSTRAPPER_ACTION_INSTALL; + } + + if (m_fApplied && !fInstalled && fCached) + { + m_fAutomaticRemoval = TRUE; + } + } + + return __super::OnDetectPackageComplete(wzPackageId, hrStatus, state, fCached); + } + + + virtual STDMETHODIMP OnDetectComplete( + __in HRESULT hrStatus, + __in BOOL fEligibleForCleanup + ) + { + if (m_fAutomaticRemoval && SUCCEEDED(hrStatus)) + { + ::PostMessageW(m_hWnd, WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP, 0, BOOTSTRAPPER_ACTION_UNINSTALL); + ExitFunction(); + } + else if (m_fApplied) + { + ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); + ExitFunction(); + } + + // If we're performing an action that modifies machine state then evaluate conditions. + BOOL fEvaluateConditions = SUCCEEDED(hrStatus) && + (BOOTSTRAPPER_ACTION_LAYOUT < m_command.action && BOOTSTRAPPER_ACTION_UPDATE_REPLACE > m_command.action); + + if (fEvaluateConditions) + { + hrStatus = EvaluateConditions(); + } + + if (SUCCEEDED(hrStatus)) + { + ::PostMessageW(m_hWnd, WM_WIXIUIBA_PLAN_PACKAGES, 0, m_command.action); + } + else + { + SetLoadPackageFailure(hrStatus); + } + + LExit: + return __super::OnDetectComplete(hrStatus, fEligibleForCleanup); + } + + + virtual STDMETHODIMP OnPlanPackageBegin( + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT repairCondition, + __in BOOTSTRAPPER_REQUEST_STATE recommendedState, + __in BOOTSTRAPPER_CACHE_TYPE recommendedCacheType, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestState, + __inout BOOTSTRAPPER_CACHE_TYPE* pRequestedCacheType, + __inout BOOL* pfCancel + ) + { + HRESULT hr = S_OK; + BAL_INFO_PACKAGE* pPackage = NULL; + + hr = BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); + if (FAILED(hr)) + { + // Non-chain package, keep default. + } + else if (BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT != pPackage->primaryPackageType) + { + // Only the primary package should be cached or executed. + if (BOOTSTRAPPER_CACHE_TYPE_FORCE == *pRequestedCacheType) + { + *pRequestedCacheType = BOOTSTRAPPER_CACHE_TYPE_KEEP; + } + + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + } + else if (BOOTSTRAPPER_DISPLAY_FULL == m_command.display && !m_fAutomaticRemoval) + { + // Make sure the MSI UI is shown regardless of the current state of the package. + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; + } + + return __super::OnPlanPackageBegin(wzPackageId, state, fCached, installCondition, repairCondition, recommendedState, recommendedCacheType, pRequestState, pRequestedCacheType, pfCancel); + } + + + virtual STDMETHODIMP OnPlanMsiPackage( + __in_z LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE action, + __in BOOTSTRAPPER_MSI_FILE_VERSIONING recommendedFileVersioning, + __inout BOOL* pfCancel, + __inout BURN_MSI_PROPERTY* pActionMsiProperty, + __inout INSTALLUILEVEL* pUiLevel, + __inout BOOL* pfDisableExternalUiHandler, + __inout BOOTSTRAPPER_MSI_FILE_VERSIONING* pFileVersioning + ) + { + INSTALLUILEVEL uiLevel = INSTALLUILEVEL_NOCHANGE; + + if (m_fAutomaticRemoval) + { + ExitFunction(); + } + + switch (m_command.display) + { + case BOOTSTRAPPER_DISPLAY_FULL: + uiLevel = INSTALLUILEVEL_FULL; + break; + + case BOOTSTRAPPER_DISPLAY_PASSIVE: + uiLevel = INSTALLUILEVEL_REDUCED; + break; + } + + if (INSTALLUILEVEL_NOCHANGE != uiLevel) + { + *pUiLevel = uiLevel; + } + + *pActionMsiProperty = BURN_MSI_PROPERTY_NONE; + *pfDisableExternalUiHandler = TRUE; + + LExit: + return __super::OnPlanMsiPackage(wzPackageId, fExecute, action, recommendedFileVersioning, pfCancel, pActionMsiProperty, pUiLevel, pfDisableExternalUiHandler, pFileVersioning); + } + + + virtual STDMETHODIMP OnPlanComplete( + __in HRESULT hrStatus + ) + { + if (SUCCEEDED(hrStatus)) + { + ::PostMessageW(m_hWnd, WM_WIXIUIBA_APPLY_PACKAGES, 0, 0); + } + else if (m_fAutomaticRemoval) + { + ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); + } + else + { + SetLoadPackageFailure(hrStatus); + } + + return __super::OnPlanComplete(hrStatus); + } + + + virtual STDMETHODIMP OnApplyBegin( + __in DWORD dwPhaseCount, + __inout BOOL* pfCancel + ) + { + m_fApplying = TRUE; + return __super::OnApplyBegin(dwPhaseCount, pfCancel); + } + + + virtual STDMETHODIMP OnCacheComplete( + __in HRESULT hrStatus + ) + { + if (FAILED(hrStatus) && !m_fAutomaticRemoval) + { + SetLoadPackageFailure(hrStatus); + } + + return __super::OnCacheComplete(hrStatus); + } + + + virtual STDMETHODIMP OnExecuteBegin( + __in DWORD cExecutingPackages, + __in BOOL* pfCancel + ) + { + m_pEngine->CloseSplashScreen(); + + return __super::OnExecuteBegin(cExecutingPackages, pfCancel); + } + + + virtual STDMETHODIMP OnApplyComplete( + __in HRESULT hrStatus, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __in BOOTSTRAPPER_APPLYCOMPLETE_ACTION recommendation, + __inout BOOTSTRAPPER_APPLYCOMPLETE_ACTION* pAction + ) + { + HRESULT hr = __super::OnApplyComplete(hrStatus, restart, recommendation, pAction); + + *pAction = BOOTSTRAPPER_APPLYCOMPLETE_ACTION_NONE; + m_fApplying = FALSE; + + if (m_fAutomaticRemoval) + { + ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); + } + else + { + m_restartResult = restart; // remember the restart result so we return the correct error code. + m_fApplied = TRUE; + + if (FAILED(hrStatus)) + { + m_hrFinal = hrStatus; + } + + ::PostMessageW(m_hWnd, WM_WIXIUIBA_DETECT_FOR_CLEANUP, 0, 0); + } + + return hr; + } + + +public: //CBalBaseBootstrapperApplication + virtual STDMETHODIMP Initialize( + __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs + ) + { + HRESULT hr = S_OK; + + hr = __super::Initialize(pCreateArgs); + BalExitOnFailure(hr, "CBalBaseBootstrapperApplication initialization failed."); + + memcpy_s(&m_command, sizeof(m_command), pCreateArgs->pCommand, sizeof(BOOTSTRAPPER_COMMAND)); + memcpy_s(&m_createArgs, sizeof(m_createArgs), pCreateArgs, sizeof(BOOTSTRAPPER_CREATE_ARGS)); + m_createArgs.pCommand = &m_command; + + LExit: + return hr; + } + + void Uninitialize( + __in const BOOTSTRAPPER_DESTROY_ARGS* /*pArgs*/, + __in BOOTSTRAPPER_DESTROY_RESULTS* /*pResults*/ + ) + { + } + + +private: + // + // UiThreadProc - entrypoint for UI thread. + // + static DWORD WINAPI UiThreadProc( + __in LPVOID pvContext + ) + { + HRESULT hr = S_OK; + CWixInternalUIBootstrapperApplication* pThis = (CWixInternalUIBootstrapperApplication*)pvContext; + BOOL fComInitialized = FALSE; + BOOL fRet = FALSE; + MSG msg = { }; + DWORD dwQuit = 0; + + // Initialize COM and theme. + hr = ::CoInitialize(NULL); + BalExitOnFailure(hr, "Failed to initialize COM."); + fComInitialized = TRUE; + + hr = pThis->InitializeData(); + BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application."); + + // Create main window. + hr = pThis->CreateMainWindow(); + BalExitOnFailure(hr, "Failed to create main window."); + + ::PostMessageW(pThis->m_hWnd, WM_WIXIUIBA_DETECT_PACKAGES, 0, 0); + + // message pump + while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) + { + if (-1 == fRet) + { + hr = E_UNEXPECTED; + BalExitOnFailure(hr, "Unexpected return value from message pump."); + } + else if (!::IsDialogMessageW(pThis->m_hWnd, &msg)) + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + + // Succeeded thus far, check to see if anything went wrong while actually + // executing changes. + if (FAILED(pThis->m_hrFinal)) + { + hr = pThis->m_hrFinal; + } + else if (pThis->CheckCanceled()) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + + LExit: + // destroy main window + pThis->DestroyMainWindow(); + + if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->m_restartResult) + { + dwQuit = ERROR_SUCCESS_REBOOT_INITIATED; + } + else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->m_restartResult) + { + dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED; + } + else if (SEVERITY_ERROR == HRESULT_SEVERITY(hr) && FACILITY_WIN32 == HRESULT_FACILITY(hr)) + { + // Convert Win32 HRESULTs back to the error code. + dwQuit = HRESULT_CODE(hr); + } + else + { + dwQuit = hr; + } + + // initiate engine shutdown + pThis->m_pEngine->Quit(dwQuit); + + // uninitialize COM + if (fComInitialized) + { + ::CoUninitialize(); + } + + return hr; + } + + + // + // InitializeData - initializes all the package and prerequisite information. + // + HRESULT InitializeData() + { + HRESULT hr = S_OK; + IXMLDOMDocument* pixdManifest = NULL; + + hr = BalManifestLoad(m_hModule, &pixdManifest); + BalExitOnFailure(hr, "Failed to load bootstrapper application manifest."); + + hr = BalInfoParseFromXml(&m_Bundle, pixdManifest); + BalExitOnFailure(hr, "Failed to load bundle information."); + + hr = EnsureSinglePrimaryPackage(); + BalExitOnFailure(hr, "Failed to ensure single primary package."); + + hr = ProcessCommandLine(); + ExitOnFailure(hr, "Unknown commandline parameters."); + + hr = BalConditionsParseFromXml(&m_Conditions, pixdManifest, NULL); + BalExitOnFailure(hr, "Failed to load conditions from XML."); + + LExit: + ReleaseObject(pixdManifest); + + return hr; + } + + + // + // ProcessCommandLine - process the provided command line arguments. + // + HRESULT ProcessCommandLine() + { + HRESULT hr = S_OK; + int argc = 0; + LPWSTR* argv = NULL; + + argc = m_BalInfoCommand.cUnknownArgs; + argv = m_BalInfoCommand.rgUnknownArgs; + + for (int i = 0; i < argc; ++i) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); + } + + hr = BalSetOverridableVariablesFromEngine(&m_Bundle.overridableVariables, &m_BalInfoCommand, m_pEngine); + BalExitOnFailure(hr, "Failed to set overridable variables from the command line."); + + LExit: + return hr; + } + + HRESULT EnsureSinglePrimaryPackage() + { + HRESULT hr = S_OK; + BAL_INFO_PACKAGE* pDefaultPackage = NULL; + BOOL fPrimaryArchSpecific = FALSE; + USHORT usNativeMachine = 0; + BAL_INFO_PRIMARY_PACKAGE_TYPE nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE; + + hr = ProcNativeMachine(::GetCurrentProcess(), &usNativeMachine); + BalExitOnFailure(hr, "Failed to get native machine value."); + + if (S_FALSE != hr) + { + switch (usNativeMachine) + { + case IMAGE_FILE_MACHINE_I386: + nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X86; + break; + case IMAGE_FILE_MACHINE_AMD64: + nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X64; + break; + case IMAGE_FILE_MACHINE_ARM64: + nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_ARM64; + break; + } + } + else + { +#if !defined(_WIN64) + BOOL fIsWow64 = FALSE; + + ProcWow64(::GetCurrentProcess(), &fIsWow64); + if (!fIsWow64) + { + nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X86; + } + else +#endif + { + nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X64; + } + } + + for (DWORD i = 0; i < m_Bundle.packages.cPackages; ++i) + { + BAL_INFO_PACKAGE* pPackage = m_Bundle.packages.rgPackages + i; + + if (BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE == pPackage->primaryPackageType) + { + // Skip. + } + else if (nativeType == pPackage->primaryPackageType) + { + if (fPrimaryArchSpecific) + { + BalExitWithRootFailure(hr, E_INVALIDDATA, "Bundle contains multiple primary packages for same architecture: %u.", nativeType); + } + + pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT; + fPrimaryArchSpecific = TRUE; + } + else if (BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT == pPackage->primaryPackageType) + { + if (pDefaultPackage) + { + BalExitWithRootFailure(hr, E_INVALIDDATA, "Bundle contains multiple default primary packages."); + } + + pDefaultPackage = pPackage; + } + } + + BalExitOnNull(pDefaultPackage, hr, E_INVALIDSTATE, "Bundle did not contain default primary package."); + + if (fPrimaryArchSpecific) + { + pDefaultPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE; + } + + LExit: + return hr; + } + + + // + // CreateMainWindow - creates the main install window. + // + HRESULT CreateMainWindow() + { + HRESULT hr = S_OK; + WNDCLASSW wc = { }; + DWORD dwWindowStyle = WS_POPUP; + + wc.lpfnWndProc = CWixInternalUIBootstrapperApplication::WndProc; + wc.hInstance = m_hModule; + wc.lpszClassName = WIXIUIBA_WINDOW_CLASS; + + if (!::RegisterClassW(&wc)) + { + ExitWithLastError(hr, "Failed to register window."); + } + + m_fRegistered = TRUE; + + // If the UI should be visible, allow it to be visible and activated so we are the foreground window. + // This allows the UAC prompt and MSI UI to automatically be activated. + if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) + { + dwWindowStyle |= WS_VISIBLE; + } + + m_hWnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, wc.lpszClassName, NULL, dwWindowStyle, 0, 0, 0, 0, HWND_DESKTOP, NULL, m_hModule, this); + ExitOnNullWithLastError(m_hWnd, hr, "Failed to create window."); + + LExit: + return hr; + } + + // + // DestroyMainWindow - clean up all the window registration. + // + void DestroyMainWindow() + { + if (::IsWindow(m_hWnd)) + { + ::DestroyWindow(m_hWnd); + m_hWnd = NULL; + } + + if (m_fRegistered) + { + ::UnregisterClassW(WIXIUIBA_WINDOW_CLASS, m_hModule); + m_fRegistered = FALSE; + } + } + + // + // WndProc - standard windows message handler. + // + static LRESULT CALLBACK WndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) + { +#pragma warning(suppress:4312) + CWixInternalUIBootstrapperApplication* pBA = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + + switch (uMsg) + { + case WM_NCCREATE: + { + LPCREATESTRUCT lpcs = reinterpret_cast(lParam); + pBA = reinterpret_cast(lpcs->lpCreateParams); +#pragma warning(suppress:4244) + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pBA)); + } + break; + + case WM_NCDESTROY: + { + LRESULT lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); + ::PostQuitMessage(0); + return lres; + } + + case WM_CLOSE: + // If the user chose not to close, do *not* let the default window proc handle the message. + if (!pBA->OnClose()) + { + return 0; + } + break; + + case WM_WIXIUIBA_DETECT_PACKAGES: __fallthrough; + case WM_WIXIUIBA_DETECT_FOR_CLEANUP: + pBA->OnDetect(); + return 0; + + case WM_WIXIUIBA_PLAN_PACKAGES: + case WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP: + pBA->OnPlan(static_cast(lParam)); + return 0; + + case WM_WIXIUIBA_APPLY_PACKAGES: + pBA->OnApply(); + return 0; + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + + + // + // OnDetect - start the processing of packages. + // + void OnDetect() + { + HRESULT hr = S_OK; + + hr = m_pEngine->Detect(); + BalExitOnFailure(hr, "Failed to start detecting chain."); + + LExit: + if (FAILED(hr)) + { + SetLoadPackageFailure(hr); + } + } + + + // + // OnPlan - plan the detected changes. + // + void OnPlan( + __in BOOTSTRAPPER_ACTION action + ) + { + HRESULT hr = S_OK; + + m_plannedAction = action; + + hr = m_pEngine->Plan(action); + BalExitOnFailure(hr, "Failed to start planning packages."); + + LExit: + if (FAILED(hr)) + { + SetLoadPackageFailure(hr); + } + } + + + // + // OnApply - apply the packages. + // + void OnApply() + { + HRESULT hr = S_OK; + + hr = m_pEngine->Apply(m_hWnd); + BalExitOnFailure(hr, "Failed to start applying packages."); + + LExit: + if (FAILED(hr)) + { + SetLoadPackageFailure(hr); + } + } + + + // + // OnClose - called when the window is trying to be closed. + // + BOOL OnClose() + { + BOOL fClose = FALSE; + + // If we've already applied, just close. + if (m_fApplied) + { + fClose = TRUE; + } + else + { + PromptCancel(m_hWnd, TRUE, NULL, NULL); + + // If we're inside Apply then we never close, we just cancel to let rollback occur. + fClose = !m_fApplying; + } + + return fClose; + } + + + HRESULT EvaluateConditions() + { + HRESULT hr = S_OK; + BOOL fResult = FALSE; + + for (DWORD i = 0; i < m_Conditions.cConditions; ++i) + { + BAL_CONDITION* pCondition = m_Conditions.rgConditions + i; + + hr = BalConditionEvaluate(pCondition, m_pEngine, &fResult, &m_sczFailedMessage); + BalExitOnFailure(hr, "Failed to evaluate condition."); + + if (!fResult) + { + hr = E_WIXSTDBA_CONDITION_FAILED; + BalExitOnFailure(hr, "%ls", m_sczFailedMessage); + } + } + + ReleaseNullStrSecure(m_sczFailedMessage); + + LExit: + return hr; + } + + + void SetLoadPackageFailure( + __in HRESULT hrStatus + ) + { + Assert(FAILED(hrStatus)); + + if (!m_fApplied) + { + m_hrFinal = hrStatus; + m_fFailedToLoadPackage = TRUE; + } + + // Quietly exit. + ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); + } + + +public: + // + // Constructor - initialize member variables. + // + CWixInternalUIBootstrapperApplication( + __in HMODULE hModule, + __in_opt PREQBA_DATA* pPrereqData, + __in IBootstrapperEngine* pEngine + ) : CBalBaseBootstrapperApplication(pEngine, 3, 3000) + { + m_hModule = hModule; + m_command = { }; + m_createArgs = { }; + + m_plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN; + + m_Bundle = { }; + m_Conditions = { }; + m_sczConfirmCloseMessage = NULL; + m_sczFailedMessage = NULL; + + m_hUiThread = NULL; + m_fRegistered = FALSE; + m_hWnd = NULL; + + m_hrFinal = S_OK; + + m_restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE; + + m_fApplying = FALSE; + m_fApplied = FALSE; + m_fAutomaticRemoval = FALSE; + m_fFailedToLoadPackage = FALSE; + m_pPrereqData = pPrereqData; + + pEngine->AddRef(); + m_pEngine = pEngine; + } + + + // + // Destructor - release member variables. + // + ~CWixInternalUIBootstrapperApplication() + { + ReleaseStr(m_sczFailedMessage); + ReleaseStr(m_sczConfirmCloseMessage); + BalConditionsUninitialize(&m_Conditions); + BalInfoUninitialize(&m_Bundle); + + ReleaseNullObject(m_pEngine); + } + +private: + HMODULE m_hModule; + BOOTSTRAPPER_CREATE_ARGS m_createArgs; + BOOTSTRAPPER_COMMAND m_command; + IBootstrapperEngine* m_pEngine; + BOOTSTRAPPER_ACTION m_plannedAction; + + BAL_INFO_BUNDLE m_Bundle; + BAL_CONDITIONS m_Conditions; + LPWSTR m_sczFailedMessage; + LPWSTR m_sczConfirmCloseMessage; + + HANDLE m_hUiThread; + BOOL m_fRegistered; + HWND m_hWnd; + + HRESULT m_hrFinal; + + BOOTSTRAPPER_APPLY_RESTART m_restartResult; + + BOOL m_fApplying; + BOOL m_fApplied; + BOOL m_fAutomaticRemoval; + BOOL m_fFailedToLoadPackage; + PREQBA_DATA* m_pPrereqData; +}; + + +// +// CreateBootstrapperApplication - creates a new IBootstrapperApplication object. +// +HRESULT CreateBootstrapperApplication( + __in HMODULE hModule, + __in_opt PREQBA_DATA* pPrereqData, + __in IBootstrapperEngine* pEngine, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults, + __out IBootstrapperApplication** ppApplication + ) +{ + HRESULT hr = S_OK; + CWixInternalUIBootstrapperApplication* pApplication = NULL; + + pApplication = new CWixInternalUIBootstrapperApplication(hModule, pPrereqData, pEngine); + BalExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new InternalUI bootstrapper application object."); + + hr = pApplication->Initialize(pArgs); + ExitOnFailure(hr, "CWixInternalUIBootstrapperApplication initialization failed."); + + pResults->pfnBootstrapperApplicationProc = BalBaseBootstrapperApplicationProc; + pResults->pvBootstrapperApplicationProcContext = pApplication; + *ppApplication = pApplication; + pApplication = NULL; + +LExit: + ReleaseObject(pApplication); + return hr; +} + + +void DestroyBootstrapperApplication( + __in IBootstrapperApplication* pApplication, + __in const BOOTSTRAPPER_DESTROY_ARGS* pArgs, + __inout BOOTSTRAPPER_DESTROY_RESULTS* pResults + ) +{ + CWixInternalUIBootstrapperApplication* pBA = (CWixInternalUIBootstrapperApplication*)pApplication; + pBA->Uninitialize(pArgs, pResults); +} diff --git a/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.h b/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.h new file mode 100644 index 00000000..b0b782dd --- /dev/null +++ b/src/ext/Bal/wixiuiba/WixInternalUIBootstrapperApplication.h @@ -0,0 +1,18 @@ +#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. + + +HRESULT CreateBootstrapperApplication( + __in HMODULE hModule, + __in_opt PREQBA_DATA* pPrereqData, + __in IBootstrapperEngine* pEngine, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults, + __out IBootstrapperApplication** ppApplication + ); + +void DestroyBootstrapperApplication( + __in IBootstrapperApplication* pApplication, + __in const BOOTSTRAPPER_DESTROY_ARGS* pArgs, + __inout BOOTSTRAPPER_DESTROY_RESULTS* pResults + ); diff --git a/src/ext/Bal/wixiuiba/precomp.cpp b/src/ext/Bal/wixiuiba/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/ext/Bal/wixiuiba/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/ext/Bal/wixiuiba/precomp.h b/src/ext/Bal/wixiuiba/precomp.h new file mode 100644 index 00000000..89ec6eab --- /dev/null +++ b/src/ext/Bal/wixiuiba/precomp.h @@ -0,0 +1,30 @@ +#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 +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "WixInternalUIBootstrapperApplication.h" +#include "wixiuiba.h" diff --git a/src/ext/Bal/wixiuiba/wixiuiba.cpp b/src/ext/Bal/wixiuiba/wixiuiba.cpp new file mode 100644 index 00000000..3e751893 --- /dev/null +++ b/src/ext/Bal/wixiuiba/wixiuiba.cpp @@ -0,0 +1,192 @@ +// 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 INTERNAL_UI_BA_STATE vstate = { }; + + +// internal function declarations + +static HRESULT LoadModulePaths( + __in INTERNAL_UI_BA_STATE* pState + ); +static HRESULT LoadInternalUIBAConfiguration( + __in INTERNAL_UI_BA_STATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ); +static HRESULT CreatePrerequisiteBA( + __in INTERNAL_UI_BA_STATE* pState, + __in IBootstrapperEngine* pEngine, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults + ); + + +// function definitions + +extern "C" BOOL WINAPI DllMain( + __in HINSTANCE hInstance, + __in DWORD dwReason, + __in LPVOID /*pvReserved*/ + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ::DisableThreadLibraryCalls(hInstance); + vstate.hInstance = hInstance; + break; + + case DLL_PROCESS_DETACH: + vstate.hInstance = NULL; + break; + } + + return TRUE; +} + +// Note: This function assumes that COM was already initialized on the thread. +extern "C" HRESULT WINAPI BootstrapperApplicationCreate( + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + IBootstrapperEngine* pEngine = NULL; + + hr = BalInitializeFromCreateArgs(pArgs, &pEngine); + ExitOnFailure(hr, "Failed to initialize Bal."); + + if (!vstate.fInitialized) + { + hr = XmlInitialize(); + BalExitOnFailure(hr, "Failed to initialize XML."); + + hr = LoadModulePaths(&vstate); + BalExitOnFailure(hr, "Failed to load the module paths."); + + hr = LoadInternalUIBAConfiguration(&vstate, pArgs); + BalExitOnFailure(hr, "Failed to get the InternalUIBA configuration."); + + vstate.fInitialized = TRUE; + } + + if (vstate.prereqData.fAlwaysInstallPrereqs && !vstate.prereqData.fCompleted || + FAILED(vstate.prereqData.hrFatalError)) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application."); + + hr = CreatePrerequisiteBA(&vstate, pEngine, pArgs, pResults); + BalExitOnFailure(hr, "Failed to create the pre-requisite bootstrapper application."); + } + else + { + hr = CreateBootstrapperApplication(vstate.hInstance, &vstate.prereqData, pEngine, pArgs, pResults, &vstate.pApplication); + BalExitOnFailure(hr, "Failed to create bootstrapper application interface."); + } + +LExit: + ReleaseNullObject(pEngine); + + return hr; +} + +extern "C" void WINAPI BootstrapperApplicationDestroy( + __in const BOOTSTRAPPER_DESTROY_ARGS* pArgs, + __in BOOTSTRAPPER_DESTROY_RESULTS* pResults + ) +{ + BOOTSTRAPPER_DESTROY_RESULTS childResults = { }; + + if (vstate.hPrereqModule) + { + PFN_BOOTSTRAPPER_APPLICATION_DESTROY pfnDestroy = reinterpret_cast(::GetProcAddress(vstate.hPrereqModule, "PrereqBootstrapperApplicationDestroy")); + if (pfnDestroy) + { + (*pfnDestroy)(pArgs, &childResults); + } + + ::FreeLibrary(vstate.hPrereqModule); + vstate.hPrereqModule = NULL; + } + + if (vstate.pApplication) + { + DestroyBootstrapperApplication(vstate.pApplication, pArgs, pResults); + ReleaseNullObject(vstate.pApplication); + } + + BalUninitialize(); + + // Need to keep track of state between reloads. + pResults->fDisableUnloading = TRUE; +} + +static HRESULT LoadModulePaths( + __in INTERNAL_UI_BA_STATE* pState + ) +{ + HRESULT hr = S_OK; + LPWSTR sczFullPath = NULL; + + hr = PathForCurrentProcess(&sczFullPath, pState->hInstance); + ExitOnFailure(hr, "Failed to get the full host path."); + + hr = PathGetDirectory(sczFullPath, &pState->sczAppBase); + ExitOnFailure(hr, "Failed to get the directory of the full process path."); + +LExit: + ReleaseStr(sczFullPath); + + return hr; +} + +static HRESULT LoadInternalUIBAConfiguration( + __in INTERNAL_UI_BA_STATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* /*pArgs*/ + ) +{ + HRESULT hr = S_OK; + + pState->prereqData.fAlwaysInstallPrereqs = TRUE; + pState->prereqData.fPerformHelp = TRUE; + pState->prereqData.fPerformLayout = TRUE; + + return hr; +} + +static HRESULT CreatePrerequisiteBA( + __in INTERNAL_UI_BA_STATE* pState, + __in IBootstrapperEngine* pEngine, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPrereqPath = NULL; + HMODULE hModule = NULL; + + hr = PathConcat(pState->sczAppBase, L"prereqba.dll", &sczPrereqPath); + BalExitOnFailure(hr, "Failed to get path to pre-requisite BA."); + + hModule = ::LoadLibraryExW(sczPrereqPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + ExitOnNullWithLastError(hModule, hr, "Failed to load pre-requisite BA DLL."); + + PFN_PREQ_BOOTSTRAPPER_APPLICATION_CREATE pfnCreate = reinterpret_cast(::GetProcAddress(hModule, "PrereqBootstrapperApplicationCreate")); + ExitOnNullWithLastError(pfnCreate, hr, "Failed to get PrereqBootstrapperApplicationCreate entry-point from: %ls", sczPrereqPath); + + hr = pfnCreate(&pState->prereqData, pEngine, pArgs, pResults); + ExitOnFailure(hr, "Failed to create prequisite bootstrapper app."); + + pState->hPrereqModule = hModule; + hModule = NULL; + +LExit: + if (hModule) + { + ::FreeLibrary(hModule); + } + ReleaseStr(sczPrereqPath); + + return hr; +} diff --git a/src/ext/Bal/wixiuiba/wixiuiba.def b/src/ext/Bal/wixiuiba/wixiuiba.def new file mode 100644 index 00000000..4488df94 --- /dev/null +++ b/src/ext/Bal/wixiuiba/wixiuiba.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 + BootstrapperApplicationCreate + BootstrapperApplicationDestroy diff --git a/src/ext/Bal/wixiuiba/wixiuiba.h b/src/ext/Bal/wixiuiba/wixiuiba.h new file mode 100644 index 00000000..76077f42 --- /dev/null +++ b/src/ext/Bal/wixiuiba/wixiuiba.h @@ -0,0 +1,13 @@ +#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. + + +struct INTERNAL_UI_BA_STATE +{ + BOOL fInitialized; + HINSTANCE hInstance; + LPWSTR sczAppBase; + HMODULE hPrereqModule; + PREQBA_DATA prereqData; + IBootstrapperApplication* pApplication; +}; diff --git a/src/ext/Bal/wixiuiba/wixiuiba.vcxproj b/src/ext/Bal/wixiuiba/wixiuiba.vcxproj new file mode 100644 index 00000000..dfadbb95 --- /dev/null +++ b/src/ext/Bal/wixiuiba/wixiuiba.vcxproj @@ -0,0 +1,72 @@ + + + + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + {0F73E566-925C-448D-99CB-3A7F5DF399C8} + DynamicLibrary + Unicode + wixiuiba + wixiuiba.def + + + + + + + ..\wixstdba\inc + shlwapi.lib + + + + + Create + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ext/Bal/wixlib/BalExtension_platform.wxi b/src/ext/Bal/wixlib/BalExtension_platform.wxi index b2750eee..5b0d78d0 100644 --- a/src/ext/Bal/wixlib/BalExtension_platform.wxi +++ b/src/ext/Bal/wixlib/BalExtension_platform.wxi @@ -18,6 +18,20 @@ + + + + + + + + + + + + + + diff --git a/src/ext/Bal/wixlib/bal.wixproj b/src/ext/Bal/wixlib/bal.wixproj index 627faecc..a7ae9a96 100644 --- a/src/ext/Bal/wixlib/bal.wixproj +++ b/src/ext/Bal/wixlib/bal.wixproj @@ -22,6 +22,9 @@ + + + diff --git a/src/ext/Bal/wixlib/wixiuiba.wxs b/src/ext/Bal/wixlib/wixiuiba.wxs new file mode 100644 index 00000000..5501a23f --- /dev/null +++ b/src/ext/Bal/wixlib/wixiuiba.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/ext/Bal/wixstdba/Resources/iuipreq.thm b/src/ext/Bal/wixstdba/Resources/iuipreq.thm new file mode 100644 index 00000000..5429b3d2 --- /dev/null +++ b/src/ext/Bal/wixstdba/Resources/iuipreq.thm @@ -0,0 +1,67 @@ + + + Segoe UI + Segoe UI + Segoe UI + Segoe UI + + + + + + + + + + + + #(loc.InstallLicenseTerms) + + + + + + + + + + + + + + + + + + + #(loc.FailureLogLinkText) + + + + + + + diff --git a/src/ext/Bal/wixstdba/Resources/iuipreq.wxl b/src/ext/Bal/wixstdba/Resources/iuipreq.wxl new file mode 100644 index 00000000..4afcd10f --- /dev/null +++ b/src/ext/Bal/wixstdba/Resources/iuipreq.wxl @@ -0,0 +1,34 @@ + + + + + + [WixBundleName] Prerequisite Setup + Prerequisite required for [WixBundleName] setup + [WixBundleName] setup + Are you sure you want to cancel? + Setup Help + /passive | /quiet - displays minimal UI with no prompts or displays no UI and + no prompts. By default UI and all prompts are displayed. + +/log log.txt - logs to a specific file. By default a log file is created in %TEMP%. + &Close + Click the "Accept and Install" button to accept the prerequisite <a href="#">license terms</a>. + &Accept and Install + &Decline + Setup Progress + Processing: + &Cancel + Prerequisite Setup Successful + Layout Successfully Completed + You must restart your computer before [WixBundleName] setup can continue. + &Restart + &Close + Setup Failed + Layout Failed + One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. + You must restart your computer to complete the rollback of the software. + &Restart + &Close + No action was taken as a system reboot is required. + diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 9aa58a28..1af1abeb 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -248,10 +248,20 @@ public: // IBootstrapperApplication if (m_fPrereq) { - // Pre-req BA should only show help or do an install (to launch the Managed BA which can then do the right action). - if (BOOTSTRAPPER_ACTION_HELP != m_command.action) + if (m_pPrereqData->fPerformLayout && BOOTSTRAPPER_ACTION_LAYOUT == m_command.action) { - m_command.action = BOOTSTRAPPER_ACTION_INSTALL; + // The parent BA has requested that this BA be in charge of layout. + m_fPrereq = FALSE; + } + else + { + m_fPreplanPrereqs = m_pPrereqData->fAlwaysInstallPrereqs; + + // Pre-req BA should only show help or do an install (to launch the parent BA which can then do the right action). + if (BOOTSTRAPPER_ACTION_HELP != m_command.action) + { + m_command.action = BOOTSTRAPPER_ACTION_INSTALL; + } } } else // maybe modify the action state if the bundle is or is not already installed. @@ -527,11 +537,11 @@ public: // IBootstrapperApplication ) { HRESULT hr = S_OK; - BOOL fPlannedPrereqs = WIXSTDBA_STATE_PLANNING_PREREQS == m_state; + BOOL fPreplannedPrereqs = WIXSTDBA_STATE_PLANNING_PREREQS == m_state; WIXSTDBA_STATE completedState = WIXSTDBA_STATE_PLANNED; BOOL fApply = TRUE; - if (fPlannedPrereqs) + if (fPreplannedPrereqs) { if (SUCCEEDED(hrStatus) && !m_fPrereqPackagePlanned) { @@ -547,7 +557,7 @@ public: // IBootstrapperApplication // 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); + ::PostMessageW(m_hWnd, WM_WIXSTDBA_SHOW_HELP, 0, 0); ExitFunction(); } @@ -562,7 +572,7 @@ public: // IBootstrapperApplication ExitFunction(); } - if (fPlannedPrereqs) + if (fPreplannedPrereqs) { // If the UI should be visible, display it now and hide the splash screen if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) @@ -1056,13 +1066,14 @@ public: // IBootstrapperApplication hr = __super::OnExecutePackageComplete(wzPackageId, hrStatus, restart, recommendation, pAction); - BAL_INFO_PACKAGE* pPackage = NULL; - HRESULT hrPrereq = BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); - if (SUCCEEDED(hrPrereq)) + if (m_fPrereq && BOOTSTRAPPER_APPLY_RESTART_NONE != restart) { + BAL_INFO_PACKAGE* pPackage = NULL; + HRESULT hrPrereq = BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); + // If the prerequisite required a restart (any restart) then do an immediate // restart to ensure that the bundle will get launched again post reboot. - if (m_fPrereq && pPackage->fPrereqPackage && BOOTSTRAPPER_APPLY_RESTART_NONE != restart) + if (SUCCEEDED(hrPrereq) && pPackage->fPrereqPackage) { *pAction = BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_RESTART; } @@ -2283,7 +2294,7 @@ private: // Okay, we're ready for packages now. pThis->SetState(WIXSTDBA_STATE_INITIALIZED, hr); - if (!pThis->m_fPreplanPrereqs && BOOTSTRAPPER_ACTION_HELP == pThis->m_command.action) + if (pThis->m_fPerformHelp && BOOTSTRAPPER_ACTION_HELP == pThis->m_command.action) { firstAction = WM_WIXSTDBA_SHOW_HELP; } @@ -4246,7 +4257,7 @@ public: m_hWnd = NULL; m_state = WIXSTDBA_STATE_INITIALIZING; - m_hrFinal = pPrereqData ? pPrereqData->hrHostInitialization : S_OK; + m_hrFinal = pPrereqData ? pPrereqData->hrFatalError : S_OK; m_restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE; m_fRestartRequired = FALSE; @@ -4269,7 +4280,8 @@ public: m_pPrereqData = pPrereqData; m_fPrereq = NULL != pPrereqData; - m_fPreplanPrereqs = m_fPrereq && m_pPrereqData->fAlwaysInstallPrereqs; + m_fPreplanPrereqs = FALSE; + m_fPerformHelp = !m_fPrereq || m_pPrereqData->fPerformHelp; m_fPrereqPackagePlanned = FALSE; m_fPrereqInstalled = FALSE; m_fPrereqSkipped = FALSE; @@ -4554,6 +4566,7 @@ private: PREQBA_DATA* m_pPrereqData; BOOL m_fPrereq; BOOL m_fPreplanPrereqs; + BOOL m_fPerformHelp; BOOL m_fPrereqPackagePlanned; BOOL m_fPrereqInstalled; BOOL m_fPrereqSkipped; diff --git a/src/ext/Bal/wixstdba/inc/preqba.h b/src/ext/Bal/wixstdba/inc/preqba.h index ed339730..25fa7105 100644 --- a/src/ext/Bal/wixstdba/inc/preqba.h +++ b/src/ext/Bal/wixstdba/inc/preqba.h @@ -4,8 +4,10 @@ struct PREQBA_DATA { - HRESULT hrHostInitialization; + HRESULT hrFatalError; BOOL fAlwaysInstallPrereqs; + BOOL fPerformHelp; + BOOL fPerformLayout; BOOL fCompleted; }; -- cgit v1.2.3-55-g6feb