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/api/burn/WixToolset.Mba.Core/IPackageInfo.cs | 5 + src/api/burn/WixToolset.Mba.Core/PackageInfo.cs | 34 + src/api/burn/balutil/balinfo.cpp | 27 + .../balutil/inc/BalBaseBootstrapperApplication.h | 14 +- src/api/burn/balutil/inc/balinfo.h | 10 + src/api/wix/WixToolset.Data/Burn/BurnConstants.cs | 3 + src/burn/engine/core.cpp | 6 +- src/burn/engine/splashscreen.cpp | 2 +- src/burn/engine/uithread.cpp | 2 +- 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 +- src/test/burn/TestData/Templates/Bundle.wxs | 4 + src/test/burn/TestData/TestData.proj | 2 +- .../ArchSpecificBundle/ArchSpecificBundle.wixproj | 17 + .../ArchSpecificBundle/ArchSpecificBundle.wxs | 13 + .../WixIuiBaTests/ArchSpecificBundle/Bundle.wxs | 63 ++ .../burn/TestData/WixIuiBaTests/DtfSamples.sln | 28 + .../WixIuiBaTests/EmbeddedUI/EmbeddedUI.config | 10 + .../WixIuiBaTests/EmbeddedUI/EmbeddedUI.csproj | 24 + .../EmbeddedUI/InstallProgressCounter.cs | 174 ++++ .../WixIuiBaTests/EmbeddedUI/SampleEmbeddedUI.cs | 141 ++++ .../WixIuiBaTests/EmbeddedUI/SetupWizard.xaml | 19 + .../WixIuiBaTests/EmbeddedUI/SetupWizard.xaml.cs | 154 ++++ .../WixIuiBaTests/EmbeddedUIBundle/Bundle.wxs | 63 ++ .../EmbeddedUIBundle/EmbeddedUIBundle.wixproj | 15 + .../EmbeddedUIBundle/EmbeddedUIBundle.wxs | 11 + .../EmbeddedUIPackage/EmbeddedUIPackage.wixproj | 16 + .../EmbeddedUIPackage/EmbeddedUIPackage.wxs | 13 + .../WixIuiBaTests/InternalUIBundle/Bundle.wxs | 63 ++ .../InternalUIBundle/InternalUIBundle.wixproj | 14 + .../InternalUIBundle/InternalUIBundle.wxs | 10 + .../InternalUIPackage/InternalUIPackage.wixproj | 16 + .../InternalUIPackage/InternalUIPackage.wxs | 24 + .../InternalUIarm64Package.wixproj | 14 + .../InternalUIarm64Package.wxs | 9 + .../InternalUIx64Package.wixproj | 14 + .../InternalUIx64Package/InternalUIx64Package.wxs | 9 + .../InternalUIx86Package.wixproj | 14 + .../InternalUIx86Package/InternalUIx86Package.wxs | 9 + .../WixIuiBaTests/ManagedCA/CustomAction.config | 10 + .../WixIuiBaTests/ManagedCA/ManagedCA.csproj | 16 + .../TestData/WixIuiBaTests/ManagedCA/SampleCA.cs | 125 +++ .../WixIuiBaTests/ManagedCA/testsub/testfile.txt | 1 + src/test/burn/WixTestTools/PackageVerifier.cs | 18 +- .../burn/WixToolsetTest.BurnE2E/WixIuiBaTests.cs | 144 ++++ src/test/dtf/Directory.Build.props | 11 - src/test/dtf/Directory.Build.targets | 6 - src/test/dtf/DtfE2ETests.sln | 28 - src/test/dtf/EmbeddedUI/EmbeddedUI.config | 10 - src/test/dtf/EmbeddedUI/EmbeddedUI.csproj | 24 - src/test/dtf/EmbeddedUI/InstallProgressCounter.cs | 174 ---- src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs | 141 ---- src/test/dtf/EmbeddedUI/SetupWizard.xaml | 19 - src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs | 154 ---- src/test/dtf/SampleCA/CustomAction.config | 10 - src/test/dtf/SampleCA/SampleCA.cs | 125 --- src/test/dtf/SampleCA/SampleCA.csproj | 16 - src/test/dtf/SampleCA/testsub/testfile.txt | 1 - src/test/test.cmd | 2 - .../Bind/GenerateManifestDataFromIRCommand.cs | 5 +- src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 2 - ...CreateBootstrapperApplicationManifestCommand.cs | 2 +- .../CreateBundleExtensionManifestCommand.cs | 2 +- .../ExtensibilityServices/BurnBackendHelper.cs | 5 +- .../WixToolset.Core.TestPackage/BundleExtractor.cs | 5 +- 103 files changed, 4053 insertions(+), 852 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 create mode 100644 src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/Bundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/DtfSamples.sln create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.config create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.csproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/InstallProgressCounter.cs create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SampleEmbeddedUI.cs create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml.cs create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/Bundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/Bundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wixproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wxs create mode 100644 src/test/burn/TestData/WixIuiBaTests/ManagedCA/CustomAction.config create mode 100644 src/test/burn/TestData/WixIuiBaTests/ManagedCA/ManagedCA.csproj create mode 100644 src/test/burn/TestData/WixIuiBaTests/ManagedCA/SampleCA.cs create mode 100644 src/test/burn/TestData/WixIuiBaTests/ManagedCA/testsub/testfile.txt create mode 100644 src/test/burn/WixToolsetTest.BurnE2E/WixIuiBaTests.cs delete mode 100644 src/test/dtf/Directory.Build.props delete mode 100644 src/test/dtf/Directory.Build.targets delete mode 100644 src/test/dtf/DtfE2ETests.sln delete mode 100644 src/test/dtf/EmbeddedUI/EmbeddedUI.config delete mode 100644 src/test/dtf/EmbeddedUI/EmbeddedUI.csproj delete mode 100644 src/test/dtf/EmbeddedUI/InstallProgressCounter.cs delete mode 100644 src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs delete mode 100644 src/test/dtf/EmbeddedUI/SetupWizard.xaml delete mode 100644 src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs delete mode 100644 src/test/dtf/SampleCA/CustomAction.config delete mode 100644 src/test/dtf/SampleCA/SampleCA.cs delete mode 100644 src/test/dtf/SampleCA/SampleCA.csproj delete mode 100644 src/test/dtf/SampleCA/testsub/testfile.txt diff --git a/src/api/burn/WixToolset.Mba.Core/IPackageInfo.cs b/src/api/burn/WixToolset.Mba.Core/IPackageInfo.cs index ee3be820..c6285f03 100644 --- a/src/api/burn/WixToolset.Mba.Core/IPackageInfo.cs +++ b/src/api/burn/WixToolset.Mba.Core/IPackageInfo.cs @@ -67,6 +67,11 @@ namespace WixToolset.Mba.Core /// string PrereqLicenseUrl { get; } + /// + /// See + /// + PrimaryPackageType PrimaryPackageType { get; } + /// /// /// diff --git a/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs b/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs index e400cbe4..3fa1b49e 100644 --- a/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs +++ b/src/api/burn/WixToolset.Mba.Core/PackageInfo.cs @@ -63,6 +63,37 @@ namespace WixToolset.Mba.Core ChainBundle, } + /// + /// Metadata for BAs like WixInternalUIBootstrapperApplication that only support one main package. + /// + public enum PrimaryPackageType + { + /// + /// Not a primary package. + /// + None, + + /// + /// The default package if no architecture specific package is available for the current architecture. + /// + Default, + + /// + /// The package to use on x86 machines. + /// + X86, + + /// + /// The package to use on x64 machines. + /// + X64, + + /// + /// The package to use on ARM64 machines. + /// + ARM64, + } + /// /// Default implementation of . /// @@ -116,6 +147,9 @@ namespace WixToolset.Mba.Core /// public string PrereqLicenseUrl { get; internal set; } + /// + public PrimaryPackageType PrimaryPackageType { get; internal set; } + /// public object CustomData { get; set; } diff --git a/src/api/burn/balutil/balinfo.cpp b/src/api/burn/balutil/balinfo.cpp index 5832281f..52a7f911 100644 --- a/src/api/burn/balutil/balinfo.cpp +++ b/src/api/burn/balutil/balinfo.cpp @@ -529,6 +529,33 @@ static HRESULT ParseBalPackageInfoFromXml( hr = XmlGetAttributeEx(pNode, L"DisplayInternalUICondition", &pPackage->sczDisplayInternalUICondition); ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get DisplayInternalUICondition setting for package."); + hr = XmlGetAttributeEx(pNode, L"PrimaryPackageType", &scz); + ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get PrimaryPackageType setting for package."); + + if (fXmlFound) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"default", -1)) + { + pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"x86", -1)) + { + pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X86; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"x64", -1)) + { + pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X64; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"arm64", -1)) + { + pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_ARM64; + } + else + { + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid value for WixBalPackageInfo/@PrimaryPackageType: %ls", scz); + } + } + ReleaseNullObject(pNode); } ExitOnFailure(hr, "Failed to parse all WixBalPackageInfo elements."); diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h index aa1ca56f..c3223bee 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h @@ -451,8 +451,8 @@ public: // IBootstrapperApplication __in BOOTSTRAPPER_ERROR_TYPE errorType, __in_z LPCWSTR wzPackageId, __in DWORD dwCode, - __in_z LPCWSTR /*wzError*/, - __in DWORD /*dwUIHint*/, + __in_z LPCWSTR wzError, + __in DWORD dwUIHint, __in DWORD /*cData*/, __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/, __in int /*nRecommendation*/, @@ -461,7 +461,15 @@ public: // IBootstrapperApplication { BalRetryErrorOccurred(wzPackageId, dwCode); - if (CheckCanceled()) + if (BOOTSTRAPPER_DISPLAY_EMBEDDED == m_display) + { + HRESULT hr = m_pEngine->SendEmbeddedError(dwCode, wzError, dwUIHint, pResult); + if (FAILED(hr)) + { + *pResult = IDERROR; + } + } + else if (CheckCanceled()) { *pResult = IDCANCEL; } diff --git a/src/api/burn/balutil/inc/balinfo.h b/src/api/burn/balutil/inc/balinfo.h index cd61f34d..818ff5ef 100644 --- a/src/api/burn/balutil/inc/balinfo.h +++ b/src/api/burn/balutil/inc/balinfo.h @@ -20,6 +20,15 @@ typedef enum BAL_INFO_PACKAGE_TYPE BAL_INFO_PACKAGE_TYPE_BUNDLE_CHAIN, } BAL_INFO_PACKAGE_TYPE; +typedef enum _BAL_INFO_PRIMARY_PACKAGE_TYPE +{ + BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE, + BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT, + BAL_INFO_PRIMARY_PACKAGE_TYPE_X86, + BAL_INFO_PRIMARY_PACKAGE_TYPE_X64, + BAL_INFO_PRIMARY_PACKAGE_TYPE_ARM64, +} BAL_INFO_PRIMARY_PACKAGE_TYPE; + typedef enum _BAL_INFO_RESTART { BAL_INFO_RESTART_UNKNOWN, @@ -54,6 +63,7 @@ typedef struct _BAL_INFO_PACKAGE BOOL fPrereqPackage; LPWSTR sczPrereqLicenseFile; LPWSTR sczPrereqLicenseUrl; + BAL_INFO_PRIMARY_PACKAGE_TYPE primaryPackageType; LPVOID pvCustomData; } BAL_INFO_PACKAGE; diff --git a/src/api/wix/WixToolset.Data/Burn/BurnConstants.cs b/src/api/wix/WixToolset.Data/Burn/BurnConstants.cs index 1ecccbd2..8eeb030b 100644 --- a/src/api/wix/WixToolset.Data/Burn/BurnConstants.cs +++ b/src/api/wix/WixToolset.Data/Burn/BurnConstants.cs @@ -14,6 +14,9 @@ namespace WixToolset.Data.Burn public const string BundleExtensionDataWixOutputStreamName = "wix-bextdata"; public const string BootstrapperApplicationDataWixOutputStreamName = "wix-badata.xml"; + public const string BootstrapperApplicationDataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; + public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; + public const string BootstrapperApplicationDataSymbolDefinitionTag = "WixBootstrapperApplicationData"; public const string BundleExtensionSearchSymbolDefinitionTag = "WixBundleExtensionSearch"; diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index 90bbc8f6..bfd979de 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -447,9 +447,6 @@ extern "C" HRESULT CorePlan( BURN_PACKAGE* pForwardCompatibleBundlePackage = NULL; BOOL fContinuePlanning = TRUE; // assume we won't skip planning due to dependencies. - hr = PlanSetVariables(action, &pEngineState->variables); - ExitOnFailure(hr, "Failed to update action."); - LogId(REPORT_STANDARD, MSG_PLAN_BEGIN, pEngineState->packages.cPackages, LoggingBurnActionToString(action)); fPlanBegan = TRUE; @@ -469,6 +466,9 @@ extern "C" HRESULT CorePlan( pEngineState->fPlanned = FALSE; PlanReset(&pEngineState->plan, &pEngineState->variables, &pEngineState->containers, &pEngineState->packages, &pEngineState->layoutPayloads); + hr = PlanSetVariables(action, &pEngineState->variables); + ExitOnFailure(hr, "Failed to update action."); + // Remember the overall action state in the plan since it shapes the changes // we make everywhere. pEngineState->plan.action = action; diff --git a/src/burn/engine/splashscreen.cpp b/src/burn/engine/splashscreen.cpp index ff61996f..7ba4e630 100644 --- a/src/burn/engine/splashscreen.cpp +++ b/src/burn/engine/splashscreen.cpp @@ -170,7 +170,7 @@ static DWORD WINAPI ThreadProc( SPLASHSCREEN_INFO splashScreenInfo = { }; WNDCLASSW wc = { }; - BOOL fRegistered = TRUE; + BOOL fRegistered = FALSE; BOOL fRet = FALSE; MSG msg = { }; diff --git a/src/burn/engine/uithread.cpp b/src/burn/engine/uithread.cpp index cdd8613c..fe1c21b8 100644 --- a/src/burn/engine/uithread.cpp +++ b/src/burn/engine/uithread.cpp @@ -98,7 +98,7 @@ static DWORD WINAPI ThreadProc( UITHREAD_INFO info = { }; WNDCLASSW wc = { }; - BOOL fRegistered = TRUE; + BOOL fRegistered = FALSE; HWND hWnd = NULL; BOOL fRet = FALSE; 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; }; diff --git a/src/test/burn/TestData/Templates/Bundle.wxs b/src/test/burn/TestData/Templates/Bundle.wxs index 612e67f5..c55f67a7 100644 --- a/src/test/burn/TestData/Templates/Bundle.wxs +++ b/src/test/burn/TestData/Templates/Bundle.wxs @@ -33,6 +33,10 @@ + + + + diff --git a/src/test/burn/TestData/TestData.proj b/src/test/burn/TestData/TestData.proj index 27bfb02b..0f7cbee9 100644 --- a/src/test/burn/TestData/TestData.proj +++ b/src/test/burn/TestData/TestData.proj @@ -4,7 +4,7 @@ - + diff --git a/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wixproj b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wixproj new file mode 100644 index 00000000..9e3cba8b --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wixproj @@ -0,0 +1,17 @@ + + + + Bundle + iui + {22B5ADAF-74D3-424A-8D32-9901FCF97E6D} + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wxs b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wxs new file mode 100644 index 00000000..71b8665f --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/ArchSpecificBundle.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/Bundle.wxs b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/Bundle.wxs new file mode 100644 index 00000000..195c159e --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ArchSpecificBundle/Bundle.wxs @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/DtfSamples.sln b/src/test/burn/TestData/WixIuiBaTests/DtfSamples.sln new file mode 100644 index 00000000..1e6c2082 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/DtfSamples.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedUI", "EmbeddedUI\EmbeddedUI.csproj", "{864B8C50-7895-4485-AC89-900D86FD8C0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCA", "ManagedCA\ManagedCA.csproj", "{8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.Build.0 = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.config b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.config new file mode 100644 index 00000000..700aff6f --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.csproj b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.csproj new file mode 100644 index 00000000..a6339220 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/EmbeddedUI.csproj @@ -0,0 +1,24 @@ + + + net35 + Sample managed embedded external UI + true + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/InstallProgressCounter.cs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/InstallProgressCounter.cs new file mode 100644 index 00000000..3d75081c --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/InstallProgressCounter.cs @@ -0,0 +1,174 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using WixToolset.Dtf.WindowsInstaller; + + /// + /// Tracks MSI progress messages and converts them to usable progress. + /// + public class InstallProgressCounter + { + private int total; + private int completed; + private int step; + private bool moveForward; + private bool enableActionData; + private int progressPhase; + private double scriptPhaseWeight; + + public InstallProgressCounter() : this(0.3) + { + } + + public InstallProgressCounter(double scriptPhaseWeight) + { + if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1)) + { + throw new ArgumentOutOfRangeException("scriptPhaseWeight"); + } + + this.scriptPhaseWeight = scriptPhaseWeight; + } + + /// + /// Gets a number between 0 and 1 that indicates the overall installation progress. + /// + public double Progress { get; private set; } + + public void ProcessMessage(InstallMessage messageType, Record messageRecord) + { + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. + + switch (messageType) + { + case InstallMessage.ActionStart: + if (this.enableActionData) + { + this.enableActionData = false; + } + break; + + case InstallMessage.ActionData: + if (this.enableActionData) + { + if (this.moveForward) + { + this.completed += this.step; + } + else + { + this.completed -= this.step; + } + + this.UpdateProgress(); + } + break; + + case InstallMessage.Progress: + this.ProcessProgressMessage(messageRecord); + break; + } + } + + private void ProcessProgressMessage(Record progressRecord) + { + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. + + if (progressRecord == null || progressRecord.FieldCount == 0) + { + return; + } + + int fieldCount = progressRecord.FieldCount; + int progressType = progressRecord.GetInteger(1); + string progressTypeString = String.Empty; + switch (progressType) + { + case 0: // Master progress reset + if (fieldCount < 4) + { + return; + } + + this.progressPhase++; + + this.total = progressRecord.GetInteger(2); + if (this.progressPhase == 1) + { + // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase + // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress + // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this + // "close" and deal with the rest. + this.total += 50; + } + + this.moveForward = (progressRecord.GetInteger(3) == 0); + this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max + this.enableActionData = false; + + this.UpdateProgress(); + break; + + case 1: // Action info + if (fieldCount < 3) + { + return; + } + + if (progressRecord.GetInteger(3) == 0) + { + this.enableActionData = false; + } + else + { + this.enableActionData = true; + this.step = progressRecord.GetInteger(2); + } + break; + + case 2: // Progress report + if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0) + { + return; + } + + if (this.moveForward) + { + this.completed += progressRecord.GetInteger(2); + } + else + { + this.completed -= progressRecord.GetInteger(2); + } + + this.UpdateProgress(); + break; + + case 3: // Progress total addition + this.total += progressRecord.GetInteger(2); + break; + } + } + + private void UpdateProgress() + { + if (this.progressPhase < 1 || this.total == 0) + { + this.Progress = 0; + } + else if (this.progressPhase == 1) + { + this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total; + } + else if (this.progressPhase == 2) + { + this.Progress = this.scriptPhaseWeight + + (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total; + } + else + { + this.Progress = 1; + } + } + } +} diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SampleEmbeddedUI.cs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SampleEmbeddedUI.cs new file mode 100644 index 00000000..ae86dc97 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SampleEmbeddedUI.cs @@ -0,0 +1,141 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Threading; + using System.Windows; + using System.Windows.Threading; + using WixToolset.Dtf.WindowsInstaller; + using Application = System.Windows.Application; + + public class SampleEmbeddedUI : IEmbeddedUI + { + private bool isMaintenance; + private Thread appThread; + private Application app; + private SetupWizard setupWizard; + private ManualResetEvent installStartEvent; + private ManualResetEvent installExitEvent; + + /// + /// Initializes the embedded UI. + /// + /// Handle to the installer which can be used to get and set properties. + /// The handle is only valid for the duration of this method call. + /// Path to the directory that contains all the files from the MsiEmbeddedUI table. + /// On entry, contains the current UI level for the installation. After this + /// method returns, the installer resets the UI level to the returned value of this parameter. + /// True if the embedded UI was successfully initialized; false if the installation + /// should continue without the embedded UI. + /// The installation was canceled by the user. + /// The embedded UI failed to initialize and + /// causes the installation to fail. + public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel) + { + if (session != null) + { + if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full) + { + // Don't show custom UI when the UI level is set to basic. + return false; + + // An embedded UI could display an alternate dialog sequence for reduced or + // basic modes, but it's not implemented here. We'll just fall back to the + // built-in MSI basic UI. + } + + if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase)) + { + // Don't show custom UI when uninstall was specified on the command line. + return false; + } + + this.isMaintenance = session.EvaluateCondition("Installed"); + } + + // Start the setup wizard on a separate thread. + this.installStartEvent = new ManualResetEvent(false); + this.installExitEvent = new ManualResetEvent(false); + this.appThread = new Thread(this.Run); + this.appThread.SetApartmentState(ApartmentState.STA); + this.appThread.Start(); + + // Wait for the setup wizard to either kickoff the install or prematurely exit. + int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent }); + if (waitResult == 1) + { + // The setup wizard set the exit event instead of the start event. Cancel the installation. + throw new InstallCanceledException(); + } + else + { + switch (this.setupWizard.Operation) + { + case SetupOperationType.Repair: + session["REINSTALL"] = "ALL"; + break; + case SetupOperationType.Uninstall: + session["REMOVE"] = "ALL"; + break; + } + + // Start the installation with a silenced internal UI. + // This "embedded external UI" will handle message types except for source resolution. + internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly; + return true; + } + } + + /// + /// Processes information and progress messages sent to the user interface. + /// + /// Message type. + /// Record that contains message data. + /// Message box buttons. + /// Message box icon. + /// Message box default button. + /// Result of processing the message. + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) + { + // Synchronously send the message to the setup wizard window on its thread. + object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send, + new Func(delegate() + { + return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton); + })); + return (MessageResult) result; + } + + /// + /// Shuts down the embedded UI at the end of the installation. + /// + /// + /// If the installation was canceled during initialization, this method will not be called. + /// If the installation was canceled or failed at any later point, this method will be called at the end. + /// + public void Shutdown() + { + // Wait for the user to exit the setup wizard. + this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal, + new Action(delegate() + { + this.setupWizard.EnableExit(); + })); + this.appThread.Join(); + } + + /// + /// Creates the setup wizard and runs the application thread. + /// + private void Run() + { + this.app = new Application(); + this.setupWizard = new SetupWizard(this.installStartEvent, this.isMaintenance); + this.setupWizard.InitializeComponent(); + this.app.Run(this.setupWizard); + this.installExitEvent.Set(); + } + } +} diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml new file mode 100644 index 00000000..97e406c2 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml.cs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml.cs new file mode 100644 index 00000000..a4345481 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUI/SetupWizard.xaml.cs @@ -0,0 +1,154 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Navigation; + using System.Windows.Shapes; + using WixToolset.Dtf.WindowsInstaller; + + public enum SetupOperationType + { + Install, + Repair, + Uninstall + } + + /// + /// Interaction logic for SetupWizard.xaml + /// + public partial class SetupWizard : Window + { + private bool isMaintenance; + private ManualResetEvent installStartEvent; + private InstallProgressCounter progressCounter; + private bool canceled; + + public SetupOperationType Operation { get; private set; } + + public SetupWizard(ManualResetEvent installStartEvent, bool isMaintenance) + { + this.installStartEvent = installStartEvent; + this.progressCounter = new InstallProgressCounter(0.5); + this.isMaintenance = isMaintenance; + + this.Loaded += this.SetupWizard_Loaded; + } + + private void SetupWizard_Loaded(object sender, RoutedEventArgs e) + { + this.Loaded -= this.SetupWizard_Loaded; + + if (this.isMaintenance) + { + this.installButton.Visibility = Visibility.Hidden; + this.repairButton.Visibility = Visibility.Visible; + this.uninstallButton.Visibility = Visibility.Visible; + } + } + + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) + { + try + { + this.progressCounter.ProcessMessage(messageType, messageRecord); + this.progressBar.Value = this.progressBar.Minimum + + this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum); + this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%"; + + switch (messageType) + { + case InstallMessage.Error: + case InstallMessage.Warning: + case InstallMessage.Info: + string message = String.Format("{0}: {1}", messageType, messageRecord); + this.LogMessage(message); + break; + } + + if (this.canceled) + { + this.canceled = false; + return MessageResult.Cancel; + } + } + catch (Exception ex) + { + this.LogMessage(ex.ToString()); + this.LogMessage(ex.StackTrace); + } + + return MessageResult.OK; + } + + private void LogMessage(string message) + { + this.messagesTextBox.Text += Environment.NewLine + message; + this.messagesTextBox.ScrollToEnd(); + } + + internal void EnableExit() + { + this.progressBar.Visibility = Visibility.Hidden; + this.progressLabel.Visibility = Visibility.Hidden; + this.cancelButton.Visibility = Visibility.Hidden; + this.exitButton.Visibility = Visibility.Visible; + } + + private void installButton_Click(object sender, RoutedEventArgs e) + { + this.Operation = SetupOperationType.Install; + this.StartInstall(); + } + + private void repairButton_Click(object sender, RoutedEventArgs e) + { + this.Operation = SetupOperationType.Repair; + this.StartInstall(); + } + + private void uninstallButton_Click(object sender, RoutedEventArgs e) + { + this.Operation = SetupOperationType.Uninstall; + this.StartInstall(); + } + + private void StartInstall() + { + this.installButton.Visibility = Visibility.Hidden; + this.repairButton.Visibility = Visibility.Hidden; + this.uninstallButton.Visibility = Visibility.Hidden; + this.progressBar.Visibility = Visibility.Visible; + this.progressLabel.Visibility = Visibility.Visible; + this.installStartEvent.Set(); + } + + private void exitButton_Click(object sender, RoutedEventArgs e) + { + this.Close(); + } + + private void cancelButton_Click(object sender, RoutedEventArgs e) + { + if (this.installButton.Visibility == Visibility.Visible) + { + this.Close(); + } + else + { + this.canceled = true; + this.cancelButton.IsEnabled = false; + } + } + } +} diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/Bundle.wxs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/Bundle.wxs new file mode 100644 index 00000000..195c159e --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/Bundle.wxs @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wixproj b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wixproj new file mode 100644 index 00000000..7c856200 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wixproj @@ -0,0 +1,15 @@ + + + + Bundle + iui + {5115D6AC-A1FE-40C6-BEB3-BEBB39E61579} + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wxs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wxs new file mode 100644 index 00000000..6adda9de --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIBundle/EmbeddedUIBundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wixproj b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wixproj new file mode 100644 index 00000000..0a62f38b --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wixproj @@ -0,0 +1,16 @@ + + + + true + {A6826B6D-2F3F-456D-BABF-1B8CDCE3AE68} + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wxs b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wxs new file mode 100644 index 00000000..e5d18c80 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/EmbeddedUIPackage/EmbeddedUIPackage.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/Bundle.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/Bundle.wxs new file mode 100644 index 00000000..195c159e --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/Bundle.wxs @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wixproj b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wixproj new file mode 100644 index 00000000..193d0ca6 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wixproj @@ -0,0 +1,14 @@ + + + + Bundle + iui + {62CB2BAE-129D-41D1-928B-5353FC542130} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wxs new file mode 100644 index 00000000..332f0b08 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIBundle/InternalUIBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wixproj b/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wixproj new file mode 100644 index 00000000..2744d4b3 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wixproj @@ -0,0 +1,16 @@ + + + + true + {7A0FE267-9EAE-4780-B41E-60C9E7044F53} + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wxs new file mode 100644 index 00000000..d5f30729 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIPackage/InternalUIPackage.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wixproj b/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wixproj new file mode 100644 index 00000000..e7d5b484 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wixproj @@ -0,0 +1,14 @@ + + + + arm64 + true + {357EBF93-039C-4378-8BCB-8B53F9B9F69A} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wxs new file mode 100644 index 00000000..c30cf607 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIarm64Package/InternalUIarm64Package.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wixproj b/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wixproj new file mode 100644 index 00000000..ff646914 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wixproj @@ -0,0 +1,14 @@ + + + + x64 + true + {2EBACF8F-BECD-401C-94F2-CFA2C9C3C07F} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wxs new file mode 100644 index 00000000..c30cf607 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIx64Package/InternalUIx64Package.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wixproj b/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wixproj new file mode 100644 index 00000000..4107ab44 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wixproj @@ -0,0 +1,14 @@ + + + + x64 + true + {1FA46DA0-5F83-459A-8A30-3E5A54D20A4D} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wxs b/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wxs new file mode 100644 index 00000000..c30cf607 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/InternalUIx86Package/InternalUIx86Package.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/ManagedCA/CustomAction.config b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/CustomAction.config new file mode 100644 index 00000000..700aff6f --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/CustomAction.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/ManagedCA/ManagedCA.csproj b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/ManagedCA.csproj new file mode 100644 index 00000000..866b7575 --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/ManagedCA.csproj @@ -0,0 +1,16 @@ + + + net20 + Sample managed custom actions + + + + + + + + + + + + diff --git a/src/test/burn/TestData/WixIuiBaTests/ManagedCA/SampleCA.cs b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/SampleCA.cs new file mode 100644 index 00000000..fc9f30fe --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/SampleCA.cs @@ -0,0 +1,125 @@ +namespace WixToolset.Samples +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Dtf.WindowsInstaller; + + public class SampleCA + { + [CustomAction] + public static ActionResult SampleCA1(Session session) + { + using (Record msgRec = new Record(0)) + { + msgRec[0] = "Hello from SampleCA1!" + + "\r\nCLR version is v" + Environment.Version; + session.Message(InstallMessage.Info, msgRec); + session.Message(InstallMessage.User, msgRec); + } + + session.Log("Testing summary info..."); + SummaryInfo summInfo = session.Database.SummaryInfo; + session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber); + session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime); + + string testProp = session["SampleCATest"]; + session.Log("Simple property test: [SampleCATest]={0}.", testProp); + + session.Log("Testing subdirectory extraction..."); + string testFilePath = "testsub\\SampleCAs.cs"; + if (!File.Exists(testFilePath)) + { + session.Log("Subdirectory extraction failed. File not found: " + testFilePath); + return ActionResult.Failure; + } + else + { + session.Log("Found file extracted in subdirectory."); + } + + session.Log("Testing record stream extraction..."); + string tempFile = null; + try + { + tempFile = Path.GetTempFileName(); + using (View binView = session.Database.OpenView( + "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " + + "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " + + "`CustomAction`.`Source` = `Binary`.`Name`")) + { + binView.Execute(); + using (Record binRec = binView.Fetch()) + { + binRec.GetStream(1, tempFile); + } + } + + session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length); + string binFileVersion = Installer.GetFileVersion(tempFile); + session.Log("CA binary file version: {0}", binFileVersion); + } + finally + { + if (tempFile != null && File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + + session.Log("Testing record stream reading..."); + using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'")) + { + binView2.Execute(); + using (Record binRec2 = binView2.Fetch()) + { + Stream stream = binRec2.GetStream("Data"); + string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd(); + session.Log("Test data: " + testData); + } + } + + session.Log("Listing components"); + using (View compView = session.Database.OpenView( + "SELECT `Component` FROM `Component`")) + { + compView.Execute(); + foreach (Record compRec in compView) + { + using (compRec) + { + session.Log("\t{0}", compRec["Component"]); + } + } + } + + session.Log("Testing the ability to access an external MSI database..."); + string tempDbFile = Path.GetTempFileName(); + using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect)) + { + // Just create an empty database. + } + using (Database tempDb2 = new Database(tempDbFile)) + { + // See if we can open and query the database. + IList tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); + session.Log("Found " + tables.Count + " tables in the newly created database."); + } + File.Delete(tempDbFile); + + return ActionResult.Success; + } + + [CustomAction("SampleCA2")] + public static ActionResult SampleCustomAction2(Session session) + { + using (Record msgRec = new Record(0)) + { + msgRec[0] = "Hello from SampleCA2!"; + session.Message(InstallMessage.Info, msgRec); + session.Message(InstallMessage.User, msgRec); + } + return ActionResult.UserExit; + } + } +} diff --git a/src/test/burn/TestData/WixIuiBaTests/ManagedCA/testsub/testfile.txt b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/testsub/testfile.txt new file mode 100644 index 00000000..8056aefd --- /dev/null +++ b/src/test/burn/TestData/WixIuiBaTests/ManagedCA/testsub/testfile.txt @@ -0,0 +1 @@ +test file for testing subdirectory support and binary stream reading diff --git a/src/test/burn/WixTestTools/PackageVerifier.cs b/src/test/burn/WixTestTools/PackageVerifier.cs index fd8378e0..4545b9ec 100644 --- a/src/test/burn/WixTestTools/PackageVerifier.cs +++ b/src/test/burn/WixTestTools/PackageVerifier.cs @@ -63,17 +63,27 @@ namespace WixTestTools return row.Value; } - public void VerifyInstalled(bool installed) + public bool IsInstalled() { var productCode = this.GetProperty("ProductCode"); - Assert.Equal(installed, MsiUtilities.IsProductInstalled(productCode)); + return MsiUtilities.IsProductInstalled(productCode); } - public void VerifyInstalledWithVersion(bool installed) + public bool IsInstalledWithVersion() { var productCode = this.GetProperty("ProductCode"); Version prodVersion = new Version(this.GetProperty("ProductVersion")); - Assert.Equal(installed, MsiUtilities.IsProductInstalledWithVersion(productCode, prodVersion)); + return MsiUtilities.IsProductInstalledWithVersion(productCode, prodVersion); + } + + public void VerifyInstalled(bool installed) + { + Assert.Equal(installed, this.IsInstalled()); + } + + public void VerifyInstalledWithVersion(bool installed) + { + Assert.Equal(installed, this.IsInstalledWithVersion()); } public void DeleteTestRegistryValue(string name) diff --git a/src/test/burn/WixToolsetTest.BurnE2E/WixIuiBaTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/WixIuiBaTests.cs new file mode 100644 index 00000000..18dd41db --- /dev/null +++ b/src/test/burn/WixToolsetTest.BurnE2E/WixIuiBaTests.cs @@ -0,0 +1,144 @@ +// 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.BurnE2E +{ + using WixTestTools; + using Xunit; + using Xunit.Abstractions; + + public class WixIuiBaTests : BurnE2ETests + { + public WixIuiBaTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + [RuntimeFact] + public void CanInstallArchitectureSpecificPrimaryPackage() + { + var defaultPackage = this.CreatePackageInstaller("InternalUIPackage"); + var x86Package = this.CreatePackageInstaller("InternalUIx86Package"); + var x64Package = this.CreatePackageInstaller("InternalUIx64Package"); + var arm64Package = this.CreatePackageInstaller("InternalUIarm64Package"); + var bundle = this.CreateBundleInstaller("ArchSpecificBundle"); + + defaultPackage.VerifyInstalled(false); + x86Package.VerifyInstalled(false); + x64Package.VerifyInstalled(false); + arm64Package.VerifyInstalled(false); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + + bundle.Install(); + bundle.VerifyRegisteredAndInPackageCache(); + defaultPackage.VerifyInstalled(false); + + var archSpecificInstalls = 0; + if (x86Package.IsInstalled()) + { + ++archSpecificInstalls; + } + + if (x64Package.IsInstalled()) + { + ++archSpecificInstalls; + } + + if (arm64Package.IsInstalled()) + { + ++archSpecificInstalls; + } + + Assert.Equal(1, archSpecificInstalls); + + bundle.Uninstall(); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + defaultPackage.VerifyInstalled(false); + x86Package.VerifyInstalled(false); + x64Package.VerifyInstalled(false); + arm64Package.VerifyInstalled(false); + } + + [RuntimeFact] + public void CanSilentlyInstallAndUninstallEmbeddedUIBundle() + { + var prereqPackage = this.CreatePackageInstaller("InternalUIPackage"); + var package = this.CreatePackageInstaller("EmbeddedUIPackage"); + var bundle = this.CreateBundleInstaller("EmbeddedUIBundle"); + + prereqPackage.VerifyInstalled(false); + package.VerifyInstalled(false); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + + bundle.Install(); + bundle.VerifyRegisteredAndInPackageCache(); + prereqPackage.VerifyInstalled(true); + package.VerifyInstalled(true); + + bundle.Uninstall(); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + prereqPackage.VerifyInstalled(true); + package.VerifyInstalled(false); + } + + [RuntimeFact] + public void CanSilentlyInstallAndUninstallInternalUIBundle() + { + var package = this.CreatePackageInstaller("InternalUIPackage"); + var bundle = this.CreateBundleInstaller("InternalUIBundle"); + + package.VerifyInstalled(false); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + + bundle.Install(); + bundle.VerifyRegisteredAndInPackageCache(); + package.VerifyInstalled(true); + + bundle.Uninstall(); + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + package.VerifyInstalled(false); + } + + // Manual test for EmbeddedUIBundle: + // 1. Double click EmbeddedUIBundle.exe. + // 2. Verify that the prereq BA came up and click the install button (allow elevation). + // 3. Verify that the prereq BA automatically closed after installing the prereq. + // 4. Verify that the MSI UI came up and click the install button. + // 5. After it's finished, click the exit button. + // 6. Verify that no other UI is shown and that everything was installed. + // 7. Double click EmbeddedUIBundle.exe (allow elevation). + // 8. Verify that the prereq BA did not come up. + // 9. Verify that the MSI UI came up and click the uninstall button. + // 10. After it's finished, click the exit button. + // 11. Verify that no other UI is shown and that everything was uninstalled except for the prereq which was permanent. + // 12. Uninstall InternalUIPackage to make sure the machine is clean for other tests. + + // Alternate EmbeddedUIBundle test - manually install InternalUIPackage first and verify that the prereq BA doesn't come up during install either. + + // Manual test for InternalUIBundle: + // 1. Double click InternalUIBundle.exe on a machine that will prompt for elevation. + // 2. Verify that the splash screen appeared but the prereq BA did not come up. + // 3. Verify that the elevation prompt came up immediately instead of flashing on the taskbar. (This is currently broken) + // 4. Allow elevation. + // 5. Verify that the MSI UI came up and the splash screen disappeared. + // 6. Accept the two CA messages and click the install button. + // 7. After it's finished, click the exit button. + // 8. Verify that no other UI is shown and that everything was installed. + // 9. Double click InternalUIBundle.exe (allow elevation). + // 10. Verify that the prereq BA did not come up. + // 11. Verify that the MSI UI came up and click the uninstall button. + // 12. After it's finished, click the exit button. + // 13. Verify that no other UI is shown and that everything was uninstalled to make sure the machine is clean for other tests. + + // Manual test for Help: + // 1. Run EmbeddedUIBundle.exe /help from the command line. + // 2. Verify that the prereq BA shows the help information without trying to install the prereqs. + + // Manual test for Layout: + // 1. Run EmbeddedUIBundle.exe /layout from an unelevated command line on a machine that will prompt for elevation. + // 2. Verify that the prereq BA performs the layout without requiring any input from the user. + // 3. Verify that it never prompted for elevation. + // 4. Click the exit button. + + // Manual test for Caching error: + // 1. Copy InternalUIBundle.exe to a separate folder so that it can't find InternalUIPackage.msi. + // 2. Attempt to install InternalUIBundle.exe (allow elevation). + // 3. Verify that the prereq BA comes up with the Failure page saying that a file couldn't be found. + } +} diff --git a/src/test/dtf/Directory.Build.props b/src/test/dtf/Directory.Build.props deleted file mode 100644 index 0035a9e6..00000000 --- a/src/test/dtf/Directory.Build.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - - IntegrationDtf - false - - - - - diff --git a/src/test/dtf/Directory.Build.targets b/src/test/dtf/Directory.Build.targets deleted file mode 100644 index 4e97b6ca..00000000 --- a/src/test/dtf/Directory.Build.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/dtf/DtfE2ETests.sln b/src/test/dtf/DtfE2ETests.sln deleted file mode 100644 index 39d8cf08..00000000 --- a/src/test/dtf/DtfE2ETests.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedUI", "EmbeddedUI\EmbeddedUI.csproj", "{864B8C50-7895-4485-AC89-900D86FD8C0D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleCA", "SampleCA\SampleCA.csproj", "{8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.Build.0 = Debug|Any CPU - {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/src/test/dtf/EmbeddedUI/EmbeddedUI.config b/src/test/dtf/EmbeddedUI/EmbeddedUI.config deleted file mode 100644 index 700aff6f..00000000 --- a/src/test/dtf/EmbeddedUI/EmbeddedUI.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj b/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj deleted file mode 100644 index a6339220..00000000 --- a/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - net35 - Sample managed embedded external UI - true - - - - - - - - - - - - - - - - - - - diff --git a/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs b/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs deleted file mode 100644 index 3d75081c..00000000 --- a/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs +++ /dev/null @@ -1,174 +0,0 @@ -namespace WixToolset.Samples.EmbeddedUI -{ - using System; - using WixToolset.Dtf.WindowsInstaller; - - /// - /// Tracks MSI progress messages and converts them to usable progress. - /// - public class InstallProgressCounter - { - private int total; - private int completed; - private int step; - private bool moveForward; - private bool enableActionData; - private int progressPhase; - private double scriptPhaseWeight; - - public InstallProgressCounter() : this(0.3) - { - } - - public InstallProgressCounter(double scriptPhaseWeight) - { - if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1)) - { - throw new ArgumentOutOfRangeException("scriptPhaseWeight"); - } - - this.scriptPhaseWeight = scriptPhaseWeight; - } - - /// - /// Gets a number between 0 and 1 that indicates the overall installation progress. - /// - public double Progress { get; private set; } - - public void ProcessMessage(InstallMessage messageType, Record messageRecord) - { - // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. - - switch (messageType) - { - case InstallMessage.ActionStart: - if (this.enableActionData) - { - this.enableActionData = false; - } - break; - - case InstallMessage.ActionData: - if (this.enableActionData) - { - if (this.moveForward) - { - this.completed += this.step; - } - else - { - this.completed -= this.step; - } - - this.UpdateProgress(); - } - break; - - case InstallMessage.Progress: - this.ProcessProgressMessage(messageRecord); - break; - } - } - - private void ProcessProgressMessage(Record progressRecord) - { - // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. - - if (progressRecord == null || progressRecord.FieldCount == 0) - { - return; - } - - int fieldCount = progressRecord.FieldCount; - int progressType = progressRecord.GetInteger(1); - string progressTypeString = String.Empty; - switch (progressType) - { - case 0: // Master progress reset - if (fieldCount < 4) - { - return; - } - - this.progressPhase++; - - this.total = progressRecord.GetInteger(2); - if (this.progressPhase == 1) - { - // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase - // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress - // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this - // "close" and deal with the rest. - this.total += 50; - } - - this.moveForward = (progressRecord.GetInteger(3) == 0); - this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max - this.enableActionData = false; - - this.UpdateProgress(); - break; - - case 1: // Action info - if (fieldCount < 3) - { - return; - } - - if (progressRecord.GetInteger(3) == 0) - { - this.enableActionData = false; - } - else - { - this.enableActionData = true; - this.step = progressRecord.GetInteger(2); - } - break; - - case 2: // Progress report - if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0) - { - return; - } - - if (this.moveForward) - { - this.completed += progressRecord.GetInteger(2); - } - else - { - this.completed -= progressRecord.GetInteger(2); - } - - this.UpdateProgress(); - break; - - case 3: // Progress total addition - this.total += progressRecord.GetInteger(2); - break; - } - } - - private void UpdateProgress() - { - if (this.progressPhase < 1 || this.total == 0) - { - this.Progress = 0; - } - else if (this.progressPhase == 1) - { - this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total; - } - else if (this.progressPhase == 2) - { - this.Progress = this.scriptPhaseWeight + - (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total; - } - else - { - this.Progress = 1; - } - } - } -} diff --git a/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs deleted file mode 100644 index ae86dc97..00000000 --- a/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace WixToolset.Samples.EmbeddedUI -{ - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Threading; - using System.Windows; - using System.Windows.Threading; - using WixToolset.Dtf.WindowsInstaller; - using Application = System.Windows.Application; - - public class SampleEmbeddedUI : IEmbeddedUI - { - private bool isMaintenance; - private Thread appThread; - private Application app; - private SetupWizard setupWizard; - private ManualResetEvent installStartEvent; - private ManualResetEvent installExitEvent; - - /// - /// Initializes the embedded UI. - /// - /// Handle to the installer which can be used to get and set properties. - /// The handle is only valid for the duration of this method call. - /// Path to the directory that contains all the files from the MsiEmbeddedUI table. - /// On entry, contains the current UI level for the installation. After this - /// method returns, the installer resets the UI level to the returned value of this parameter. - /// True if the embedded UI was successfully initialized; false if the installation - /// should continue without the embedded UI. - /// The installation was canceled by the user. - /// The embedded UI failed to initialize and - /// causes the installation to fail. - public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel) - { - if (session != null) - { - if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full) - { - // Don't show custom UI when the UI level is set to basic. - return false; - - // An embedded UI could display an alternate dialog sequence for reduced or - // basic modes, but it's not implemented here. We'll just fall back to the - // built-in MSI basic UI. - } - - if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase)) - { - // Don't show custom UI when uninstall was specified on the command line. - return false; - } - - this.isMaintenance = session.EvaluateCondition("Installed"); - } - - // Start the setup wizard on a separate thread. - this.installStartEvent = new ManualResetEvent(false); - this.installExitEvent = new ManualResetEvent(false); - this.appThread = new Thread(this.Run); - this.appThread.SetApartmentState(ApartmentState.STA); - this.appThread.Start(); - - // Wait for the setup wizard to either kickoff the install or prematurely exit. - int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent }); - if (waitResult == 1) - { - // The setup wizard set the exit event instead of the start event. Cancel the installation. - throw new InstallCanceledException(); - } - else - { - switch (this.setupWizard.Operation) - { - case SetupOperationType.Repair: - session["REINSTALL"] = "ALL"; - break; - case SetupOperationType.Uninstall: - session["REMOVE"] = "ALL"; - break; - } - - // Start the installation with a silenced internal UI. - // This "embedded external UI" will handle message types except for source resolution. - internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly; - return true; - } - } - - /// - /// Processes information and progress messages sent to the user interface. - /// - /// Message type. - /// Record that contains message data. - /// Message box buttons. - /// Message box icon. - /// Message box default button. - /// Result of processing the message. - public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, - MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) - { - // Synchronously send the message to the setup wizard window on its thread. - object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send, - new Func(delegate() - { - return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton); - })); - return (MessageResult) result; - } - - /// - /// Shuts down the embedded UI at the end of the installation. - /// - /// - /// If the installation was canceled during initialization, this method will not be called. - /// If the installation was canceled or failed at any later point, this method will be called at the end. - /// - public void Shutdown() - { - // Wait for the user to exit the setup wizard. - this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal, - new Action(delegate() - { - this.setupWizard.EnableExit(); - })); - this.appThread.Join(); - } - - /// - /// Creates the setup wizard and runs the application thread. - /// - private void Run() - { - this.app = new Application(); - this.setupWizard = new SetupWizard(this.installStartEvent, this.isMaintenance); - this.setupWizard.InitializeComponent(); - this.app.Run(this.setupWizard); - this.installExitEvent.Set(); - } - } -} diff --git a/src/test/dtf/EmbeddedUI/SetupWizard.xaml b/src/test/dtf/EmbeddedUI/SetupWizard.xaml deleted file mode 100644 index 97e406c2..00000000 --- a/src/test/dtf/EmbeddedUI/SetupWizard.xaml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs deleted file mode 100644 index a4345481..00000000 --- a/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs +++ /dev/null @@ -1,154 +0,0 @@ -namespace WixToolset.Samples.EmbeddedUI -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading; - using System.Windows; - using System.Windows.Controls; - using System.Windows.Data; - using System.Windows.Documents; - using System.Windows.Input; - using System.Windows.Media; - using System.Windows.Media.Imaging; - using System.Windows.Navigation; - using System.Windows.Shapes; - using WixToolset.Dtf.WindowsInstaller; - - public enum SetupOperationType - { - Install, - Repair, - Uninstall - } - - /// - /// Interaction logic for SetupWizard.xaml - /// - public partial class SetupWizard : Window - { - private bool isMaintenance; - private ManualResetEvent installStartEvent; - private InstallProgressCounter progressCounter; - private bool canceled; - - public SetupOperationType Operation { get; private set; } - - public SetupWizard(ManualResetEvent installStartEvent, bool isMaintenance) - { - this.installStartEvent = installStartEvent; - this.progressCounter = new InstallProgressCounter(0.5); - this.isMaintenance = isMaintenance; - - this.Loaded += this.SetupWizard_Loaded; - } - - private void SetupWizard_Loaded(object sender, RoutedEventArgs e) - { - this.Loaded -= this.SetupWizard_Loaded; - - if (this.isMaintenance) - { - this.installButton.Visibility = Visibility.Hidden; - this.repairButton.Visibility = Visibility.Visible; - this.uninstallButton.Visibility = Visibility.Visible; - } - } - - public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, - MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) - { - try - { - this.progressCounter.ProcessMessage(messageType, messageRecord); - this.progressBar.Value = this.progressBar.Minimum + - this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum); - this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%"; - - switch (messageType) - { - case InstallMessage.Error: - case InstallMessage.Warning: - case InstallMessage.Info: - string message = String.Format("{0}: {1}", messageType, messageRecord); - this.LogMessage(message); - break; - } - - if (this.canceled) - { - this.canceled = false; - return MessageResult.Cancel; - } - } - catch (Exception ex) - { - this.LogMessage(ex.ToString()); - this.LogMessage(ex.StackTrace); - } - - return MessageResult.OK; - } - - private void LogMessage(string message) - { - this.messagesTextBox.Text += Environment.NewLine + message; - this.messagesTextBox.ScrollToEnd(); - } - - internal void EnableExit() - { - this.progressBar.Visibility = Visibility.Hidden; - this.progressLabel.Visibility = Visibility.Hidden; - this.cancelButton.Visibility = Visibility.Hidden; - this.exitButton.Visibility = Visibility.Visible; - } - - private void installButton_Click(object sender, RoutedEventArgs e) - { - this.Operation = SetupOperationType.Install; - this.StartInstall(); - } - - private void repairButton_Click(object sender, RoutedEventArgs e) - { - this.Operation = SetupOperationType.Repair; - this.StartInstall(); - } - - private void uninstallButton_Click(object sender, RoutedEventArgs e) - { - this.Operation = SetupOperationType.Uninstall; - this.StartInstall(); - } - - private void StartInstall() - { - this.installButton.Visibility = Visibility.Hidden; - this.repairButton.Visibility = Visibility.Hidden; - this.uninstallButton.Visibility = Visibility.Hidden; - this.progressBar.Visibility = Visibility.Visible; - this.progressLabel.Visibility = Visibility.Visible; - this.installStartEvent.Set(); - } - - private void exitButton_Click(object sender, RoutedEventArgs e) - { - this.Close(); - } - - private void cancelButton_Click(object sender, RoutedEventArgs e) - { - if (this.installButton.Visibility == Visibility.Visible) - { - this.Close(); - } - else - { - this.canceled = true; - this.cancelButton.IsEnabled = false; - } - } - } -} diff --git a/src/test/dtf/SampleCA/CustomAction.config b/src/test/dtf/SampleCA/CustomAction.config deleted file mode 100644 index 700aff6f..00000000 --- a/src/test/dtf/SampleCA/CustomAction.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/dtf/SampleCA/SampleCA.cs b/src/test/dtf/SampleCA/SampleCA.cs deleted file mode 100644 index fc9f30fe..00000000 --- a/src/test/dtf/SampleCA/SampleCA.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace WixToolset.Samples -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Dtf.WindowsInstaller; - - public class SampleCA - { - [CustomAction] - public static ActionResult SampleCA1(Session session) - { - using (Record msgRec = new Record(0)) - { - msgRec[0] = "Hello from SampleCA1!" + - "\r\nCLR version is v" + Environment.Version; - session.Message(InstallMessage.Info, msgRec); - session.Message(InstallMessage.User, msgRec); - } - - session.Log("Testing summary info..."); - SummaryInfo summInfo = session.Database.SummaryInfo; - session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber); - session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime); - - string testProp = session["SampleCATest"]; - session.Log("Simple property test: [SampleCATest]={0}.", testProp); - - session.Log("Testing subdirectory extraction..."); - string testFilePath = "testsub\\SampleCAs.cs"; - if (!File.Exists(testFilePath)) - { - session.Log("Subdirectory extraction failed. File not found: " + testFilePath); - return ActionResult.Failure; - } - else - { - session.Log("Found file extracted in subdirectory."); - } - - session.Log("Testing record stream extraction..."); - string tempFile = null; - try - { - tempFile = Path.GetTempFileName(); - using (View binView = session.Database.OpenView( - "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " + - "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " + - "`CustomAction`.`Source` = `Binary`.`Name`")) - { - binView.Execute(); - using (Record binRec = binView.Fetch()) - { - binRec.GetStream(1, tempFile); - } - } - - session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length); - string binFileVersion = Installer.GetFileVersion(tempFile); - session.Log("CA binary file version: {0}", binFileVersion); - } - finally - { - if (tempFile != null && File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } - - session.Log("Testing record stream reading..."); - using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'")) - { - binView2.Execute(); - using (Record binRec2 = binView2.Fetch()) - { - Stream stream = binRec2.GetStream("Data"); - string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd(); - session.Log("Test data: " + testData); - } - } - - session.Log("Listing components"); - using (View compView = session.Database.OpenView( - "SELECT `Component` FROM `Component`")) - { - compView.Execute(); - foreach (Record compRec in compView) - { - using (compRec) - { - session.Log("\t{0}", compRec["Component"]); - } - } - } - - session.Log("Testing the ability to access an external MSI database..."); - string tempDbFile = Path.GetTempFileName(); - using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect)) - { - // Just create an empty database. - } - using (Database tempDb2 = new Database(tempDbFile)) - { - // See if we can open and query the database. - IList tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); - session.Log("Found " + tables.Count + " tables in the newly created database."); - } - File.Delete(tempDbFile); - - return ActionResult.Success; - } - - [CustomAction("SampleCA2")] - public static ActionResult SampleCustomAction2(Session session) - { - using (Record msgRec = new Record(0)) - { - msgRec[0] = "Hello from SampleCA2!"; - session.Message(InstallMessage.Info, msgRec); - session.Message(InstallMessage.User, msgRec); - } - return ActionResult.UserExit; - } - } -} diff --git a/src/test/dtf/SampleCA/SampleCA.csproj b/src/test/dtf/SampleCA/SampleCA.csproj deleted file mode 100644 index 866b7575..00000000 --- a/src/test/dtf/SampleCA/SampleCA.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - net20 - Sample managed custom actions - - - - - - - - - - - - diff --git a/src/test/dtf/SampleCA/testsub/testfile.txt b/src/test/dtf/SampleCA/testsub/testfile.txt deleted file mode 100644 index 8056aefd..00000000 --- a/src/test/dtf/SampleCA/testsub/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -test file for testing subdirectory support and binary stream reading diff --git a/src/test/test.cmd b/src/test/test.cmd index 1c2c154b..85deb61e 100644 --- a/src/test/test.cmd +++ b/src/test/test.cmd @@ -14,8 +14,6 @@ @call msi\test_msi.cmd %_C% %_T% || exit /b @call burn\test_burn.cmd %_C% %_T% || exit /b -msbuild -Restore dtf\DtfE2ETests.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:%_L%\dtfe2etests.binlog || exit /b - dotnet test wix -c %_C% --nologo -l "trx;LogFileName=%_L%\TestResults\WixToolsetTest.WixE2ETests.trx" || exit /b @popd diff --git a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 740a6e26..9eeafee3 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -10,6 +10,7 @@ namespace WixToolset.Core.Burn.Bind using WixToolset.Core.Burn.Bundles; using WixToolset.Core.Burn.ExtensibilityServices; using WixToolset.Data; + using WixToolset.Data.Burn; using WixToolset.Data.Symbols; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; @@ -191,10 +192,10 @@ namespace WixToolset.Core.Burn.Bind switch (customDataSymbol.Type) { case WixBundleCustomDataType.BootstrapperApplication: - writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); + writer.WriteStartElement(elementName, BurnConstants.BootstrapperApplicationDataNamespace); break; case WixBundleCustomDataType.BundleExtension: - writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); + writer.WriteStartElement(elementName, BurnConstants.BundleExtensionDataNamespace); break; default: throw new NotImplementedException(); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs index 25ad88cf..08ed7d56 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -24,10 +24,8 @@ namespace WixToolset.Core.Burn.Bundles public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; public const string BADataFileName = "BootstrapperApplicationData.xml"; - public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; - public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; // See WinNT.h for details about the PE format, including the // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 3167ccd3..6d8e0822 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -67,7 +67,7 @@ namespace WixToolset.Core.Burn.Bundles { writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); + writer.WriteStartElement("BootstrapperApplicationData", BurnConstants.BootstrapperApplicationDataNamespace); this.WriteBundleInfo(writer); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs index e587413e..ab6dcea1 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -53,7 +53,7 @@ namespace WixToolset.Core.Burn.Bundles { writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); - writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); + writer.WriteStartElement("BundleExtensionData", BurnConstants.BundleExtensionDataNamespace); this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs index 52f01f58..e267369f 100644 --- a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs @@ -9,6 +9,7 @@ namespace WixToolset.Core.Burn.ExtensibilityServices using System.Xml; using WixToolset.Core.Burn.Bundles; using WixToolset.Data; + using WixToolset.Data.Burn; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Data; @@ -160,7 +161,7 @@ namespace WixToolset.Core.Burn.ExtensibilityServices public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) { - this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); + this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnConstants.BootstrapperApplicationDataNamespace); } public void AddBundleExtensionData(string extensionId, string xml) @@ -172,7 +173,7 @@ namespace WixToolset.Core.Burn.ExtensibilityServices public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) { var manifestData = this.GetBundleExtensionManifestData(extensionId); - manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); + manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnConstants.BundleExtensionDataNamespace); } #endregion diff --git a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs index bd13a9b2..3cc98e3a 100644 --- a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs +++ b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -5,6 +5,7 @@ namespace WixToolset.Core.TestPackage using System.IO; using System.Xml; using WixToolset.Core.Burn.Bundles; + using WixToolset.Data.Burn; using WixToolset.Extensibility.Services; /// @@ -72,7 +73,7 @@ namespace WixToolset.Core.TestPackage public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) { var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); + namespaceManager.AddNamespace(prefix, BurnConstants.BootstrapperApplicationDataNamespace); return namespaceManager; } @@ -85,7 +86,7 @@ namespace WixToolset.Core.TestPackage public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) { var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); + namespaceManager.AddNamespace(prefix, BurnConstants.BundleExtensionDataNamespace); return namespaceManager; } -- cgit v1.2.3-55-g6feb