From 2222d3e0e988763b3a46d07583acfd5046949e36 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 5 Mar 2012 18:04:16 -0800 Subject: [PATCH] BobArnson: SFBUG:3288872 - add Control loc overrides to fi-FI localization. BobArnson: SFBUG:3302804 - prevent hidden variables from showing up in log. RobMen: SFBUG:3459756 - pause autoupdates during execution in Burn. Only allow one Burn to be executing at at time. RobMen: SFBUG:3488534 - launch maintenance mode when Bundle already installed. Add WixBundleInstalled and WixBundleAction built-in variables. RobMen: Improve logging about cached packages in Burn. RobMen: Disable check to allow only one Burn to be executing at a time due to embedded and related bundle handling. HeathS: Improved logging when source list registration failed. RobMen: Fix progress from embedded/related bundles in Burn. BobArnson: SFBUG:3439124 - Use Control localization in locutil to localize ThmUtil controls. RobMen: Implement WixBundleLastUsedSource in Burn to better handle prompt for source scenarios. RobMen: Fix Burn progress for repair/uninstall and layout. Improve Burn acquisition logging a little more. BobArnson: Switch from Hyperlink to Hypertext controls in WixStdBA themes. Localization string ids have changed. RobMen: SFBUG:3495355 - fix feature selection for superseded MSIs in Burn. HeathS: Changed default bundle provider key to UpgradeCode and Version. RobMen: SFBUG:3487838 - Fix per-user packages in Burn. RobMen: SFBUG:3196900 - Burn attempts rollback of MSI packages even if the cancel happened after MSI finalized installation. Add execute state to log during Burn apply. BobArnson: Fix SFBUG:3496347 - tell the rich-edit control how much text is coming. Add WixStandardBootstrapperApplication and WixManagedBootstrapperApplicationHost to BalUtilExtension to simplify authoring of BA options (instead of undiscoverable WixVariables). Add bal:WixStandardBootstrapperApplication/@SuppressOptionsUI to hide the Options button if a bundle installation directory shouldn't be supported. Add bal:WixStandardBootstrapperApplication/@SuppressDowngradeFailure to let attempted downgrades happen. This is useful for redistributable bundles where a later version major-upgrades prior versions and where a product that ships with a prior version shouldn't cause an error when a later version of the redistributable is already present. Support localized strings for license file and URL. RobMen: Default Burn packages and rollback boundaries to be vital. Add MsiPackage/@Visible to control MSI visibility in ARP. BobArnson: Use ClearType when available for ThmUtil controls. WixBuild: Version 3.6.2705.0 --HG-- branch : wix36 --- history.txt | 65 +++ inc/wixver.cs | 2 +- inc/wixver.h | 6 +- inc/wixver.targets | 4 +- inc/wixver.wxi | 2 +- src/burn/engine/apply.cpp | 231 +++++++-- src/burn/engine/apply.h | 4 + src/burn/engine/cache.cpp | 365 ++++++++++--- src/burn/engine/cache.h | 17 +- src/burn/engine/container.cpp | 11 +- src/burn/engine/container.h | 1 + src/burn/engine/core.cpp | 117 +++-- src/burn/engine/core.h | 14 +- src/burn/engine/downloadengine.cpp | 6 +- src/burn/engine/elevation.cpp | 178 ++++++- src/burn/engine/elevation.h | 12 + src/burn/engine/engine.cpp | 34 +- src/burn/engine/engine.mc | 102 +++- src/burn/engine/exeengine.cpp | 21 +- src/burn/engine/exeengine.h | 1 + src/burn/engine/logging.cpp | 26 + src/burn/engine/logging.h | 8 + src/burn/engine/msiengine.cpp | 131 +++-- src/burn/engine/msiengine.h | 3 +- src/burn/engine/mspengine.cpp | 9 +- src/burn/engine/msuengine.cpp | 3 +- src/burn/engine/msuengine.h | 1 + src/burn/engine/package.h | 10 +- src/burn/engine/plan.cpp | 60 ++- src/burn/engine/plan.h | 8 +- src/burn/engine/registration.cpp | 6 + src/burn/engine/variable.cpp | 431 ++++++++------- src/burn/engine/variable.h | 6 + src/chm/html/bundle_built_in_variables.htm | 4 + src/dutil/dirutil.cpp | 8 +- src/dutil/inc/locutil.h | 34 +- src/dutil/inc/thmutil.h | 2 +- src/dutil/locutil.cpp | 250 +++++++-- src/dutil/thmutil.cpp | 58 ++- src/ext/BalExtension/balutil/balcondition.cpp | 6 +- .../inc/BalBaseBootstrapperApplication.h | 8 - .../BalExtension/balutil/inc/balcondition.h | 2 +- src/ext/BalExtension/wixext/BalCompiler.cs | 233 +++++++++ src/ext/BalExtension/wixext/Xsd/bal.xsd | 106 ++++ src/ext/BalExtension/wixext/data/tables.xml | 7 + .../wixstdba/Resources/HyperlinkTheme.wxl | 17 +- .../wixstdba/Resources/HyperlinkTheme.xml | 18 +- .../wixstdba/Resources/RtfTheme.wxl | 28 +- .../wixstdba/Resources/RtfTheme.xml | 30 +- .../wixstdba/Resources/mbapreq.thm | 13 +- .../wixstdba/Resources/mbapreq.wxl | 7 +- .../WixStandardBootstrapperApplication.cpp | 179 +++++-- src/ext/UIExtension/wixlib/WixUI_fi-FI.wxl | 5 + src/wix/Binder.cs | 67 ++- src/wix/BundlePackageAttributes.cs | 25 + src/wix/Compiler.cs | 32 +- src/wix/CompilerCore.cs | 12 + src/wix/Data/messages.xml | 2 +- src/wix/Data/tables.xml | 3 +- src/wix/Localization.cs | 72 +++ src/wix/TableDefinition.cs | 2 +- src/wix/Wix.csproj | 1 + src/wix/Xsd/wix.xsd | 25 +- test/data/Burn/DependencyTests/A.wxs | 58 +++ test/data/Burn/DependencyTests/B.wxs | 60 +++ test/data/Burn/DependencyTests/BundleA.wxs | 37 ++ test/data/Burn/DependencyTests/BundleB.wxs | 37 ++ test/data/Burn/DependencyTests/BundleC.wxs | 31 ++ test/data/Burn/DependencyTests/BundleD.wxs | 29 ++ test/data/Burn/DependencyTests/BundleE.wxs | 32 ++ test/data/Burn/DependencyTests/BundleF.wxs | 30 ++ test/data/Burn/DependencyTests/BundleG.wxs | 29 ++ test/data/Burn/DependencyTests/C.wxs | 68 +++ .../DependencyTests}/PatchA.wxs | 12 +- test/data/Burn/FailureTests/A.wxs | 58 +++ test/data/Burn/FailureTests/B.wxs | 58 +++ test/data/Burn/FailureTests/BundleA.wxs | 31 ++ test/data/Burn/FailureTests/BundleB.wxs | 30 ++ test/data/Burn/RelatedBundleTests/BundleE.wxs | 33 ++ .../DependencyExtensionTests/BundleA.wxs | 45 -- .../DependencyExtensionTests/BundleB.wxs | 45 -- .../DependencyExtensionTests/BundleC.wxs | 39 -- .../DependencyExtensionTests/BundleD.wxs | 37 -- .../DependencyExtensionTests/BundleE.wxs | 40 -- .../DependencyExtensionTests/BundleF.wxs | 38 -- .../DependencyExtensionTests/BundleG.wxs | 37 -- .../DependencyExtensionTests/Fail.wxs | 57 -- test/src/Burn/TestBA/TestBA.cs | 13 + .../src/WixTests/Burn/Burn.DependencyTests.cs | 490 ++++++++++++++++++ test/src/WixTests/Burn/Burn.FailureTests.cs | 112 ++++ test/test.bat | 11 +- 91 files changed, 3741 insertions(+), 1007 deletions(-) create mode 100644 src/wix/BundlePackageAttributes.cs create mode 100644 test/data/Burn/DependencyTests/A.wxs create mode 100644 test/data/Burn/DependencyTests/B.wxs create mode 100644 test/data/Burn/DependencyTests/BundleA.wxs create mode 100644 test/data/Burn/DependencyTests/BundleB.wxs create mode 100644 test/data/Burn/DependencyTests/BundleC.wxs create mode 100644 test/data/Burn/DependencyTests/BundleD.wxs create mode 100644 test/data/Burn/DependencyTests/BundleE.wxs create mode 100644 test/data/Burn/DependencyTests/BundleF.wxs create mode 100644 test/data/Burn/DependencyTests/BundleG.wxs create mode 100644 test/data/Burn/DependencyTests/C.wxs rename test/data/{Extensions/DependencyExtension/DependencyExtensionTests => Burn/DependencyTests}/PatchA.wxs (60%) create mode 100644 test/data/Burn/FailureTests/A.wxs create mode 100644 test/data/Burn/FailureTests/B.wxs create mode 100644 test/data/Burn/FailureTests/BundleA.wxs create mode 100644 test/data/Burn/FailureTests/BundleB.wxs create mode 100644 test/data/Burn/RelatedBundleTests/BundleE.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleA.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleB.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleC.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleD.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleE.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleF.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleG.wxs delete mode 100644 test/data/Extensions/DependencyExtension/DependencyExtensionTests/Fail.wxs create mode 100644 test/src/WixTests/Burn/Burn.DependencyTests.cs create mode 100644 test/src/WixTests/Burn/Burn.FailureTests.cs diff --git a/history.txt b/history.txt index 1db32bd2..f85dd6e7 100644 --- a/history.txt +++ b/history.txt @@ -1873,3 +1873,68 @@ RobMen: SFBUG:3428804 - show restart message in wixstdba appropriately. WixBuild: Version 3.6.2627.0 +BobArnson: SFBUG:3288872 - add Control loc overrides to fi-FI localization. + +BobArnson: SFBUG:3302804 - prevent hidden variables from showing up in log. + +RobMen: SFBUG:3459756 - pause autoupdates during execution in Burn. + Only allow one Burn to be executing at at time. + +RobMen: SFBUG:3488534 - launch maintenance mode when Bundle already installed. + Add WixBundleInstalled and WixBundleAction built-in variables. + +RobMen: Improve logging about cached packages in Burn. + +RobMen: Disable check to allow only one Burn to be executing at a time due to + embedded and related bundle handling. + +HeathS: Improved logging when source list registration failed. + +RobMen: Fix progress from embedded/related bundles in Burn. + +BobArnson: SFBUG:3439124 - Use Control localization in locutil to localize + ThmUtil controls. + +RobMen: Implement WixBundleLastUsedSource in Burn to better handle prompt for + source scenarios. + +RobMen: Fix Burn progress for repair/uninstall and layout. + Improve Burn acquisition logging a little more. + +BobArnson: Switch from Hyperlink to Hypertext controls in WixStdBA themes. + Localization string ids have changed. + +RobMen: SFBUG:3495355 - fix feature selection for superseded MSIs in Burn. + +HeathS: Changed default bundle provider key to UpgradeCode and Version. + +RobMen: SFBUG:3487838 - Fix per-user packages in Burn. + +RobMen: SFBUG:3196900 - Burn attempts rollback of MSI packages even if the + cancel happened after MSI finalized installation. + Add execute state to log during Burn apply. + +BobArnson: Fix SFBUG:3496347 - tell the rich-edit control how much text is + coming. + Add WixStandardBootstrapperApplication and + WixManagedBootstrapperApplicationHost to BalUtilExtension to + simplify authoring of BA options (instead of undiscoverable + WixVariables). + Add bal:WixStandardBootstrapperApplication/@SuppressOptionsUI to + hide the Options button if a bundle installation directory + shouldn't be supported. + Add bal:WixStandardBootstrapperApplication/@SuppressDowngradeFailure + to let attempted downgrades happen. This is useful for + redistributable bundles where a later version major-upgrades + prior versions and where a product that ships with a prior + version shouldn't cause an error when a later version of the + redistributable is already present. + Support localized strings for license file and URL. + +RobMen: Default Burn packages and rollback boundaries to be vital. + Add MsiPackage/@Visible to control MSI visibility in ARP. + +BobArnson: Use ClearType when available for ThmUtil controls. + +WixBuild: Version 3.6.2705.0 + diff --git a/inc/wixver.cs b/inc/wixver.cs index d8bc3dd8..60d7b5d3 100644 --- a/inc/wixver.cs +++ b/inc/wixver.cs @@ -11,4 +11,4 @@ // [assembly: AssemblyVersion("3.0.0.0")] -[assembly: AssemblyFileVersion("3.6.2627.0")] +[assembly: AssemblyFileVersion("3.6.2705.0")] diff --git a/inc/wixver.h b/inc/wixver.h index d6127b27..3a4f4db4 100644 --- a/inc/wixver.h +++ b/inc/wixver.h @@ -1,17 +1,17 @@ /* ************************************************************************** * E:\delivery\Dev\wix36_public\inc\wixver.h - version information for project * - * 2012.2.27 - generated by build system. + * 2012.3.5 - generated by build system. * **************************************************************************/ #ifndef _VERSION_FILE_H_ #define _VERSION_FILE_H_ #define szVerMajorMinor "3.6" -#define szVerMajorMinorBuild "3.6.2627.0" +#define szVerMajorMinorBuild "3.6.2705.0" #define rmj 3 #define rmm 6 -#define rup 2627 +#define rup 2705 #define szVerName "Official Release" #endif // _VERSION_FILE_H_ diff --git a/inc/wixver.targets b/inc/wixver.targets index 99594d7a..19a9223f 100644 --- a/inc/wixver.targets +++ b/inc/wixver.targets @@ -3,9 +3,9 @@ 3 6 - 2627 + 2705 0 3.6 - 3.6.2627.0 + 3.6.2705.0 diff --git a/inc/wixver.wxi b/inc/wixver.wxi index 9b0effbe..1288ad53 100644 --- a/inc/wixver.wxi +++ b/inc/wixver.wxi @@ -8,7 +8,7 @@ - + diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index 13cae503..d940069a 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -58,12 +58,17 @@ static HRESULT ExtractContainer( __in_ecount(cExtractPayloads) BURN_EXTRACT_PAYLOAD* rgExtractPayloads, __in DWORD cExtractPayloads ); +static DWORD64 GetCacheActionSuccessProgress( + __in BURN_CACHE_ACTION* pCacheAction + ); static HRESULT LayoutBundle( __in BURN_USER_EXPERIENCE* pUX, __in HANDLE hPipe, __in_z LPCWSTR wzExecutableName, __in_z LPCWSTR wzLayoutDirectory, - __in_z LPCWSTR wzUnverifiedPath + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwSuccessfulCacheProgress, + __in DWORD64 qwTotalCacheSize ); static HRESULT AcquireContainerOrPayload( __in BURN_USER_EXPERIENCE* pUX, @@ -72,14 +77,16 @@ static HRESULT AcquireContainerOrPayload( __in_opt BURN_PACKAGE* pPackage, __in_opt BURN_PAYLOAD* pPayload, __in LPCWSTR wzDestinationPath, - __in DWORD64 qwTotalCacheSize, - __inout DWORD64* pqwTotalCacheProgress + __in DWORD64 qwSuccessfulCacheProgress, + __in DWORD64 qwTotalCacheSize ); static HRESULT LayoutOrCachePayload( __in BURN_USER_EXPERIENCE* pUX, __in HANDLE hPipe, __in_opt BURN_PACKAGE* pPackage, __in BURN_PAYLOAD* pPayload, + __in DWORD64 qwSuccessfullyCacheProgress, + __in DWORD64 qwTotalCacheSize, __in_z_opt LPCWSTR wzLayoutDirectory, __in_z LPCWSTR wzUnverifiedPath, __in BOOL fMove, @@ -215,6 +222,34 @@ static HRESULT ExecutePackageComplete( // function definitions +extern "C" HRESULT ApplyLock( + __in BOOL /*fPerMachine*/, + __out HANDLE* /*phLock*/ + ) +{ + HRESULT hr = S_OK; +#if 0 // eventually figure out the correct way to support this. In its current form, embedded bundles (including related bundles) are hosed. + DWORD er = ERROR_SUCCESS; + HANDLE hLock = NULL; + + hLock = ::CreateMutexW(NULL, TRUE, fPerMachine ? L"Global\\WixBurnExecutionLock" : L"Local\\WixBurnExecutionLock"); + ExitOnNullWithLastError(hLock, hr, "Failed to create lock."); + + er = ::GetLastError(); + if (ERROR_ALREADY_EXISTS == er) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING)); + } + + *phLock = hLock; + hLock = NULL; + +LExit: + ReleaseHandle(hLock); +#endif + return hr; +} + extern "C" HRESULT ApplyRegister( __in BURN_ENGINE_STATE* pEngineState ) @@ -243,7 +278,7 @@ extern "C" HRESULT ApplyRegister( } else // need to complete registration on the machine. { - hr = CacheCalculateBundleWorkingPath(pEngineState->registration.fPerMachine, pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &sczEngineWorkingPath); + hr = CacheCalculateBundleWorkingPath(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &sczEngineWorkingPath); ExitOnFailure(hr, "Failed to calculate working path for engine."); // begin new session @@ -320,7 +355,7 @@ extern "C" HRESULT ApplyCache( BOOL fRetry = FALSE; DWORD iRetryAction = BURN_PLAN_INVALID_ACTION_INDEX; BURN_PACKAGE* pStartedPackage = NULL; - DWORD64 qwCacheProgress = 0; + DWORD64 qwSuccessfulCachedProgress = 0; // Allow us to retry and skip packages. DWORD iPackageStartAction = BURN_PLAN_INVALID_ACTION_INDEX; @@ -354,6 +389,8 @@ extern "C" HRESULT ApplyCache( } else // skip the action. { + // If we skipped it, we can assume it was successful so add the action's progress now. + qwSuccessfulCachedProgress += GetCacheActionSuccessProgress(pCacheAction); continue; } } @@ -365,9 +402,10 @@ extern "C" HRESULT ApplyCache( break; case BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE: - hr = LayoutBundle(pUX, hPipe, pCacheAction->bundleLayout.sczExecutableName, pCacheAction->bundleLayout.sczLayoutDirectory, pCacheAction->bundleLayout.sczUnverifiedPath); + hr = LayoutBundle(pUX, hPipe, pCacheAction->bundleLayout.sczExecutableName, pCacheAction->bundleLayout.sczLayoutDirectory, pCacheAction->bundleLayout.sczUnverifiedPath, qwSuccessfulCachedProgress, pPlan->qwCacheSizeTotal); if (SUCCEEDED(hr)) { + qwSuccessfulCachedProgress += pCacheAction->bundleLayout.qwBundleSize; ++(*pcOverallProgressTicks); hr = ReportOverallProgressTicks(pUX, FALSE, pPlan->cOverallProgressTicksTotal, *pcOverallProgressTicks); @@ -392,8 +430,12 @@ extern "C" HRESULT ApplyCache( break; case BURN_CACHE_ACTION_TYPE_ACQUIRE_CONTAINER: - hr = AcquireContainerOrPayload(pUX, pVariables, pCacheAction->resolveContainer.pContainer, NULL, NULL, pCacheAction->resolveContainer.sczUnverifiedPath, pPlan->qwCacheSizeTotal, &qwCacheProgress); - if (FAILED(hr)) + hr = AcquireContainerOrPayload(pUX, pVariables, pCacheAction->resolveContainer.pContainer, NULL, NULL, pCacheAction->resolveContainer.sczUnverifiedPath, qwSuccessfulCachedProgress, pPlan->qwCacheSizeTotal); + if (SUCCEEDED(hr)) + { + qwSuccessfulCachedProgress += pCacheAction->resolveContainer.pContainer->qwFileSize; + } + else { LogErrorId(hr, MSG_FAILED_ACQUIRE_CONTAINER, pCacheAction->resolveContainer.pContainer->sczId, pCacheAction->resolveContainer.sczUnverifiedPath, NULL); } @@ -404,53 +446,63 @@ extern "C" HRESULT ApplyCache( // action is still being skipped then skip this action. if (BURN_PLAN_INVALID_ACTION_INDEX != pCacheAction->extractContainer.iSkipUntilAcquiredByAction && pPlan->rgCacheActions[pCacheAction->extractContainer.iSkipUntilAcquiredByAction].fSkipUntilRetried) { + // TODO: Note there is a potential bug here where retry can cause this cost to be added multiple times. + qwSuccessfulCachedProgress += pCacheAction->extractContainer.qwTotalExtractSize; break; } hr = ExtractContainer(pUX, pCacheAction->extractContainer.pContainer, pCacheAction->extractContainer.sczContainerUnverifiedPath, pCacheAction->extractContainer.rgPayloads, pCacheAction->extractContainer.cPayloads); - if (FAILED(hr)) + if (SUCCEEDED(hr)) + { + qwSuccessfulCachedProgress += pCacheAction->extractContainer.qwTotalExtractSize; + } + else { LogErrorId(hr, MSG_FAILED_EXTRACT_CONTAINER, pCacheAction->extractContainer.pContainer->sczId, pCacheAction->extractContainer.sczContainerUnverifiedPath, NULL); } break; case BURN_CACHE_ACTION_TYPE_ACQUIRE_PAYLOAD: - hr = AcquireContainerOrPayload(pUX, pVariables, NULL, pCacheAction->resolvePayload.pPackage, pCacheAction->resolvePayload.pPayload, pCacheAction->resolvePayload.sczUnverifiedPath, pPlan->qwCacheSizeTotal, &qwCacheProgress); - if (FAILED(hr)) + hr = AcquireContainerOrPayload(pUX, pVariables, NULL, pCacheAction->resolvePayload.pPackage, pCacheAction->resolvePayload.pPayload, pCacheAction->resolvePayload.sczUnverifiedPath, qwSuccessfulCachedProgress, pPlan->qwCacheSizeTotal); + if (SUCCEEDED(hr)) + { + qwSuccessfulCachedProgress += pCacheAction->resolvePayload.pPayload->qwFileSize; + } + else { LogErrorId(hr, MSG_FAILED_ACQUIRE_PAYLOAD, pCacheAction->resolvePayload.pPayload->sczKey, pCacheAction->resolvePayload.sczUnverifiedPath, NULL); } break; case BURN_CACHE_ACTION_TYPE_CACHE_PAYLOAD: - hr = LayoutOrCachePayload(pUX, pCacheAction->cachePayload.pPackage->fPerMachine ? hPipe : INVALID_HANDLE_VALUE, pCacheAction->cachePayload.pPackage, pCacheAction->cachePayload.pPayload, NULL, pCacheAction->cachePayload.sczUnverifiedPath, pCacheAction->cachePayload.fMove, pCacheAction->cachePayload.cTryAgainAttempts, &fRetryPayload); + hr = LayoutOrCachePayload(pUX, pCacheAction->cachePayload.pPackage->fPerMachine ? hPipe : INVALID_HANDLE_VALUE, pCacheAction->cachePayload.pPackage, pCacheAction->cachePayload.pPayload, qwSuccessfulCachedProgress, pPlan->qwCacheSizeTotal, NULL, pCacheAction->cachePayload.sczUnverifiedPath, pCacheAction->cachePayload.fMove, pCacheAction->cachePayload.cTryAgainAttempts, &fRetryPayload); if (FAILED(hr)) { LogErrorId(hr, MSG_FAILED_CACHE_PAYLOAD, pCacheAction->cachePayload.pPayload->sczKey, pCacheAction->cachePayload.sczUnverifiedPath, NULL); - } - if (fRetryPayload) - { - pRetryPayload = pCacheAction->cachePayload.pPayload; - iRetryPayloadAction = pCacheAction->cachePayload.iTryAgainAction; + if (fRetryPayload) + { + pRetryPayload = pCacheAction->cachePayload.pPayload; + iRetryPayloadAction = pCacheAction->cachePayload.iTryAgainAction; - ++pCacheAction->cachePayload.cTryAgainAttempts; + ++pCacheAction->cachePayload.cTryAgainAttempts; + } } break; case BURN_CACHE_ACTION_TYPE_LAYOUT_PAYLOAD: - hr = LayoutOrCachePayload(pUX, hPipe, pCacheAction->layoutPayload.pPackage, pCacheAction->layoutPayload.pPayload, pCacheAction->layoutPayload.sczLayoutDirectory, pCacheAction->layoutPayload.sczUnverifiedPath, pCacheAction->layoutPayload.fMove, pCacheAction->layoutPayload.cTryAgainAttempts, &fRetryPayload); + hr = LayoutOrCachePayload(pUX, hPipe, pCacheAction->layoutPayload.pPackage, pCacheAction->layoutPayload.pPayload, qwSuccessfulCachedProgress, pPlan->qwCacheSizeTotal, pCacheAction->layoutPayload.sczLayoutDirectory, pCacheAction->layoutPayload.sczUnverifiedPath, pCacheAction->layoutPayload.fMove, pCacheAction->layoutPayload.cTryAgainAttempts, &fRetryPayload); if (FAILED(hr)) { LogErrorId(hr, MSG_FAILED_LAYOUT_PAYLOAD, pCacheAction->layoutPayload.pPayload->sczKey, pCacheAction->layoutPayload.sczLayoutDirectory, pCacheAction->layoutPayload.sczUnverifiedPath); - } - if (fRetryPayload) - { - pRetryPayload = pCacheAction->layoutPayload.pPayload; - iRetryPayloadAction = pCacheAction->layoutPayload.iTryAgainAction; + if (fRetryPayload) + { + pRetryPayload = pCacheAction->layoutPayload.pPayload; + iRetryPayloadAction = pCacheAction->layoutPayload.iTryAgainAction; - ++pCacheAction->layoutPayload.cTryAgainAttempts; + ++pCacheAction->layoutPayload.cTryAgainAttempts; + } } break; @@ -493,6 +545,12 @@ extern "C" HRESULT ApplyCache( LogErrorId(hr, MSG_APPLY_RETRYING_PAYLOAD, pRetryPayload->sczKey, NULL, NULL); + // Reduce the successful progress since we're retrying the payload. + BURN_CACHE_ACTION* pRetryCacheAction = pPlan->rgCacheActions + iRetryPayloadAction; + DWORD64 qwRetryCacheActionSuccessProgress = GetCacheActionSuccessProgress(pRetryCacheAction); + Assert(qwSuccessfulCachedProgress >= qwRetryCacheActionSuccessProgress); + qwSuccessfulCachedProgress -= qwRetryCacheActionSuccessProgress; + iRetryAction = iRetryPayloadAction; fRetry = TRUE; } @@ -718,19 +776,42 @@ static HRESULT ExtractContainer( return hr; } +static DWORD64 GetCacheActionSuccessProgress( + __in BURN_CACHE_ACTION* pCacheAction + ) +{ + switch (pCacheAction->type) + { + case BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE: + return pCacheAction->bundleLayout.qwBundleSize; + + case BURN_CACHE_ACTION_TYPE_EXTRACT_CONTAINER: + return pCacheAction->extractContainer.qwTotalExtractSize; + + case BURN_CACHE_ACTION_TYPE_ACQUIRE_CONTAINER: + return pCacheAction->resolveContainer.pContainer->qwFileSize; + + case BURN_CACHE_ACTION_TYPE_ACQUIRE_PAYLOAD: + return pCacheAction->resolvePayload.pPayload->qwFileSize; + } + + return 0; +} + static HRESULT LayoutBundle( __in BURN_USER_EXPERIENCE* pUX, __in HANDLE hPipe, __in_z LPCWSTR wzExecutableName, __in_z LPCWSTR wzLayoutDirectory, - __in_z LPCWSTR wzUnverifiedPath + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwSuccessfulCacheProgress, + __in DWORD64 qwTotalCacheSize ) { HRESULT hr = S_OK; LPWSTR sczBundlePath = NULL; LPWSTR sczDestinationPath = NULL; int nEquivalentPaths = 0; - LONGLONG llBundleSize = 0; BURN_CACHE_ACQUIRE_PROGRESS_CONTEXT progress = { }; BOOL fRetry = FALSE; @@ -749,12 +830,9 @@ static HRESULT LayoutBundle( ExitFunction1(hr = S_OK); } - hr = FileSize(sczBundlePath, &llBundleSize); - ExitOnFailure1(hr, "Failed to get the size of the bundle: %ls", sczBundlePath); - progress.pUX = pUX; - progress.qwCacheProgress = 0; - progress.qwTotalCacheSize = static_cast(llBundleSize); + progress.qwCacheProgress = qwSuccessfulCacheProgress; + progress.qwTotalCacheSize = qwTotalCacheSize; do { @@ -832,8 +910,8 @@ static HRESULT AcquireContainerOrPayload( __in_opt BURN_PACKAGE* pPackage, __in_opt BURN_PAYLOAD* pPayload, __in LPCWSTR wzDestinationPath, - __in DWORD64 qwTotalCacheSize, - __inout DWORD64* pqwCacheProgress + __in DWORD64 qwSuccessfulCacheProgress, + __in DWORD64 qwTotalCacheSize ) { AssertSz(pContainer || pPayload, "Must provide a container or a payload."); @@ -842,6 +920,7 @@ static HRESULT AcquireContainerOrPayload( int nEquivalentPaths = 0; LPCWSTR wzPackageOrContainerId = pContainer ? pContainer->sczId : pPackage ? pPackage->sczId : NULL; LPCWSTR wzPayloadId = pPayload ? pPayload->sczKey : NULL; + LPCWSTR wzRelativePath = pContainer ? pContainer->sczFilePath : pPayload->sczFilePath; LPWSTR sczSourceFullPath = NULL; BURN_CACHE_ACQUIRE_PROGRESS_CONTEXT progress = { }; BOOL fRetry = FALSE; @@ -850,32 +929,26 @@ static HRESULT AcquireContainerOrPayload( progress.pPackage = pPackage; progress.pPayload = pPayload; progress.pUX = pUX; - progress.qwCacheProgress = *pqwCacheProgress; + progress.qwCacheProgress = qwSuccessfulCacheProgress; progress.qwTotalCacheSize = qwTotalCacheSize; do { - BOOL fCopy = FALSE; - BOOL fDownload = FALSE; - LPCWSTR wzDownloadUrl = pContainer ? pContainer->downloadSource.sczUrl : pPayload->downloadSource.sczUrl; LPCWSTR wzSourcePath = pContainer ? pContainer->sczSourcePath : pPayload->sczSourcePath; - if (PathIsAbsolute(wzSourcePath)) - { - hr = StrAllocString(&sczSourceFullPath, wzSourcePath, 0); - ExitOnFailure(hr, "Failed to copy full path."); - } - else - { - hr = CacheGetOriginalSourcePath(pVariables, wzSourcePath, &sczSourceFullPath); - ExitOnFailure(hr, "Failed to create full path relative to original source."); - } + + BOOL fFoundLocal = FALSE; + BOOL fCopy = FALSE; + BOOL fDownload = FALSE; fRetry = FALSE; progress.fCancel = FALSE; + hr = CacheFindLocalSource(wzSourcePath, pVariables, &fFoundLocal, &sczSourceFullPath); + ExitOnFailure(hr, "Failed to search local source."); + // If the file exists locally, copy it. - if (FileExistsEx(sczSourceFullPath, NULL)) + if (fFoundLocal) { // If the source path and destination path are different, do the copy (otherwise there's no point). hr = PathCompare(sczSourceFullPath, wzDestinationPath, &nEquivalentPaths); @@ -885,6 +958,9 @@ static HRESULT AcquireContainerOrPayload( } else // can't find the file locally so prompt for source. { + DWORD dwLogId = pContainer ? (wzPayloadId ? MSG_PROMPT_CONTAINER_PAYLOAD_SOURCE : MSG_PROMPT_CONTAINER_SOURCE) : pPackage ? MSG_PROMPT_PACKAGE_PAYLOAD_SOURCE : MSG_PROMPT_BUNDLE_PAYLOAD_SOURCE; + LogId(REPORT_STANDARD, dwLogId, wzPackageOrContainerId ? wzPackageOrContainerId : L"", wzPayloadId ? wzPayloadId : L"", sczSourceFullPath); + hr = PromptForSource(pUX, wzPackageOrContainerId, wzPayloadId, sczSourceFullPath, wzDownloadUrl, &fRetry, &fDownload); // If the BA requested download then ensure a download url is available (it may have been set @@ -910,6 +986,12 @@ static HRESULT AcquireContainerOrPayload( hr = CopyPayload(&progress, sczSourceFullPath, wzDestinationPath); // Error handling happens after sending complete message to BA. + + // We successfully copied from a source location, set that as the last used source. + if (SUCCEEDED(hr)) + { + CacheSetLastUsedSource(pVariables, sczSourceFullPath, wzRelativePath); + } } else if (fDownload) { @@ -938,11 +1020,6 @@ static HRESULT AcquireContainerOrPayload( LExit: ReleaseStr(sczSourceFullPath); - if (SUCCEEDED(hr)) - { - (*pqwCacheProgress) += pContainer ? pContainer->qwFileSize : pPayload->qwFileSize; - } - return hr; } @@ -951,6 +1028,8 @@ static HRESULT LayoutOrCachePayload( __in HANDLE hPipe, __in_opt BURN_PACKAGE* pPackage, __in BURN_PAYLOAD* pPayload, + __in DWORD64 qwSuccessfulCachedProgress, + __in DWORD64 qwTotalCacheSize, __in_z_opt LPCWSTR wzLayoutDirectory, __in_z LPCWSTR wzUnverifiedPath, __in BOOL fMove, @@ -959,6 +1038,18 @@ static HRESULT LayoutOrCachePayload( ) { HRESULT hr = S_OK; + LARGE_INTEGER liPayloadSize = { }; + LARGE_INTEGER liZero = { }; + BURN_CACHE_ACQUIRE_PROGRESS_CONTEXT progress = { }; + + Assert(qwSuccessfulCachedProgress >= pPayload->qwFileSize); + + liPayloadSize.QuadPart = pPayload->qwFileSize; + progress.pPackage = pPackage; + progress.pPayload = pPayload; + progress.pUX = pUX; + progress.qwCacheProgress = qwSuccessfulCachedProgress - pPayload->qwFileSize; // remove the payload size, since it was marked successful thus included in the successful size already. + progress.qwTotalCacheSize = qwTotalCacheSize; *pfRetry = FALSE; @@ -983,6 +1074,22 @@ static HRESULT LayoutOrCachePayload( hr = CacheCompletePayload(pPackage->fPerMachine, pPayload, pPackage->sczCacheId, wzUnverifiedPath, fMove); } + // If succeeded, send 100% complete here. If the payload was already cached this is the first progress the BA + // will get. + if (SUCCEEDED(hr)) + { + CacheProgressRoutine(liPayloadSize, liPayloadSize, liZero, liZero, 0, 0, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, &progress); + if (progress.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else if (progress.fError) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + } + ExitOnRootFailure1(hr, "BA aborted verify of payload: %ls", pPayload->sczKey); + } + nResult = pUX->pUserExperience->OnCacheVerifyComplete(pPackage ? pPackage->sczId : NULL, pPayload->sczKey, hr, FAILED(hr) && cTryAgainAttempts < BURN_CACHE_MAX_RECOMMENDED_VERIFY_TRYAGAIN_ATTEMPTS ? IDTRYAGAIN : IDNOACTION); nResult = UserExperienceCheckExecuteResult(pUX, FALSE, MB_RETRYTRYAGAIN, nResult); if (FAILED(hr)) @@ -1060,6 +1167,11 @@ static HRESULT CopyPayload( { HRESULT hr = S_OK; DWORD dwFileAttributes = 0; + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : L""; + LPCWSTR wzPayloadId = pProgress->pPayload ? pProgress->pPayload->sczKey : L""; + + DWORD dwLogId = pProgress->pContainer ? (pProgress->pPayload ? MSG_ACQUIRE_CONTAINER_PAYLOAD : MSG_ACQUIRE_CONTAINER) : pProgress->pPackage ? MSG_ACQUIRE_PACKAGE_PAYLOAD : MSG_ACQUIRE_BUNDLE_PAYLOAD; + LogId(REPORT_STANDARD, dwLogId, wzPackageOrContainerId, wzPayloadId, "copy", wzSourcePath); // If the destination file already exists, clear the readonly bit to avoid E_ACCESSDENIED. if (FileExistsEx(wzDestinationPath, &dwFileAttributes)) @@ -1098,10 +1210,15 @@ static HRESULT DownloadPayload( { HRESULT hr = S_OK; DWORD dwFileAttributes = 0; + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : L""; + LPCWSTR wzPayloadId = pProgress->pPayload ? pProgress->pPayload->sczKey : L""; BURN_DOWNLOAD_SOURCE* pDownloadSource = pProgress->pContainer ? &pProgress->pContainer->downloadSource : &pProgress->pPayload->downloadSource; DWORD64 qwDownloadSize = pProgress->pContainer ? pProgress->pContainer->qwFileSize : pProgress->pPayload->qwFileSize; BURN_CACHE_CALLBACK callback = { }; + DWORD dwLogId = pProgress->pContainer ? (pProgress->pPayload ? MSG_ACQUIRE_CONTAINER_PAYLOAD : MSG_ACQUIRE_CONTAINER) : pProgress->pPackage ? MSG_ACQUIRE_PACKAGE_PAYLOAD : MSG_ACQUIRE_BUNDLE_PAYLOAD; + LogId(REPORT_STANDARD, dwLogId, wzPackageOrContainerId, wzPayloadId, "download", pDownloadSource->sczUrl); + // If the destination file already exists, clear the readonly bit to avoid E_ACCESSDENIED. if (FileExistsEx(wzDestinationPath, &dwFileAttributes)) { @@ -1488,12 +1605,12 @@ static HRESULT ExecuteExePackage( // Execute package. if (pExecuteAction->exePackage.pPackage->fPerMachine) { - hrExecute = ElevationExecuteExePackage(pEngineState->companionConnection.hPipe, pExecuteAction, &pEngineState->variables, GenericExecuteMessageHandler, pContext, pRestart); + hrExecute = ElevationExecuteExePackage(pEngineState->companionConnection.hPipe, pExecuteAction, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart); ExitOnFailure(hrExecute, "Failed to configure per-machine EXE package."); } else { - hrExecute = ExeEngineExecutePackage(pExecuteAction, &pEngineState->variables, GenericExecuteMessageHandler, pContext, pRestart); + hrExecute = ExeEngineExecutePackage(pExecuteAction, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart); ExitOnFailure(hrExecute, "Failed to configure per-user EXE package."); } @@ -1650,7 +1767,7 @@ static HRESULT ExecuteMsuPackage( // execute package if (pExecuteAction->msuPackage.pPackage->fPerMachine) { - hrExecute = ElevationExecuteMsuPackage(pEngineState->companionConnection.hPipe, pExecuteAction, GenericExecuteMessageHandler, pContext, pRestart); + hrExecute = ElevationExecuteMsuPackage(pEngineState->companionConnection.hPipe, pExecuteAction, fRollback, GenericExecuteMessageHandler, pContext, pRestart); ExitOnFailure(hrExecute, "Failed to configure per-machine MSU package."); } else diff --git a/src/burn/engine/apply.h b/src/burn/engine/apply.h index 555501cd..95ca774f 100644 --- a/src/burn/engine/apply.h +++ b/src/burn/engine/apply.h @@ -63,6 +63,10 @@ typedef int (*PFN_GENERICMESSAGEHANDLER)( ); +HRESULT ApplyLock( + __in BOOL fPerMachine, + __out HANDLE* phLock + ); HRESULT ApplyRegister( __in BURN_ENGINE_STATE* pEngineState ); diff --git a/src/burn/engine/cache.cpp b/src/burn/engine/cache.cpp index 195418cf..e8c9fde5 100644 --- a/src/burn/engine/cache.cpp +++ b/src/burn/engine/cache.cpp @@ -23,10 +23,17 @@ static const LPCWSTR UNVERFIED_CACHE_FOLDER_NAME = L".unverified"; static const DWORD FILE_OPERATION_RETRY_COUNT = 3; static const DWORD FILE_OPERATION_RETRY_WAIT = 2000; +static BOOL vfInitializedCache = FALSE; +static BOOL vfRunningFromCache = FALSE; + static HRESULT CalculateWorkingFolder( __in_z LPCWSTR wzBundleId, __deref_out_z LPWSTR* psczWorkingFolder ); +static HRESULT GetLastUsedSourceFolder( + __in BURN_VARIABLES* pVariables, + __out_z LPWSTR* psczLastSource + ); static HRESULT CreateCompletedPath( __in BOOL fPerMachine, __in LPCWSTR wzCacheId, @@ -88,6 +95,52 @@ static HRESULT GetVerifiedCertificateChain( ); +extern "C" HRESULT CacheInitialize( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCurrentPath = NULL; + LPWSTR sczCompletedFolder = NULL; + LPWSTR sczCompletedPath = NULL; + LPWSTR sczOriginalSource = NULL; + int nCompare = 0; + + hr = PathForCurrentProcess(&sczCurrentPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + + hr = CacheGetCompletedPath(pRegistration->fPerMachine, pRegistration->sczId, &sczCompletedFolder); + ExitOnFailure(hr, "Failed to get completed path for bundle."); + + hr = PathConcat(sczCompletedFolder, pRegistration->sczExecutableName, &sczCompletedPath); + ExitOnFailure(hr, "Failed to combine working path with engine file name."); + + hr = PathCompare(sczCurrentPath, sczCompletedPath, &nCompare); + ExitOnFailure1(hr, "Failed to compare current path for bundle: %ls", sczCurrentPath); + + vfRunningFromCache = (CSTR_EQUAL == nCompare); + + // If we're not running from the cache, ensure the original source is set. + if (!vfRunningFromCache) + { + // If the original source has not been set already then set it where the bundle is + // running from right now. This value will be persisted and we'll use it when launched + // from the package cache since none of our packages will be relative to that location. + hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource); + if (E_NOTFOUND == hr) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, sczCurrentPath, FALSE); + ExitOnFailure(hr, "Failed to set original source variable."); + } + } + + vfInitializedCache = TRUE; + +LExit: + return hr; +} + extern "C" HRESULT CacheEnsureWorkingFolder( __in_z LPCWSTR wzBundleId, __deref_out_z_opt LPWSTR* psczWorkingFolder @@ -165,46 +218,34 @@ extern "C" HRESULT CacheGetOriginalSourcePath( } extern "C" HRESULT CacheCalculateBundleWorkingPath( - __in BOOL fPerMachine, __in_z LPCWSTR wzBundleId, __in LPCWSTR wzExecutableName, __deref_out_z LPWSTR* psczWorkingPath ) { + Assert(vfInitializedCache); + HRESULT hr = S_OK; - int nCompare = 0; - LPWSTR sczCurrentProcess = NULL; LPWSTR sczWorkingFolder = NULL; // If the bundle is running out of the package cache then we use that as the // working folder since we feel safe in the package cache. - hr = PathForCurrentProcess(&sczCurrentProcess, NULL); - ExitOnFailure(hr, "Failed to get current process path."); - - hr = CacheGetCompletedPath(fPerMachine, wzBundleId, &sczWorkingFolder); - ExitOnFailure1(hr, "Failed to get completed path for bundle: %ls", wzBundleId); - - hr = PathConcat(sczWorkingFolder, wzExecutableName, psczWorkingPath); - ExitOnFailure(hr, "Failed to combine working path with engine file name."); - - hr = PathCompare(sczCurrentProcess, *psczWorkingPath, &nCompare); - ExitOnFailure1(hr, "Failed to compare current path for bundle: %ls", sczCurrentProcess); - - if (CSTR_EQUAL == nCompare) + if (vfRunningFromCache) { - ExitFunction(); + hr = PathForCurrentProcess(psczWorkingPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); } + else // Otherwise, use the real working folder. + { + hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to get working folder for bundle."); - // Otherwise, use the real working folder. - hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder); - ExitOnFailure(hr, "Failed to get working folder for bundle."); - - hr = StrAllocFormatted(psczWorkingPath, L"%ls%ls\\%ls", sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName); - ExitOnFailure(hr, "Failed to calculate the bundle working path."); + hr = StrAllocFormatted(psczWorkingPath, L"%ls%ls\\%ls", sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName); + ExitOnFailure(hr, "Failed to calculate the bundle working path."); + } LExit: ReleaseStr(sczWorkingFolder); - ReleaseStr(sczCurrentProcess); return hr; } @@ -299,6 +340,157 @@ extern "C" HRESULT CacheGetResumePath( return hr; } +extern "C" HRESULT CacheFindLocalSource( + __in_z LPCWSTR wzSourcePath, + __in BURN_VARIABLES* pVariables, + __out BOOL* pfFound, + __out_z LPWSTR* psczSourceFullPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCurrentProcess = NULL; + LPWSTR sczCurrentProcessFolder = NULL; + LPWSTR sczCurrentPath = NULL; + LPWSTR sczLastSourcePath = NULL; + LPWSTR sczLastSourceFolder = NULL; + LPCWSTR rgwzSearchPaths[2] = { }; + DWORD cSearchPaths = 0; + + // If the source path provided is a full path, obviously that is where we should be looking. + if (PathIsAbsolute(wzSourcePath)) + { + rgwzSearchPaths[0] = wzSourcePath; + cSearchPaths = 1; + } + else + { + // If we're not running from cache or we couldn't get the last source, use + // the current process location first. In the case where we are in the cache + // and couldn't find a last used source we unfortunately will be picking the + // bundle cache path which isn't likely to have what we are looking for. + hr = GetLastUsedSourceFolder(pVariables, &sczLastSourceFolder); + if (!vfRunningFromCache || FAILED(hr)) + { + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + ExitOnFailure(hr, "Failed to get path to current process."); + + hr = PathGetDirectory(sczCurrentProcess, &sczCurrentProcessFolder); + ExitOnFailure(hr, "Failed to get current process directory."); + + hr = PathConcat(sczCurrentProcessFolder, wzSourcePath, &sczCurrentPath); + ExitOnFailure(hr, "Failed to combine last source with source."); + + rgwzSearchPaths[0] = sczCurrentPath; + cSearchPaths = 1; + } + + // If we have a last used source and it does not duplicate the existing search path, + // add the last used source to the search path second. + if (sczLastSourceFolder && *sczLastSourceFolder) + { + hr = PathConcat(sczLastSourceFolder, wzSourcePath, &sczLastSourcePath); + ExitOnFailure(hr, "Failed to combine last source with source."); + + if (0 == cSearchPaths || CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, rgwzSearchPaths[0], -1, sczLastSourcePath, -1)) + { + rgwzSearchPaths[cSearchPaths] = sczLastSourcePath; + ++cSearchPaths; + } + } + } + + *pfFound = FALSE; // assume we won't find the file locally. + + for (DWORD i = 0; i < cSearchPaths; ++i) + { + // If the file exists locally, copy its path. + if (FileExistsEx(rgwzSearchPaths[i], NULL)) + { + hr = StrAllocString(psczSourceFullPath, rgwzSearchPaths[i], 0); + ExitOnFailure(hr, "Failed to copy source path."); + + *pfFound = TRUE; + break; + } + } + + // If nothing was found, return the first thing in our search path as the + // best path where we thought we should have found the file. + if (!*pfFound) + { + hr = StrAllocString(psczSourceFullPath, rgwzSearchPaths[0], 0); + ExitOnFailure(hr, "Failed to copy source path."); + } + +LExit: + ReleaseStr(sczCurrentPath); + ReleaseStr(sczCurrentProcessFolder); + ReleaseStr(sczCurrentProcess); + ReleaseStr(sczLastSourceFolder); + ReleaseStr(sczLastSourcePath); + + return hr; +} + +extern "C" HRESULT CacheSetLastUsedSource( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzRelativePath + ) +{ + HRESULT hr = S_OK; + size_t cchSourcePath = 0; + size_t cchRelativePath = 0; + size_t iSourceRelativePath = 0; + LPWSTR sczSourceFolder = NULL; + LPWSTR sczLastSourceFolder = NULL; + int nCompare = 0; + + hr = ::StringCchLengthW(wzSourcePath, STRSAFE_MAX_CCH, &cchSourcePath); + ExitOnFailure(hr, "Failed to determine length of source path."); + + hr = ::StringCchLengthW(wzRelativePath, STRSAFE_MAX_CCH, &cchRelativePath); + ExitOnFailure(hr, "Failed to determine length of relative path."); + + // If the source path is smaller than the relative path (plus space for "X:\") then we know they + // are not relative to each other. + if (cchSourcePath < cchRelativePath + 3) + { + ExitFunction(); + } + + // If the source path ends with the relative path then this source could be a new path. + iSourceRelativePath = cchSourcePath - cchRelativePath; + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzSourcePath + iSourceRelativePath, -1, wzRelativePath, -1)) + { + hr = StrAllocString(&sczSourceFolder, wzSourcePath, iSourceRelativePath); + ExitOnFailure(hr, "Failed to trim source folder."); + + hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, &sczLastSourceFolder); + if (SUCCEEDED(hr)) + { + nCompare = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczSourceFolder, -1, sczLastSourceFolder, -1); + } + else if (E_NOTFOUND == hr) + { + nCompare = CSTR_GREATER_THAN; + hr = S_OK; + } + + if (CSTR_EQUAL != nCompare) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, sczSourceFolder, FALSE); + ExitOnFailure(hr, "Failed to set last source."); + } + } + +LExit: + ReleaseStr(sczLastSourceFolder); + ReleaseStr(sczSourceFolder); + + return hr; +} + extern "C" HRESULT CacheSendProgressCallback( __in BURN_CACHE_CALLBACK* pCallback, __in DWORD64 dw64Progress, @@ -368,7 +560,6 @@ extern "C" void CacheSendErrorCallback( } extern "C" HRESULT CacheBundleToWorkingDirectory( - __in BOOL fPerMachine, __in_z LPCWSTR wzBundleId, __in_z LPCWSTR wzExecutableName, __in BURN_PAYLOADS* pUxPayloads, @@ -376,8 +567,9 @@ extern "C" HRESULT CacheBundleToWorkingDirectory( __deref_out_z_opt LPWSTR* psczEngineWorkingPath ) { + Assert(vfInitializedCache); + HRESULT hr = S_OK; - int nCompare = 0; LPWSTR sczSourcePath = NULL; LPWSTR sczSourceDirectory = NULL; LPWSTR sczWorkingFolder = NULL; @@ -392,55 +584,48 @@ extern "C" HRESULT CacheBundleToWorkingDirectory( // If the bundle is running out of the package cache then we don't need to copy it to // the working folder since we feel safe in the package cache and will run from there. - hr = CacheGetCompletedPath(fPerMachine, wzBundleId, &sczTargetDirectory); - ExitOnFailure1(hr, "Failed to get completed path for bundle: %ls", wzBundleId); - - hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath); - ExitOnFailure(hr, "Failed to combine working path with engine file name."); - - hr = PathCompare(sczSourcePath, sczTargetPath, &nCompare); - ExitOnFailure1(hr, "Failed to compare current path for bundle: %ls", sczSourcePath); - - if (CSTR_EQUAL == nCompare) + if (vfRunningFromCache) { - ExitFunction(); + hr = StrAllocString(&sczTargetPath, sczSourcePath, 0); + ExitOnFailure(hr, "Failed to use current process path as target path."); } + else // otherwise, carry on putting the bundle in the working folder. + { + hr = PathGetDirectory(sczSourcePath, &sczSourceDirectory); + ExitOnFailure1(hr, "Failed to get directory from engine path: %ls", sczSourcePath); - // Otherwise, carry on putting the bundle in the working folder. - hr = PathGetDirectory(sczSourcePath, &sczSourceDirectory); - ExitOnFailure1(hr, "Failed to get directory from engine path: %ls", sczSourcePath); - - hr = CacheEnsureWorkingFolder(wzBundleId, &sczWorkingFolder); - ExitOnFailure(hr, "Failed to create working path to copy engine."); + hr = CacheEnsureWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to create working path to copy engine."); - hr = PathConcat(sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, &sczTargetDirectory); - ExitOnFailure(hr, "Failed to calculate the bundle working folder target name."); + hr = PathConcat(sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, &sczTargetDirectory); + ExitOnFailure(hr, "Failed to calculate the bundle working folder target name."); - hr = DirEnsureExists(sczTargetDirectory, NULL); - ExitOnFailure(hr, "Failed create bundle working folder."); + hr = DirEnsureExists(sczTargetDirectory, NULL); + ExitOnFailure(hr, "Failed create bundle working folder."); - hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath); - ExitOnFailure(hr, "Failed to combine working path with engine file name."); + hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath); + ExitOnFailure(hr, "Failed to combine working path with engine file name."); - // Copy the engine without any attached containers to the working path. - hr = CopyEngineWithSignatureFixup(sczSourcePath, sczTargetPath, pSection); - ExitOnFailure2(hr, "Failed to copy engine: '%ls' to working path: %ls", sczSourcePath, sczTargetPath); + // Copy the engine without any attached containers to the working path. + hr = CopyEngineWithSignatureFixup(sczSourcePath, sczTargetPath, pSection); + ExitOnFailure2(hr, "Failed to copy engine: '%ls' to working path: %ls", sczSourcePath, sczTargetPath); - // Copy external UX payloads to working path. - for (DWORD i = 0; i < pUxPayloads->cPayloads; ++i) - { - BURN_PAYLOAD* pPayload = &pUxPayloads->rgPayloads[i]; - - if (BURN_PAYLOAD_PACKAGING_EXTERNAL == pPayload->packaging) + // Copy external UX payloads to working path. + for (DWORD i = 0; i < pUxPayloads->cPayloads; ++i) { - hr = PathConcat(sczSourceDirectory, pPayload->sczSourcePath, &sczPayloadSourcePath); - ExitOnFailure(hr, "Failed to build payload source path for working copy."); + BURN_PAYLOAD* pPayload = &pUxPayloads->rgPayloads[i]; + + if (BURN_PAYLOAD_PACKAGING_EXTERNAL == pPayload->packaging) + { + hr = PathConcat(sczSourceDirectory, pPayload->sczSourcePath, &sczPayloadSourcePath); + ExitOnFailure(hr, "Failed to build payload source path for working copy."); - hr = PathConcat(sczTargetDirectory, pPayload->sczFilePath, &sczPayloadTargetPath); - ExitOnFailure(hr, "Failed to build payload target path for working copy."); + hr = PathConcat(sczTargetDirectory, pPayload->sczFilePath, &sczPayloadTargetPath); + ExitOnFailure(hr, "Failed to build payload target path for working copy."); - hr = FileEnsureCopyWithRetry(sczPayloadSourcePath, sczPayloadTargetPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); - ExitOnFailure2(hr, "Failed to copy UX payload from: '%ls' to: '%ls'", sczPayloadSourcePath, sczPayloadTargetPath); + hr = FileEnsureCopyWithRetry(sczPayloadSourcePath, sczPayloadTargetPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnFailure2(hr, "Failed to copy UX payload from: '%ls' to: '%ls'", sczPayloadSourcePath, sczPayloadTargetPath); + } } } @@ -918,6 +1103,28 @@ static HRESULT CalculateWorkingFolder( return hr; } +static HRESULT GetLastUsedSourceFolder( + __in BURN_VARIABLES* pVariables, + __out_z LPWSTR* psczLastSource + ) +{ + HRESULT hr = S_OK; + LPWSTR sczOriginalSource = NULL; + + hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, psczLastSource); + if (E_NOTFOUND == hr) + { + // Try the original source folder. + hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource); + if (SUCCEEDED(hr)) + { + hr = PathGetDirectory(sczOriginalSource, psczLastSource); + } + } + + return hr; +} + static HRESULT CreateCompletedPath( __in BOOL fPerMachine, __in LPCWSTR wzId, @@ -1337,16 +1544,32 @@ static HRESULT RemoveBundleOrPackage( LogId(REPORT_STANDARD, fBundle ? MSG_UNCACHE_BUNDLE : MSG_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory); - hr = DirEnsureDeleteEx(sczDirectory, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); - if (E_PATHNOTFOUND == hr) + // Try really hard to remove the cache directory. + hr = E_FAIL; + for (DWORD iRetry = 0; FAILED(hr) && iRetry < FILE_OPERATION_RETRY_COUNT; ++iRetry) { - // TODO: should we log that the bundle was already removed? - hr = S_OK; + if (0 < iRetry) + { + ::Sleep(FILE_OPERATION_RETRY_WAIT); + } + + hr = DirEnsureDeleteEx(sczDirectory, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); + if (E_PATHNOTFOUND == hr) + { + break; + } } - ExitOnFailure1(hr, "Failed to remove cached directory: %ls", sczDirectory); - // Try to remove root package cache in the off chance it is now empty. - DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE); + if (FAILED(hr)) + { + LogId(REPORT_STANDARD, fBundle ? MSG_UNABLE_UNCACHE_BUNDLE : MSG_UNABLE_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory, hr); + hr = S_OK; + } + else + { + // Try to remove root package cache in the off chance it is now empty. + DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE); + } LExit: ReleaseStr(sczDirectory); diff --git a/src/burn/engine/cache.h b/src/burn/engine/cache.h index 4908e0aa..f9338d6e 100644 --- a/src/burn/engine/cache.h +++ b/src/burn/engine/cache.h @@ -42,6 +42,10 @@ typedef struct _BURN_CACHE_CALLBACK // functions +HRESULT CacheInitialize( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ); HRESULT CacheEnsureWorkingFolder( __in LPCWSTR wzBundleId, __deref_out_z_opt LPWSTR* psczWorkingFolder @@ -52,7 +56,6 @@ HRESULT CacheGetOriginalSourcePath( __out_z_opt LPWSTR* psczOriginalSource ); HRESULT CacheCalculateBundleWorkingPath( - __in BOOL fPerMachine, __in_z LPCWSTR wzBundleId, __in LPCWSTR wzExecutableName, __deref_out_z LPWSTR* psczWorkingPath @@ -80,6 +83,17 @@ HRESULT CacheGetResumePath( __in_z LPCWSTR wzPayloadWorkingPath, __deref_out_z LPWSTR* psczResumePath ); +HRESULT CacheFindLocalSource( + __in_z LPCWSTR wzSourcePath, + __in BURN_VARIABLES* pVariables, + __out BOOL* pfFound, + __out_z LPWSTR* psczSourceFullPath + ); +HRESULT CacheSetLastUsedSource( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzRelativePath + ); HRESULT CacheSendProgressCallback( __in BURN_CACHE_CALLBACK* pCallback, __in DWORD64 dw64Progress, @@ -93,7 +107,6 @@ void CacheSendErrorCallback( __out_opt BOOL* pfRetry ); HRESULT CacheBundleToWorkingDirectory( - __in BOOL fPerMachine, __in_z LPCWSTR wzBundleId, __in_z LPCWSTR wzExecutableName, __in BURN_PAYLOADS* pUxPayloads, diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp index ef42966d..5d857d7f 100644 --- a/src/burn/engine/container.cpp +++ b/src/burn/engine/container.cpp @@ -99,13 +99,17 @@ extern "C" HRESULT ContainersParseFromXml( ExitOnFailure(hr, "Failed to get @AttachedIndex."); } - // @SourcePath - hr = XmlGetAttributeEx(pixnNode, L"SourcePath", &pContainer->sczSourcePath); + // @FilePath + hr = XmlGetAttributeEx(pixnNode, L"FilePath", &pContainer->sczFilePath); if (E_NOTFOUND != hr) { - ExitOnFailure(hr, "Failed to get @SourcePath."); + ExitOnFailure(hr, "Failed to get @FilePath."); } + // The source path starts as the file path. + hr = StrAllocString(&pContainer->sczSourcePath, pContainer->sczFilePath, 0); + ExitOnFailure(hr, "Failed to copy @FilePath"); + // @DownloadUrl hr = XmlGetAttributeEx(pixnNode, L"DownloadUrl", &pContainer->downloadSource.sczUrl); if (E_NOTFOUND != hr || (!pContainer->fPrimary && !pContainer->sczSourcePath)) // if the package is not a primary package, it must have a source path or a download url @@ -155,6 +159,7 @@ extern "C" void ContainersUninitialize( ReleaseStr(pContainer->sczId); ReleaseStr(pContainer->sczHash); ReleaseStr(pContainer->sczSourcePath); + ReleaseStr(pContainer->sczFilePath); ReleaseStr(pContainer->downloadSource.sczUrl); ReleaseStr(pContainer->downloadSource.sczUser); ReleaseStr(pContainer->downloadSource.sczPassword); diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h index 5945d6c0..ce59e9a9 100644 --- a/src/burn/engine/container.h +++ b/src/burn/engine/container.h @@ -88,6 +88,7 @@ typedef struct _BURN_CONTAINER DWORD dwAttachedIndex; DWORD64 qwFileSize; LPWSTR sczHash; + LPWSTR sczFilePath; // relative path to container. LPWSTR sczSourcePath; BURN_DOWNLOAD_SOURCE downloadSource; diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index 55763c24..d6743ea3 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -39,6 +39,7 @@ static HRESULT ParseCommandLine( __in BURN_PIPE_CONNECTION* pCompanionConnection, __in BURN_PIPE_CONNECTION* pEmbeddedConnection, __out BURN_MODE* pMode, + __out BURN_AU_PAUSE_ACTION* pAutomaticUpdates, __out BURN_ELEVATION_STATE* pElevationState, __out BOOL* pfDisableUnelevate, __out DWORD *pdwLoggingAttributes, @@ -74,7 +75,7 @@ extern "C" HRESULT CoreInitialize( BURN_CONTAINER_CONTEXT containerContext = { }; // parse command line - hr = ParseCommandLine(wzCommandLine, &pEngineState->command, &pEngineState->companionConnection, &pEngineState->embeddedConnection, &pEngineState->mode, &pEngineState->elevationState, &pEngineState->fDisableUnelevate, &pEngineState->log.dwAttributes, &pEngineState->log.sczPath, &pEngineState->sczIgnoreDependencies); + hr = ParseCommandLine(wzCommandLine, &pEngineState->command, &pEngineState->companionConnection, &pEngineState->embeddedConnection, &pEngineState->mode, &pEngineState->automaticUpdates, &pEngineState->elevationState, &pEngineState->fDisableUnelevate, &pEngineState->log.dwAttributes, &pEngineState->log.sczPath, &pEngineState->sczIgnoreDependencies); ExitOnFailure(hr, "Failed to parse command line."); // initialize variables @@ -276,7 +277,7 @@ extern "C" HRESULT CoreDetect( { pPackage = pEngineState->packages.rgPackages + iPackage; - LogId(REPORT_STANDARD, MSG_DETECTED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingBoolToString(pPackage->fCached)); + LogId(REPORT_STANDARD, MSG_DETECTED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingCacheStateToString(pPackage->cache)); if (BURN_PACKAGE_TYPE_MSI == pPackage->type) { @@ -325,7 +326,6 @@ extern "C" HRESULT CorePlan( BURN_PACKAGE* pPackage = NULL; DWORD dwExecuteActionEarlyIndex = 0; HANDLE hSyncpointEvent = NULL; - BOOTSTRAPPER_REQUEST_STATE defaultRequested = BOOTSTRAPPER_REQUEST_STATE_NONE; BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; HANDLE hRollbackBoundaryCompleteEvent = NULL; DWORD iAfterExecuteFirstNonPermanentPackage = BURN_PLAN_INVALID_ACTION_INDEX; @@ -350,6 +350,9 @@ extern "C" HRESULT CorePlan( pEngineState->plan.action = action; pEngineState->plan.wzBundleId = pEngineState->registration.sczId; + hr = PlanSetVariables(action, &pEngineState->variables); + ExitOnFailure(hr, "Failed to update action."); + // By default we want to keep the registration if the bundle was already installed. pEngineState->plan.fKeepRegistrationDefault = pEngineState->registration.fInstalled; @@ -363,7 +366,7 @@ extern "C" HRESULT CorePlan( if (BOOTSTRAPPER_ACTION_LAYOUT == action) { // Plan the bundle's layout. - hr = PlanLayoutBundle(&pEngineState->plan, pEngineState->registration.sczExecutableName, &pEngineState->variables, &pEngineState->payloads, &sczLayoutDirectory); + hr = PlanLayoutBundle(&pEngineState->plan, pEngineState->registration.sczExecutableName, pEngineState->section.qwBundleSize, &pEngineState->variables, &pEngineState->payloads, &sczLayoutDirectory); ExitOnFailure(hr, "Failed to plan the layout of the bundle."); } else if (pEngineState->registration.fPerMachine) // the registration of this bundle is per-machine then the plan needs to be per-machine as well. @@ -401,10 +404,10 @@ extern "C" HRESULT CorePlan( } // Remember the default requested state so the engine doesn't get blamed for planning the wrong thing if the UX changes it. - hr = PlanDefaultPackageRequestState(pPackage->type, pPackage->currentState, !pPackage->fUninstallable, action, &pEngineState->variables, pPackage->sczInstallCondition, pEngineState->command.relationType, &defaultRequested); + hr = PlanDefaultPackageRequestState(pPackage->type, pPackage->currentState, !pPackage->fUninstallable, action, &pEngineState->variables, pPackage->sczInstallCondition, pEngineState->command.relationType, &pPackage->defaultRequested); ExitOnFailure(hr, "Failed to set default package state."); - pPackage->requested = defaultRequested; + pPackage->requested = pPackage->defaultRequested; nResult = pEngineState->userExperience.pUserExperience->OnPlanPackageBegin(pPackage->sczId, &pPackage->requested); hr = UserExperienceInterpretResult(&pEngineState->userExperience, MB_OKCANCEL, nResult); @@ -524,7 +527,7 @@ extern "C" HRESULT CorePlan( DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == action) ? pEngineState->packages.cPackages - 1 - i : i; pPackage = pEngineState->packages.rgPackages + iPackage; - LogId(REPORT_STANDARD, MSG_PLANNED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingRequestStateToString(defaultRequested), LoggingRequestStateToString(pPackage->requested), LoggingActionStateToString(pPackage->execute), LoggingActionStateToString(pPackage->rollback), LoggingBoolToString(pPackage->fAcquire), LoggingBoolToString(pPackage->fUncache), LoggingDependencyActionToString(pPackage->dependency)); + LogId(REPORT_STANDARD, MSG_PLANNED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingRequestStateToString(pPackage->defaultRequested), LoggingRequestStateToString(pPackage->requested), LoggingActionStateToString(pPackage->execute), LoggingActionStateToString(pPackage->rollback), LoggingBoolToString(pPackage->fAcquire), LoggingBoolToString(pPackage->fUncache), LoggingDependencyActionToString(pPackage->dependency)); } #ifdef DEBUG @@ -555,6 +558,12 @@ extern "C" HRESULT CoreElevate( // If the elevated companion pipe isn't created yet, let's make that happen. if (INVALID_HANDLE_VALUE == pEngineState->companionConnection.hPipe) { + if (!pEngineState->sczBundleEngineWorkingPath) + { + hr = CacheBundleToWorkingDirectory(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->userExperience.payloads, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath); + ExitOnFailure(hr, "Failed to cache engine to working directory."); + } + hr = ElevationElevate(pEngineState, hwndParent); ExitOnFailure(hr, "Failed to actually elevate."); @@ -574,8 +583,10 @@ extern "C" HRESULT CoreApply( HRESULT hr = S_OK; BOOL fLayoutOnly = (BOOTSTRAPPER_ACTION_LAYOUT == pEngineState->plan.action); BOOL fActivated = FALSE; + HANDLE hLock = NULL; DWORD cOverallProgressTicks = 0; HANDLE hCacheThread = NULL; + BOOL fElevated = FALSE; BOOL fRegistered = FALSE; BOOL fKeepRegistration = pEngineState->plan.fKeepRegistrationDefault; BOOL fRollback = FALSE; @@ -594,13 +605,28 @@ extern "C" HRESULT CoreApply( hr = UserExperienceInterpretResult(&pEngineState->userExperience, MB_OKCANCEL, nResult); ExitOnRootFailure(hr, "UX aborted apply begin."); - // If the plan contains per-machine contents, let's make sure we are elevated. + hr = ApplyLock(FALSE, &hLock); + ExitOnFailure(hr, "Another per-user setup is already executing."); + + // Ensure the engine is cached to the working path. + if (!pEngineState->sczBundleEngineWorkingPath) + { + hr = CacheBundleToWorkingDirectory(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->userExperience.payloads, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath); + ExitOnFailure(hr, "Failed to cache engine to working directory."); + } + + // If the plan contains per-machine contents, let's make sure we are elevated and locked at the machine level. if (pEngineState->plan.fPerMachine) { AssertSz(!fLayoutOnly, "A Layout plan should never require elevation."); hr = CoreElevate(pEngineState, hwndParent); ExitOnFailure(hr, "Failed to elevate."); + + hr = ElevationApplyInitialize(pEngineState->companionConnection.hPipe, pEngineState->automaticUpdates); + ExitOnFailure(hr, "Another per-machine setup is already executing."); + + fElevated = TRUE; } // Register only if we are not doing a layout. @@ -658,6 +684,17 @@ extern "C" HRESULT CoreApply( ApplyUnregister(pEngineState, fKeepRegistration, fSuspend, restart); } + if (fElevated) + { + ElevationApplyUninitialize(pEngineState->companionConnection.hPipe); + } + + if (hLock) + { + ::ReleaseMutex(hLock); + ::CloseHandle(hLock); + } + if (fActivated) { UserExperienceDeactivateEngine(&pEngineState->userExperience); @@ -666,7 +703,7 @@ extern "C" HRESULT CoreApply( ReleaseHandle(hCacheThread); nResult = pEngineState->userExperience.pUserExperience->OnApplyComplete(hr, restart); - if (IDRESTART == nResult) + if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart || IDRESTART == nResult) { pEngineState->fRestart = TRUE; } @@ -740,6 +777,7 @@ static HRESULT ParseCommandLine( __in BURN_PIPE_CONNECTION* pCompanionConnection, __in BURN_PIPE_CONNECTION* pEmbeddedConnection, __out BURN_MODE* pMode, + __out BURN_AU_PAUSE_ACTION* pAutomaticUpdates, __out BURN_ELEVATION_STATE* pElevationState, __out BOOL* pfDisableUnelevate, __out DWORD *pdwLoggingAttributes, @@ -863,6 +901,18 @@ static HRESULT ParseCommandLine( pCommand->action = BOOTSTRAPPER_ACTION_INSTALL; } } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"noaupause", -1)) + { + *pAutomaticUpdates = BURN_AU_PAUSE_ACTION_NONE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"keepaupaused", -1)) + { + // Switch /noaupause takes precedence. + if (BURN_AU_PAUSE_ACTION_NONE != *pAutomaticUpdates) + { + *pAutomaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME; + } + } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_LOG_APPEND, -1)) { if (i + 1 >= argc) @@ -1054,7 +1104,7 @@ static HRESULT DetectPackagePayloadsCached( { HRESULT hr = S_OK; LPWSTR sczCachePath = NULL; - BOOL fAllPayloadsCached = FALSE; + BURN_CACHE_STATE cache = BURN_CACHE_STATE_NONE; // assume the package will not be cached. LPWSTR sczPayloadCachePath = NULL; LONGLONG llSize = 0; @@ -1063,38 +1113,43 @@ static HRESULT DetectPackagePayloadsCached( hr = CacheGetCompletedPath(pPackage->fPerMachine, pPackage->sczCacheId, &sczCachePath); ExitOnFailure(hr, "Failed to get completed cache path."); - fAllPayloadsCached = DirExists(sczCachePath, NULL); // assume all payloads will be cached if the cache directory exists. - - for (DWORD i = 0; fAllPayloadsCached && i < pPackage->cPayloads; ++i) + // If the cached directory exists, we have something. + if (DirExists(sczCachePath, NULL)) { - BURN_PACKAGE_PAYLOAD* pPackagePayload = pPackage->rgPayloads + i; + cache = BURN_CACHE_STATE_COMPLETE; // assume all payloads are cached. - hr = PathConcat(sczCachePath, pPackagePayload->pPayload->sczFilePath, &sczPayloadCachePath); - ExitOnFailure(hr, "Failed to concat payload cache path."); - - // TODO: should we do a full on hash verification on the file to ensure the exact right - // file is cached? - hr = FileSize(sczPayloadCachePath, &llSize); - if (SUCCEEDED(hr) && static_cast(llSize) == pPackagePayload->pPayload->qwFileSize) - { - pPackagePayload->fCached = TRUE; - } - else + // Check all payloads to see if any are missing or not the right size. + for (DWORD i = 0; i < pPackage->cPayloads; ++i) { - if (static_cast(llSize) != pPackagePayload->pPayload->qwFileSize) + BURN_PACKAGE_PAYLOAD* pPackagePayload = pPackage->rgPayloads + i; + + hr = PathConcat(sczCachePath, pPackagePayload->pPayload->sczFilePath, &sczPayloadCachePath); + ExitOnFailure(hr, "Failed to concat payload cache path."); + + // TODO: should we do a full on hash verification on the file to ensure the exact right + // file is cached? + hr = FileSize(sczPayloadCachePath, &llSize); + if (SUCCEEDED(hr) && static_cast(llSize) == pPackagePayload->pPayload->qwFileSize) { - hr = HRESULT_FROM_WIN32(ERROR_FILE_CORRUPT); + pPackagePayload->fCached = TRUE; } + else + { + if (static_cast(llSize) != pPackagePayload->pPayload->qwFileSize) + { + hr = HRESULT_FROM_WIN32(ERROR_FILE_CORRUPT); + } - LogId(REPORT_STANDARD, MSG_DETECT_PACKAGE_NOT_FULLY_CACHED, pPackage->sczId, pPackagePayload->pPayload->sczKey, hr); + LogId(REPORT_STANDARD, MSG_DETECT_PACKAGE_NOT_FULLY_CACHED, pPackage->sczId, pPackagePayload->pPayload->sczKey, hr); - fAllPayloadsCached = FALSE; // found a payload that was not cached so our assumption above was wrong. - hr = S_OK; + cache = BURN_CACHE_STATE_PARTIAL; // found a payload that was not cached so we are partial. + hr = S_OK; + } } } } - pPackage->fCached = fAllPayloadsCached; + pPackage->cache = cache; LExit: ReleaseStr(sczPayloadCachePath); diff --git a/src/burn/engine/core.h b/src/burn/engine/core.h index f099e576..ceb032f7 100644 --- a/src/burn/engine/core.h +++ b/src/burn/engine/core.h @@ -42,13 +42,16 @@ const LPCWSTR BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES = L"burn.ignoredependen const LPCWSTR BURN_COMMANDLINE_SWITCH_PREFIX = L"burn."; const LPCWSTR BURN_BUNDLE_LAYOUT_DIRECTORY = L"WixBundleLayoutDirectory"; +const LPCWSTR BURN_BUNDLE_ACTION = L"WixBundleAction"; +const LPCWSTR BURN_BUNDLE_INSTALLED = L"WixBundleInstalled"; const LPCWSTR BURN_BUNDLE_ELEVATED = L"WixBundleElevated"; +const LPCWSTR BURN_BUNDLE_PROVIDER_KEY = L"WixBundleProviderKey"; const LPCWSTR BURN_BUNDLE_TAG = L"WixBundleTag"; // The following constants must stay in sync with src\wix\Binder.cs const LPCWSTR BURN_BUNDLE_NAME = L"WixBundleName"; const LPCWSTR BURN_BUNDLE_ORIGINAL_SOURCE = L"WixBundleOriginalSource"; -const LPCWSTR BURN_BUNDLE_PROVIDER_KEY = L"WixBundleProviderKey"; +const LPCWSTR BURN_BUNDLE_LAST_USED_SOURCE = L"WixBundleLastUsedSource"; // enums @@ -69,6 +72,13 @@ enum BURN_ELEVATION_STATE BURN_ELEVATION_STATE_ELEVATED_EXPLICITLY, }; +enum BURN_AU_PAUSE_ACTION +{ + BURN_AU_PAUSE_ACTION_NONE, + BURN_AU_PAUSE_ACTION_IFELEVATED, + BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME, +}; + // structs @@ -110,10 +120,12 @@ typedef struct _BURN_ENGINE_STATE BURN_PLAN plan; BURN_MODE mode; + BURN_AU_PAUSE_ACTION automaticUpdates; BURN_ELEVATION_STATE elevationState; DWORD dwElevatedLoggingTlsId; + LPWSTR sczBundleEngineWorkingPath; BURN_PIPE_CONNECTION companionConnection; BURN_PIPE_CONNECTION embeddedConnection; diff --git a/src/burn/engine/downloadengine.cpp b/src/burn/engine/downloadengine.cpp index 476bc5fe..f58bc79d 100644 --- a/src/burn/engine/downloadengine.cpp +++ b/src/burn/engine/downloadengine.cpp @@ -97,7 +97,6 @@ static HRESULT OpenRequest( ); static HRESULT SendRequest( __in HINTERNET hUrl, - __in_z LPCWSTR wzMethod, __inout_z LPWSTR* psczUrl, __out BOOL* pfRetry, __out BOOL* pfRangesAccepted @@ -507,7 +506,7 @@ static HRESULT MakeRequest( hr = OpenRequest(hConnect, wzMethod, uri.scheme, uri.sczPath, uri.sczQueryString, wzHeaders, &hUrl); ExitOnFailure1(hr, "Failed to open internet URL: %ls", *psczSourceUrl); - hr = SendRequest(hUrl, wzMethod, psczSourceUrl, &fRetry, pfRangeRequestsAccepted); + hr = SendRequest(hUrl, psczSourceUrl, &fRetry, pfRangeRequestsAccepted); ExitOnFailure1(hr, "Failed to send request to URL: %ls", *psczSourceUrl); } while (fRetry); @@ -578,7 +577,6 @@ static HRESULT OpenRequest( static HRESULT SendRequest( __in HINTERNET hUrl, - __in_z LPCWSTR wzMethod, __inout_z LPWSTR* psczUrl, __out BOOL* pfRetry, __out BOOL* pfRangesAccepted @@ -596,8 +594,6 @@ static HRESULT SendRequest( hr = InternetQueryInfoNumber(hUrl, HTTP_QUERY_STATUS_CODE, &lCode); ExitOnFailure1(hr, "Failed to get HTTP status code for URL: %ls", *psczUrl); - LogStringLine(REPORT_STANDARD, "Download engine HTTP %d %ls to %ls", lCode, wzMethod, *psczUrl); - switch (lCode) { case 200: // OK but range requests don't work. diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 5ef835a4..ece20e30 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -26,6 +26,8 @@ const DWORD BURN_TIMEOUT = 5 * 60 * 1000; // TODO: is 5 minutes good? typedef enum _BURN_ELEVATION_MESSAGE_TYPE { BURN_ELEVATION_MESSAGE_TYPE_UNKNOWN, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE, BURN_ELEVATION_MESSAGE_TYPE_SESSION_BEGIN, BURN_ELEVATION_MESSAGE_TYPE_SESSION_RESUME, BURN_ELEVATION_MESSAGE_TYPE_SESSION_END, @@ -67,6 +69,8 @@ typedef struct _BURN_ELEVATION_CHILD_MESSAGE_CONTEXT { DWORD dwLoggingTlsId; HANDLE hPipe; + HANDLE* phLock; + BOOL* pfDisabledAutomaticUpdates; BURN_PACKAGES* pPackages; BURN_RELATED_BUNDLES* pRelatedBundles; BURN_PAYLOADS* pPayloads; @@ -116,6 +120,15 @@ static HRESULT OnGenericExecuteFilesInUse( __in LPVOID pvContext, __out DWORD* pdwResult ); +static HRESULT OnApplyInitialize( + __in HANDLE* phLock, + __in BOOL* pfDisabledWindowsUpdate, + __in BYTE* pbData, + __in DWORD cbData + ); +static HRESULT OnApplyUninitialize( + __in HANDLE* phLock + ); static HRESULT OnSessionBegin( __in BURN_REGISTRATION* pRegistration, __in BURN_VARIABLES* pVariables, @@ -222,7 +235,6 @@ extern "C" HRESULT ElevationElevate( HRESULT hr = S_OK; int nResult = IDOK; - LPWSTR sczEngineWorkingPath = NULL; HANDLE hPipesCreatedEvent = INVALID_HANDLE_VALUE; LPWSTR sczError = NULL; @@ -230,9 +242,6 @@ extern "C" HRESULT ElevationElevate( hr = UserExperienceInterpretResult(&pEngineState->userExperience, MB_OKCANCEL, nResult); ExitOnRootFailure(hr, "UX aborted elevation requirement."); - hr = CacheBundleToWorkingDirectory(pEngineState->registration.fPerMachine, pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->userExperience.payloads, &pEngineState->section, &sczEngineWorkingPath); - ExitOnFailure(hr, "Failed to cache engine to working directory."); - hr = PipeCreateNameAndSecret(&pEngineState->companionConnection.sczName, &pEngineState->companionConnection.sczSecret); ExitOnFailure(hr, "Failed to create pipe name and client token."); @@ -244,7 +253,7 @@ extern "C" HRESULT ElevationElevate( nResult = IDOK; // Create the elevated process and if successful, wait for it to connect. - hr = PipeLaunchChildProcess(sczEngineWorkingPath, &pEngineState->companionConnection, TRUE, hwndParent); + hr = PipeLaunchChildProcess(pEngineState->sczBundleEngineWorkingPath, &pEngineState->companionConnection, TRUE, hwndParent); if (SUCCEEDED(hr)) { hr = PipeWaitForChildConnect(&pEngineState->companionConnection); @@ -267,7 +276,6 @@ extern "C" HRESULT ElevationElevate( LExit: ReleaseStr(sczError); ReleaseHandle(hPipesCreatedEvent); - ReleaseStr(sczEngineWorkingPath); if (FAILED(hr)) { @@ -277,6 +285,53 @@ extern "C" HRESULT ElevationElevate( return hr; } +extern "C" HRESULT ElevationApplyInitialize( + __in HANDLE hPipe, + __in BURN_AU_PAUSE_ACTION auAction + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)auAction); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationApplyUninitialize( + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + /******************************************************************* ElevationSessionBegin - @@ -543,6 +598,7 @@ extern "C" HRESULT ElevationExecuteExePackage( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction, __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -561,6 +617,9 @@ extern "C" HRESULT ElevationExecuteExePackage( hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->exePackage.action); ExitOnFailure(hr, "Failed to write action to message buffer."); + hr = BuffWriteNumber(&pbData, &cbData, fRollback); + ExitOnFailure(hr, "Failed to write rollback."); + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->exePackage.sczIgnoreDependencies); ExitOnFailure(hr, "Failed to write the list of dependencies to ignore to the message buffer."); @@ -730,6 +789,7 @@ extern "C" HRESULT ElevationExecuteMspPackage( extern "C" HRESULT ElevationExecuteMsuPackage( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -751,6 +811,9 @@ extern "C" HRESULT ElevationExecuteMsuPackage( hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msuPackage.action); ExitOnFailure(hr, "Failed to write action to message buffer."); + hr = BuffWriteNumber(&pbData, &cbData, fRollback); + ExitOnFailure(hr, "Failed to write rollback."); + // send message context.pfnGenericMessageHandler = pfnGenericMessageHandler; context.pvContext = pvContext; @@ -844,6 +907,8 @@ extern "C" HRESULT ElevationChildPumpMessages( __in BURN_VARIABLES* pVariables, __in BURN_REGISTRATION* pRegistration, __in BURN_USER_EXPERIENCE* pUserExperience, + __out HANDLE* phLock, + __out BOOL* pfDisabledAutomaticUpdates, __out DWORD* pdwChildExitCode, __out BOOL* pfRestart ) @@ -865,6 +930,8 @@ extern "C" HRESULT ElevationChildPumpMessages( context.dwLoggingTlsId = dwLoggingTlsId; context.hPipe = hPipe; + context.phLock = phLock; + context.pfDisabledAutomaticUpdates = pfDisabledAutomaticUpdates; context.pPackages = pPackages; context.pRelatedBundles = pRelatedBundles; context.pPayloads = pPayloads; @@ -891,6 +958,16 @@ extern "C" HRESULT ElevationChildPumpMessages( return hr; } +extern "C" HRESULT ElevationChildResumeAutomaticUpdates() +{ + HRESULT hr = S_OK; + + hr = WuaResumeAutomaticUpdates(); + ExitOnFailure(hr, "Failed to resume automatic updates after pausing them, continuing..."); + +LExit: + return hr; +} // internal function definitions @@ -960,8 +1037,8 @@ static HRESULT ProcessGenericExecuteMessages( HRESULT hr = S_OK; SIZE_T iData = 0; BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + DWORD dwAllowedResults = 0; DWORD dwProgress = 0; - DWORD dwTotal = 0; DWORD dwResult = 0; GENERIC_EXECUTE_MESSAGE message = { }; @@ -970,15 +1047,15 @@ static HRESULT ProcessGenericExecuteMessages( { case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS: // read message parameters - hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &dwProgress); + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &dwAllowedResults); ExitOnFailure(hr, "Failed to read progress."); - hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &dwTotal); + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &dwProgress); ExitOnFailure(hr, "Failed to read total."); message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; - message.progress.dwPercentage = 100 * dwProgress / dwTotal; + message.dwAllowedResults = dwAllowedResults; + message.progress.dwPercentage = dwProgress; // send message dwResult = (DWORD)pContext->pfnGenericMessageHandler(&message, pContext->pvContext); break; @@ -986,6 +1063,7 @@ static HRESULT ProcessGenericExecuteMessages( case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE: hr = OnGenericExecuteFilesInUse(pMsg->pvData, pMsg->cbData, pContext->pfnGenericMessageHandler, pContext->pvContext, &dwResult); break; + default: hr = E_INVALIDARG; ExitOnRootFailure(hr, "Invalid package message."); @@ -1125,6 +1203,14 @@ static HRESULT ProcessElevatedChildMessage( switch (pMsg->dwMessage) { + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE: + hrResult = OnApplyInitialize(pContext->phLock, pContext->pfDisabledAutomaticUpdates, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE: + hrResult = OnApplyUninitialize(pContext->phLock); + break; + case BURN_ELEVATION_MESSAGE_TYPE_SESSION_BEGIN: hrResult = OnSessionBegin(pContext->pRegistration, pContext->pVariables, pContext->pUserExperience, (BYTE*)pMsg->pvData, pMsg->cbData); break; @@ -1287,6 +1373,64 @@ static HRESULT OnGenericExecuteFilesInUse( return hr; } +static HRESULT OnApplyInitialize( + __in HANDLE* phLock, + __in BOOL* pfDisabledWindowsUpdate, + __in BYTE* pbData, + __in DWORD cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + DWORD dwAUAction = 0; + + // deserialize message data + hr = BuffReadNumber(pbData, cbData, &iData, &dwAUAction); + ExitOnFailure(hr, "Failed to read action."); + + // initialize. + hr = ApplyLock(TRUE, phLock); + ExitOnFailure(hr, "Failed to acquire lock due to setup in other session."); + + // Attempt to pause AU with best effort. + if (BURN_AU_PAUSE_ACTION_IFELEVATED == dwAUAction || BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME == dwAUAction) + { + hr = WuaPauseAutomaticUpdates(); + if (FAILED(hr)) + { + LogId(REPORT_STANDARD, MSG_FAILED_PAUSE_AU, hr); + hr = S_OK; + } + else if (BURN_AU_PAUSE_ACTION_IFELEVATED == dwAUAction) + { + *pfDisabledWindowsUpdate = TRUE; + } + } + + // TODO: start a system restore point. + +LExit: + return hr; +} + +static HRESULT OnApplyUninitialize( + __in HANDLE* phLock + ) +{ + Assert(phLock); + + // TODO: end system restore point. + + if (*phLock) + { + ::ReleaseMutex(*phLock); + ::CloseHandle(*phLock); + *phLock = NULL; + } + + return S_OK; +} + static HRESULT OnSessionBegin( __in BURN_REGISTRATION* pRegistration, __in BURN_VARIABLES* pVariables, @@ -1523,6 +1667,7 @@ static HRESULT OnExecuteExePackage( HRESULT hr = S_OK; SIZE_T iData = 0; LPWSTR sczPackage = NULL; + DWORD dwRollback = 0; BURN_EXECUTE_ACTION executeAction = { }; LPWSTR sczIgnoreDependencies = NULL; BOOTSTRAPPER_APPLY_RESTART exeRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; @@ -1536,6 +1681,9 @@ static HRESULT OnExecuteExePackage( hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.exePackage.action); ExitOnFailure(hr, "Failed to read action."); + hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); + ExitOnFailure(hr, "Failed to read rollback."); + hr = BuffReadString(pbData, cbData, &iData, &sczIgnoreDependencies); ExitOnFailure(hr, "Failed to read the list of dependencies to ignore."); @@ -1557,7 +1705,7 @@ static HRESULT OnExecuteExePackage( } // execute EXE package - hr = ExeEngineExecutePackage(&executeAction, pVariables, GenericExecuteMessageHandler, hPipe, &exeRestart); + hr = ExeEngineExecutePackage(&executeAction, pVariables, static_cast(dwRollback), GenericExecuteMessageHandler, hPipe, &exeRestart); ExitOnFailure(hr, "Failed to execute EXE package."); LExit: @@ -1762,6 +1910,7 @@ static HRESULT OnExecuteMsuPackage( HRESULT hr = S_OK; SIZE_T iData = 0; LPWSTR sczPackage = NULL; + DWORD dwRollback = 0; BURN_EXECUTE_ACTION executeAction = { }; BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; @@ -1777,11 +1926,14 @@ static HRESULT OnExecuteMsuPackage( hr = BuffReadNumber(pbData, cbData, &iData, reinterpret_cast(&executeAction.msuPackage.action)); ExitOnFailure(hr, "Failed to read action."); + hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); + ExitOnFailure(hr, "Failed to read rollback."); + hr = PackageFindById(pPackages, sczPackage, &executeAction.msuPackage.pPackage); ExitOnFailure1(hr, "Failed to find package: %ls", sczPackage); // execute MSU package - hr = MsuEngineExecutePackage(&executeAction, GenericExecuteMessageHandler, hPipe, &restart); + hr = MsuEngineExecutePackage(&executeAction, static_cast(dwRollback), GenericExecuteMessageHandler, hPipe, &restart); ExitOnFailure(hr, "Failed to execute MSU package."); LExit: diff --git a/src/burn/engine/elevation.h b/src/burn/engine/elevation.h index aba14beb..c92c6ce7 100644 --- a/src/burn/engine/elevation.h +++ b/src/burn/engine/elevation.h @@ -31,6 +31,13 @@ HRESULT ElevationElevate( __in BURN_ENGINE_STATE* pEngineState, __in_opt HWND hwndParent ); +HRESULT ElevationApplyInitialize( + __in HANDLE hPipe, + __in BURN_AU_PAUSE_ACTION auAction + ); +HRESULT ElevationApplyUninitialize( + __in HANDLE hPipe + ); HRESULT ElevationSessionBegin( __in HANDLE hPipe, __in_z LPCWSTR wzEngineWorkingPath, @@ -77,6 +84,7 @@ HRESULT ElevationExecuteExePackage( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction, __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -104,6 +112,7 @@ HRESULT ElevationExecuteMspPackage( HRESULT ElevationExecuteMsuPackage( __in HANDLE hPipe, __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -135,9 +144,12 @@ HRESULT ElevationChildPumpMessages( __in BURN_VARIABLES* pVariables, __in BURN_REGISTRATION* pRegistration, __in BURN_USER_EXPERIENCE* pUserExperience, + __out HANDLE* phLock, + __out BOOL* pfDisabledAutomaticUpdates, __out DWORD* pdwChildExitCode, __out BOOL* pfRestart ); +HRESULT ElevationChildResumeAutomaticUpdates(); #ifdef __cplusplus } diff --git a/src/burn/engine/engine.cpp b/src/burn/engine/engine.cpp index a4ff0ac2..255f0de4 100644 --- a/src/burn/engine/engine.cpp +++ b/src/burn/engine/engine.cpp @@ -212,6 +212,7 @@ static void InitializeEngineState( { BOOL fElevated = FALSE; + pEngineState->automaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED; pEngineState->dwElevatedLoggingTlsId = TLS_OUT_OF_INDEXES; ::InitializeCriticalSection(&pEngineState->csActive); ::InitializeCriticalSection(&pEngineState->userExperience.csEngineActive); @@ -230,6 +231,7 @@ static void UninitializeEngineState( PipeConnectionUninitialize(&pEngineState->embeddedConnection); PipeConnectionUninitialize(&pEngineState->companionConnection); + ReleaseStr(pEngineState->sczBundleEngineWorkingPath) ReleaseHandle(pEngineState->hMessageWindowThread); @@ -276,6 +278,10 @@ static HRESULT RunNormal( hr = LoggingOpen(&pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->registration.sczDisplayName); ExitOnFailure(hr, "Failed to open log."); + // Ensure the cache functions are initialized since we might use them soon. + hr = CacheInitialize(&pEngineState->registration, &pEngineState->variables); + ExitOnFailure(hr, "Failed to initialize internal cache functionality."); + // When launched explicitly unelevated, create the pipes so the elevated process can connect. if (BURN_ELEVATION_STATE_UNELEVATED_EXPLICITLY == pEngineState->elevationState) { @@ -293,9 +299,6 @@ static HRESULT RunNormal( ExitOnFailure(hr, "Failed to connect to elevated parent process."); ReleaseHandle(hPipesCreatedEvent); - - hr = CacheBundleToWorkingDirectory(pEngineState->registration.fPerMachine, pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->userExperience.payloads, &pEngineState->section, NULL); - ExitOnFailure(hr, "Failed to cache engine to working directory when launched unelevated."); } // Ensure we're on a supported operating system. @@ -323,7 +326,10 @@ static HRESULT RunNormal( hr = CoreQueryRegistration(pEngineState); ExitOnFailure(hr, "Failed to query registration."); - // set the registration variables + // Set some built-in variables before loading the BA. + hr = PlanSetVariables(pEngineState->command.action, &pEngineState->variables); + ExitOnFailure(hr, "Failed to set action variables."); + hr = RegistrationSetVariables(&pEngineState->registration, &pEngineState->variables); ExitOnFailure(hr, "Failed to set registration variables."); @@ -334,9 +340,6 @@ static HRESULT RunNormal( ExitOnFailure(hr, "Failed to set layout directory variable to value provided from command-line."); } - // Ensure the original source is initialized. - CacheGetOriginalSourcePath(&pEngineState->variables, NULL, NULL); - do { fReloadApp = FALSE; @@ -373,6 +376,8 @@ static HRESULT RunElevated( ) { HRESULT hr = S_OK; + HANDLE hLock = NULL; + BOOL fDisabledAutomaticUpdates = FALSE; // If we were launched elevated implicitly, launch an unelevated copy of ourselves. if (BURN_ELEVATION_STATE_ELEVATED == pEngineState->elevationState) @@ -412,13 +417,24 @@ static HRESULT RunElevated( ExitOnFailure(hr, "Failed to create the message window."); // Pump messages from parent process. - hr = ElevationChildPumpMessages(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe, pEngineState->companionConnection.hCachePipe, &pEngineState->packages, &pEngineState->registration.relatedBundles, &pEngineState->payloads, &pEngineState->variables, &pEngineState->registration, &pEngineState->userExperience, &pEngineState->userExperience.dwExitCode, &pEngineState->fRestart); + hr = ElevationChildPumpMessages(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe, pEngineState->companionConnection.hCachePipe, &pEngineState->packages, &pEngineState->registration.relatedBundles, &pEngineState->payloads, &pEngineState->variables, &pEngineState->registration, &pEngineState->userExperience, &hLock, &fDisabledAutomaticUpdates, &pEngineState->userExperience.dwExitCode, &pEngineState->fRestart); ExitOnFailure(hr, "Failed to pump messages from parent process."); LExit: // If the message window is still around, close it. UiCloseMessageWindow(pEngineState); + if (fDisabledAutomaticUpdates) + { + ElevationChildResumeAutomaticUpdates(); + } + + if (hLock) + { + ::ReleaseMutex(hLock); + ::CloseHandle(hLock); + } + return hr; } @@ -624,6 +640,8 @@ static HRESULT Restart() HANDLE hProcessToken = NULL; TOKEN_PRIVILEGES priv = { }; + LogId(REPORT_STANDARD, MSG_RESTARTING); + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken)) { ExitWithLastError(hr, "Failed to get process token."); diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 8620dc57..3e75d73a 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -68,6 +68,13 @@ Bootstrapper application requested restart at shutdown. Planned to restart alrea . MessageId=5 +Severity=Warning +SymbolicName=MSG_RESTARTING +Language=English +Restarting computer... +. + +MessageId=6 Severity=Success SymbolicName=MSG_BA_REQUESTED_RELOAD Language=English @@ -218,7 +225,7 @@ MessageId=203 Severity=Success SymbolicName=MSG_PLANNED_MSI_FEATURE Language=English -Planned feature: %1!ls!, state: %2!hs!, requested: %3!hs!, execute action: %4!hs!, rollback action: %5!hs! +Planned feature: %1!ls!, state: %2!hs!, default requested %3!hs!, ba requested: %4!hs!, execute action: %5!hs!, rollback action: %6!hs! . MessageId=204 @@ -260,7 +267,7 @@ MessageId=301 Severity=Success SymbolicName=MSG_APPLYING_PACKAGE Language=English -Applying package: %1!ls!, action: %2!hs!, path: %3!ls!, arguments: '%4!ls!' +Applying %1!hs! package: %2!ls!, action: %3!hs!, path: %4!ls!, arguments: '%5!ls!' . MessageId=302 @@ -298,6 +305,13 @@ Language=English Attempted to uninstall absent package: %1!ls!. Continuing... . +MessageId=308 +Severity=Warning +SymbolicName=MSG_FAILED_PAUSE_AU +Language=English +Automatic updates could not be paused due to error: 0x%1!x!. Continuing... +. + MessageId=310 Severity=Error SymbolicName=MSG_FAILED_VERIFY_PAYLOAD @@ -347,6 +361,13 @@ Language=English Failed to layout payload: %2!ls! to layout directory: %3!ls!, error: %1!ls!. . +MessageId=317 +Severity=Success +SymbolicName=MSG_ROLLBACK_PACKAGE_SKIPPED +Language=English +Skipped rollback of package: %1!ls!, action: %2!hs!, already: %3!hs! +. + MessageId=320 Severity=Success SymbolicName=MSG_DEPENDENCY_BUNDLE_REGISTER @@ -424,6 +445,62 @@ Language=English Removing bundle dependency provider: %1!ls! . +MessageId=335 +Severity=Success +SymbolicName=MSG_ACQUIRE_BUNDLE_PAYLOAD +Language=English +Acquiring bundle payload: %2!ls!, %3!hs! from: %4!ls! +. + +MessageId=336 +Severity=Success +SymbolicName=MSG_ACQUIRE_CONTAINER +Language=English +Acquiring container: %1!ls!, %3!hs! from: %4!ls! +. + +MessageId=337 +Severity=Success +SymbolicName=MSG_ACQUIRE_CONTAINER_PAYLOAD +Language=English +Acquiring container: %1!ls!, payload: %2!ls!, %3!hs! from: %4!ls! +. + +MessageId=338 +Severity=Success +SymbolicName=MSG_ACQUIRE_PACKAGE_PAYLOAD +Language=English +Acquiring package: %1!ls!, payload: %2!ls!, %3!hs! from: %4!ls! +. + +MessageId=340 +Severity=Warning +SymbolicName=MSG_PROMPT_BUNDLE_PAYLOAD_SOURCE +Language=English +Prompt for source of bundle payload: %2!ls!, path: %3!ls! +. + +MessageId=341 +Severity=Warning +SymbolicName=MSG_PROMPT_CONTAINER_SOURCE +Language=English +Prompt for source of container: %1!ls!, path: %3!ls! +. + +MessageId=342 +Severity=Warning +SymbolicName=MSG_PROMPT_CONTAINER_PAYLOAD_SOURCE +Language=English +Prompt for source of container: %1!ls!, payload: %2!ls!, path: %3!ls! +. + +MessageId=343 +Severity=Warning +SymbolicName=MSG_PROMPT_PACKAGE_PAYLOAD_SOURCE +Language=English +Prompt for source of package: %1!ls!, payload: %2!ls!, path: %3!ls! +. + MessageId=348 Severity=Warning SymbolicName=MSG_APPLY_RETRYING_PACKAGE @@ -459,6 +536,27 @@ Language=English Removing cached bundle: %1!ls!, from path: %2!ls! . +MessageId=353 +Severity=Warning +SymbolicName=MSG_UNABLE_UNCACHE_PACKAGE +Language=English +Unable to remove cached package: %1!ls!, from path: %2!ls!, reason: 0x%3!x!. Continuing... +. + +MessageId=354 +Severity=Warning +SymbolicName=MSG_UNABLE_UNCACHE_BUNDLE +Language=English +Unable to remove cached bundle: %1!ls!, from path: %2!ls!, reason: 0x%3!x!. Continuing... +. + +MessageId=355 +Severity=Warning +SymbolicName=MSG_SOURCELIST_REGISTER +Language=English +Unable to register source directory: %1!ls!, product: %2!ls!, reason: 0x%3!x!. Continuing... +. + MessageId=399 Severity=Success SymbolicName=MSG_APPLY_COMPLETE diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index 19544b8b..2dbf6a6e 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -398,6 +398,7 @@ extern "C" HRESULT ExeEnginePlanAddPackage( extern "C" HRESULT ExeEngineExecutePackage( __in BURN_EXECUTE_ACTION* pExecuteAction, __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -407,9 +408,11 @@ extern "C" HRESULT ExeEngineExecutePackage( int nResult = IDNOACTION; LPCWSTR wzArguments = NULL; LPWSTR sczArgumentsFormatted = NULL; + LPWSTR sczArgumentsObfuscated = NULL; LPWSTR sczCachedDirectory = NULL; LPWSTR sczExecutablePath = NULL; LPWSTR sczCommand = NULL; + LPWSTR sczCommandObfuscated = NULL; STARTUPINFOW si = { }; PROCESS_INFORMATION pi = { }; DWORD dwExitCode = 0; @@ -449,22 +452,34 @@ extern "C" HRESULT ExeEngineExecutePackage( ExitOnFailure(hr, "Failed to format argument string."); hr = StrAllocFormatted(&sczCommand, L"\"%ls\" %s", sczExecutablePath, sczArgumentsFormatted); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = VariableFormatStringObfuscated(pVariables, wzArguments, &sczArgumentsObfuscated, NULL); + ExitOnFailure(hr, "Failed to format obfuscated argument string."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\" %s", sczExecutablePath, sczArgumentsObfuscated); } else { hr = StrAllocFormatted(&sczCommand, L"\"%ls\"", sczExecutablePath); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\"", sczExecutablePath); } - ExitOnFailure(hr, "Failed to create executable command."); + ExitOnFailure(hr, "Failed to create obfuscated executable command."); // Add the list of dependencies to ignore, if any, to the command line. if (pExecuteAction->exePackage.sczIgnoreDependencies) { hr = StrAllocFormatted(&sczCommand, L"%ls -%ls=%ls", sczCommand, BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->exePackage.sczIgnoreDependencies); ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the command line."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"%ls -%ls=%ls", sczCommandObfuscated, BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->exePackage.sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the obfuscated command line."); } // Log before we add the secret pipe name and client token for embedded processes. - LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, pExecuteAction->exePackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->exePackage.action), sczExecutablePath, sczCommand); + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pExecuteAction->exePackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->exePackage.action), sczExecutablePath, sczCommandObfuscated); if (BURN_EXE_PROTOCOL_TYPE_BURN == pExecuteAction->exePackage.pPackage->Exe.protocol) { @@ -517,9 +532,11 @@ extern "C" HRESULT ExeEngineExecutePackage( LExit: ReleaseStr(sczArgumentsFormatted); + ReleaseStr(sczArgumentsObfuscated); ReleaseStr(sczCachedDirectory); ReleaseStr(sczExecutablePath); ReleaseStr(sczCommand); + ReleaseStr(sczCommandObfuscated); ReleaseHandle(pi.hThread); ReleaseHandle(pi.hProcess); diff --git a/src/burn/engine/exeengine.h b/src/burn/engine/exeengine.h index 900a8fcf..cfcb307c 100644 --- a/src/burn/engine/exeengine.h +++ b/src/burn/engine/exeengine.h @@ -53,6 +53,7 @@ HRESULT ExeEnginePlanAddPackage( HRESULT ExeEngineExecutePackage( __in BURN_EXECUTE_ACTION* pExecuteAction, __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index 38b3fdf9..50c27549 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -219,6 +219,8 @@ extern "C" LPCSTR LoggingBurnActionToString( return "Unknown"; case BOOTSTRAPPER_ACTION_HELP: return "Help"; + case BOOTSTRAPPER_ACTION_LAYOUT: + return "Layout"; case BOOTSTRAPPER_ACTION_UNINSTALL: return "Uninstall"; case BOOTSTRAPPER_ACTION_INSTALL: @@ -325,6 +327,23 @@ extern "C" LPCSTR LoggingPackageStateToString( } } +extern "C" LPCSTR LoggingCacheStateToString( + __in BURN_CACHE_STATE cacheState + ) +{ + switch (cacheState) + { + case BURN_CACHE_STATE_NONE: + return "None"; + case BURN_CACHE_STATE_PARTIAL: + return "Partial"; + case BURN_CACHE_STATE_COMPLETE: + return "Complete"; + default: + return "Invalid"; + } +} + extern "C" LPCSTR LoggingMsiFeatureStateToString( __in BOOTSTRAPPER_FEATURE_STATE featureState ) @@ -450,6 +469,13 @@ extern "C" LPCSTR LoggingRequestStateToString( } } +extern "C" LPCSTR LoggingRollbackOrExecute( + __in BOOL fRollback + ) +{ + return fRollback ? "rollback" : "execute"; +} + extern "C" LPWSTR LoggingStringOrUnknownIfNull( __in LPCWSTR wz ) diff --git a/src/burn/engine/logging.h b/src/burn/engine/logging.h index 8a13364b..6522f6a4 100644 --- a/src/burn/engine/logging.h +++ b/src/burn/engine/logging.h @@ -100,6 +100,10 @@ LPCSTR LoggingPackageStateToString( __in BOOTSTRAPPER_PACKAGE_STATE packageState ); +LPCSTR LoggingCacheStateToString( + __in BURN_CACHE_STATE cacheState + ); + LPCSTR LoggingMsiFeatureStateToString( __in BOOTSTRAPPER_FEATURE_STATE featureState ); @@ -124,6 +128,10 @@ LPCSTR LoggingRequestStateToString( __in BOOTSTRAPPER_REQUEST_STATE requestState ); +LPCSTR LoggingRollbackOrExecute( + __in BOOL fRollback + ); + LPWSTR LoggingStringOrUnknownIfNull( __in LPCWSTR wz ); diff --git a/src/burn/engine/msiengine.cpp b/src/burn/engine/msiengine.cpp index 3d55b4a2..40c2025e 100644 --- a/src/burn/engine/msiengine.cpp +++ b/src/burn/engine/msiengine.cpp @@ -582,18 +582,18 @@ extern "C" HRESULT MsiEngineDetectPackage( { BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; - // get current state - if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == pPackage->currentState) // only try to detect features if the product is installed + // Try to detect features state if the product is present on the machine. + if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT <= pPackage->currentState) { hr = WiuQueryFeatureState(pPackage->Msi.sczProductCode, pFeature->sczId, &installState); ExitOnFailure(hr, "Failed to query feature state."); - if (INSTALLSTATE_UNKNOWN == installState) // in case of an upgrade this could happen + if (INSTALLSTATE_UNKNOWN == installState) // in case of an upgrade a feature could be removed. { installState = INSTALLSTATE_ABSENT; } } - else + else // MSI not installed then the features can't be either. { installState = INSTALLSTATE_ABSENT; } @@ -648,8 +648,6 @@ extern "C" HRESULT MsiEnginePlanCalculatePackage( DWORD64 qwInstalledVersion = pPackage->Msi.qwInstalledVersion; BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; - BOOTSTRAPPER_FEATURE_STATE featureRequestedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; - BOOTSTRAPPER_FEATURE_STATE featureExpectedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; BOOL fFeatureActionDelta = FALSE; BOOL fRollbackFeatureActionDelta = FALSE; int nResult = 0; @@ -662,14 +660,20 @@ extern "C" HRESULT MsiEnginePlanCalculatePackage( for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) { BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + BOOTSTRAPPER_FEATURE_STATE defaultFeatureRequestedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + BOOTSTRAPPER_FEATURE_STATE featureRequestedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + BOOTSTRAPPER_FEATURE_STATE featureExpectedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; // evaluate feature conditions - hr = EvaluateActionStateConditions(pVariables, pFeature->sczAddLocalCondition, pFeature->sczAddSourceCondition, pFeature->sczAdvertiseCondition, &featureRequestedState); + hr = EvaluateActionStateConditions(pVariables, pFeature->sczAddLocalCondition, pFeature->sczAddSourceCondition, pFeature->sczAdvertiseCondition, &defaultFeatureRequestedState); ExitOnFailure(hr, "Failed to evaluate requested state conditions."); hr = EvaluateActionStateConditions(pVariables, pFeature->sczRollbackAddLocalCondition, pFeature->sczRollbackAddSourceCondition, pFeature->sczRollbackAdvertiseCondition, &featureExpectedState); ExitOnFailure(hr, "Failed to evaluate expected state conditions."); + // Remember the default feature requested state so the engine doesn't get blamed for planning the wrong thing if the UX changes it. + featureRequestedState = defaultFeatureRequestedState; + // send MSI feature plan message to UX nResult = pUserExperience->pUserExperience->OnPlanMsiFeature(pPackage->sczId, pFeature->sczId, &featureRequestedState); hr = UserExperienceInterpretResult(pUserExperience, MB_OKCANCEL, nResult); @@ -682,7 +686,7 @@ extern "C" HRESULT MsiEnginePlanCalculatePackage( hr = CalculateFeatureAction(featureRequestedState, BOOTSTRAPPER_FEATURE_ACTION_NONE == pFeature->execute ? featureExpectedState : pFeature->currentState, FALSE, &pFeature->rollback, &fRollbackFeatureActionDelta); ExitOnFailure(hr, "Failed to calculate rollback feature state."); - LogId(REPORT_STANDARD, MSG_PLANNED_MSI_FEATURE, pFeature->sczId, LoggingMsiFeatureStateToString(pFeature->currentState), LoggingMsiFeatureStateToString(featureRequestedState), LoggingMsiFeatureActionToString(pFeature->execute), LoggingMsiFeatureActionToString(pFeature->rollback)); + LogId(REPORT_STANDARD, MSG_PLANNED_MSI_FEATURE, pFeature->sczId, LoggingMsiFeatureStateToString(pFeature->currentState), LoggingMsiFeatureStateToString(defaultFeatureRequestedState), LoggingMsiFeatureStateToString(featureRequestedState), LoggingMsiFeatureActionToString(pFeature->execute), LoggingMsiFeatureActionToString(pFeature->rollback)); } } @@ -837,6 +841,29 @@ extern "C" HRESULT MsiEnginePlanAddPackage( ExitOnFailure(hr, "Failed to plan package cache syncpoint"); } + // add rollback action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; + pAction->msiPackage.pPackage = pPackage; + pAction->msiPackage.uiLevel = MsiEngineCalculateInstallLevel(pPackage->Msi.fDisplayInternalUI, display); + pAction->msiPackage.action = pPackage->rollback; + pAction->msiPackage.rgFeatures = rgRollbackFeatureActions; + rgRollbackFeatureActions = NULL; + + LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, &pAction->msiPackage.sczLogPath); // ignore errors. + pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; + + // Plan a checkpoint between rollback and execute so that we always attempt + // rollback in the case that the MSI was not able to rollback itself (e.g. + // user pushes cancel after InstallFinalize). + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to append execute checkpoint."); + } + // add execute action if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute) { @@ -862,23 +889,6 @@ extern "C" HRESULT MsiEnginePlanAddPackage( pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; } - // add rollback action - if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) - { - hr = PlanAppendRollbackAction(pPlan, &pAction); - ExitOnFailure(hr, "Failed to append rollback action."); - - pAction->type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; - pAction->msiPackage.pPackage = pPackage; - pAction->msiPackage.uiLevel = MsiEngineCalculateInstallLevel(pPackage->Msi.fDisplayInternalUI, display); - pAction->msiPackage.action = pPackage->rollback; - pAction->msiPackage.rgFeatures = rgRollbackFeatureActions; - rgRollbackFeatureActions = NULL; - - LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, &pAction->msiPackage.sczLogPath); // ignore errors. - pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; - } - // Update any slipstream patches' state. for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) { @@ -909,9 +919,41 @@ extern "C" HRESULT MsiEngineExecutePackage( WIU_MSI_EXECUTE_CONTEXT context = { }; WIU_RESTART restart = WIU_RESTART_NONE; + LPWSTR sczInstalledVersion = NULL; LPWSTR sczCachedDirectory = NULL; LPWSTR sczMsiPath = NULL; LPWSTR sczProperties = NULL; + LPWSTR sczObfuscatedProperties = NULL; + + // During rollback, if the package is already in the rollback state we expect don't + // touch it again. + if (fRollback) + { + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->msiPackage.action) + { + hr = WiuGetProductInfoEx(pExecuteAction->msiPackage.pPackage->Msi.sczProductCode, NULL, pExecuteAction->msiPackage.pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (FAILED(hr)) // package not present. + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pExecuteAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_ABSENT)); + + hr = S_OK; + ExitFunction(); + } + } + else if (BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->msiPackage.action) + { + hr = WiuGetProductInfoEx(pExecuteAction->msiPackage.pPackage->Msi.sczProductCode, NULL, pExecuteAction->msiPackage.pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (SUCCEEDED(hr)) // package present. + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pExecuteAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_PRESENT)); + + hr = S_OK; + ExitFunction(); + } + + hr = S_OK; + } + } // Default to "verbose" logging and set extra debug mode only if explicitly required. DWORD dwLogMode = INSTALLLOGMODE_VERBOSE; @@ -939,21 +981,30 @@ extern "C" HRESULT MsiEngineExecutePackage( } // set up properties - hr = MsiEngineConcatProperties(pExecuteAction->msiPackage.pPackage->Msi.rgProperties, pExecuteAction->msiPackage.pPackage->Msi.cProperties, pVariables, fRollback, &sczProperties); + hr = MsiEngineConcatProperties(pExecuteAction->msiPackage.pPackage->Msi.rgProperties, pExecuteAction->msiPackage.pPackage->Msi.cProperties, pVariables, fRollback, &sczProperties, FALSE); ExitOnFailure(hr, "Failed to add properties to argument string."); + hr = MsiEngineConcatProperties(pExecuteAction->msiPackage.pPackage->Msi.rgProperties, pExecuteAction->msiPackage.pPackage->Msi.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); + ExitOnFailure(hr, "Failed to add obfuscated properties to argument string."); + // add feature action properties hr = ConcatFeatureActionProperties(pExecuteAction->msiPackage.pPackage, pExecuteAction->msiPackage.rgFeatures, &sczProperties); ExitOnFailure(hr, "Failed to add feature action properties to argument string."); + hr = ConcatFeatureActionProperties(pExecuteAction->msiPackage.pPackage, pExecuteAction->msiPackage.rgFeatures, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add feature action properties to obfuscated argument string."); + // add patch properties, except on uninstall because that can confuse the Windows Installer in some situations. if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL != pExecuteAction->msiPackage.action) { hr = ConcatPatchProperty(pExecuteAction->msiPackage.pPackage, &sczProperties); ExitOnFailure(hr, "Failed to add patch properties to argument string."); + + hr = ConcatPatchProperty(pExecuteAction->msiPackage.pPackage, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add patch properties to obfuscated argument string."); } - LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, pExecuteAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), sczMsiPath, sczProperties ? sczProperties : L""); + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pExecuteAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), sczMsiPath, sczObfuscatedProperties ? sczObfuscatedProperties : L""); // // Do the actual action. @@ -1024,11 +1075,7 @@ extern "C" HRESULT MsiEngineExecutePackage( hr = WiuConfigureProductEx(pExecuteAction->msiPackage.pPackage->Msi.sczProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT, sczProperties, &restart); if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) { - if (!fRollback) - { - LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pExecuteAction->msiPackage.pPackage->sczId); - } - + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pExecuteAction->msiPackage.pPackage->sczId); hr = S_OK; } ExitOnFailure(hr, "Failed to uninstall MSI package."); @@ -1051,8 +1098,10 @@ extern "C" HRESULT MsiEngineExecutePackage( WiuUninitializeExternalUI(&context); ReleaseStr(sczProperties); + ReleaseStr(sczObfuscatedProperties); ReleaseStr(sczMsiPath); ReleaseStr(sczCachedDirectory); + ReleaseStr(sczInstalledVersion); switch (restart) { @@ -1077,7 +1126,8 @@ extern "C" HRESULT MsiEngineConcatProperties( __in DWORD cProperties, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, - __deref_out_z LPWSTR* psczProperties + __deref_out_z LPWSTR* psczProperties, + __in BOOL fObfuscateHiddenVariables ) { HRESULT hr = S_OK; @@ -1090,7 +1140,14 @@ extern "C" HRESULT MsiEngineConcatProperties( BURN_MSIPROPERTY* pProperty = &rgProperties[i]; // format property value - hr = VariableFormatString(pVariables, (fRollback && pProperty->sczRollbackValue) ? pProperty->sczRollbackValue : pProperty->sczValue, &sczValue, NULL); + if (fObfuscateHiddenVariables) + { + hr = VariableFormatStringObfuscated(pVariables, (fRollback && pProperty->sczRollbackValue) ? pProperty->sczRollbackValue : pProperty->sczValue, &sczValue, NULL); + } + else + { + hr = VariableFormatString(pVariables, (fRollback && pProperty->sczRollbackValue) ? pProperty->sczRollbackValue : pProperty->sczValue, &sczValue, NULL); + } ExitOnFailure(hr, "Failed to format property value."); // escape property value @@ -1635,7 +1692,11 @@ static void RegisterSourceDirectory( ExitOnFailure1(hr, "Failed to get directory for path: %ls", wzMsiPath); hr = WiuSourceListAddSourceEx(pPackage->Msi.sczProductCode, NULL, dwContext, MSICODE_PRODUCT, sczMsiDirectory, 1); - ExitOnFailure2(hr, "Failed to register source directory: %ls; product: %ls", sczMsiDirectory, pPackage->Msi.sczProductCode); + if (FAILED(hr)) + { + LogId(REPORT_VERBOSE, MSG_SOURCELIST_REGISTER, sczMsiDirectory, pPackage->Msi.sczProductCode, hr); + ExitFunction(); + } LExit: ReleaseStr(sczMsiDirectory); diff --git a/src/burn/engine/msiengine.h b/src/burn/engine/msiengine.h index 22a2a443..9d017fb8 100644 --- a/src/burn/engine/msiengine.h +++ b/src/burn/engine/msiengine.h @@ -71,7 +71,8 @@ HRESULT MsiEngineConcatProperties( __in DWORD cProperties, __in BURN_VARIABLES* pVariables, __in BOOL fRollback, - __deref_out_z LPWSTR* psczProperties + __deref_out_z LPWSTR* psczProperties, + __in BOOL fObfuscateHiddenVariables ); INSTALLUILEVEL MsiEngineCalculateInstallLevel( __in BOOL fDisplayInternalUI, diff --git a/src/burn/engine/mspengine.cpp b/src/burn/engine/mspengine.cpp index 70914a63..c96ee133 100644 --- a/src/burn/engine/mspengine.cpp +++ b/src/burn/engine/mspengine.cpp @@ -436,6 +436,7 @@ extern "C" HRESULT MspEngineExecutePackage( LPWSTR sczMspPath = NULL; LPWSTR sczPatches = NULL; LPWSTR sczProperties = NULL; + LPWSTR sczObfuscatedProperties = NULL; // default to "verbose" logging DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; @@ -488,10 +489,13 @@ extern "C" HRESULT MspEngineExecutePackage( } // set up properties - hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczProperties); + hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczProperties, FALSE); ExitOnFailure(hr, "Failed to add properties to argument string."); - LogId(REPORT_STANDARD, MSG_APPLYING_PATCH_PACKAGE, pExecuteAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pExecuteAction->mspTarget.action), sczMspPath, sczProperties, pExecuteAction->mspTarget.sczTargetProductCode); + hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); + ExitOnFailure(hr, "Failed to add properties to obfuscated argument string."); + + LogId(REPORT_STANDARD, MSG_APPLYING_PATCH_PACKAGE, pExecuteAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pExecuteAction->mspTarget.action), sczMspPath, sczObfuscatedProperties, pExecuteAction->mspTarget.sczTargetProductCode); // // Do the actual action. @@ -537,6 +541,7 @@ extern "C" HRESULT MspEngineExecutePackage( ReleaseStr(sczCachedDirectory); ReleaseStr(sczMspPath); ReleaseStr(sczProperties); + ReleaseStr(sczObfuscatedProperties); ReleaseStr(sczPatches); switch (restart) diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp index ec520f04..f69794a6 100644 --- a/src/burn/engine/msuengine.cpp +++ b/src/burn/engine/msuengine.cpp @@ -251,6 +251,7 @@ extern "C" HRESULT MsuEnginePlanAddPackage( extern "C" HRESULT MsuEngineExecutePackage( __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart @@ -314,7 +315,7 @@ extern "C" HRESULT MsuEngineExecutePackage( ExitOnFailure(hr, "Failed to append log path to MSU command-line."); } - LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, pExecuteAction->msuPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msuPackage.action), sczMsuPath ? sczMsuPath : pExecuteAction->msuPackage.pPackage->Msu.sczKB, sczCommand); + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pExecuteAction->msuPackage.pPackage->sczId, LoggingActionStateToString(pExecuteAction->msuPackage.action), sczMsuPath ? sczMsuPath : pExecuteAction->msuPackage.pPackage->Msu.sczKB, sczCommand); hr = EnsureWUServiceEnabled(&schWu, &fWuWasDisabled); ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package."); diff --git a/src/burn/engine/msuengine.h b/src/burn/engine/msuengine.h index 33998e04..182d01ba 100644 --- a/src/burn/engine/msuengine.h +++ b/src/burn/engine/msuengine.h @@ -51,6 +51,7 @@ HRESULT MsuEnginePlanAddPackage( ); HRESULT MsuEngineExecutePackage( __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h index e1e23c9d..b7876afd 100644 --- a/src/burn/engine/package.h +++ b/src/burn/engine/package.h @@ -56,6 +56,13 @@ enum BURN_PACKAGE_TYPE BURN_PACKAGE_TYPE_MSU, }; +enum BURN_CACHE_STATE +{ + BURN_CACHE_STATE_NONE, + BURN_CACHE_STATE_PARTIAL, + BURN_CACHE_STATE_COMPLETE, +}; + enum BURN_DEPENDENCY_ACTION { BURN_DEPENDENCY_ACTION_NONE, @@ -168,8 +175,9 @@ typedef struct _BURN_PACKAGE BURN_ROLLBACK_BOUNDARY* pRollbackBoundaryBackward; // used during uninstall. BOOTSTRAPPER_PACKAGE_STATE currentState; // only valid after Detect. - BOOL fCached; // only valid after Detect. + BURN_CACHE_STATE cache; // only valid after Detect. BOOTSTRAPPER_PACKAGE_STATE expected; // only valid during Plan. + BOOTSTRAPPER_REQUEST_STATE defaultRequested;// only valid during Plan. BOOTSTRAPPER_REQUEST_STATE requested; // only valid during Plan. BOOL fAcquire; // only valid during Plan. BOOL fUncache; // only valid during Plan. diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp index 81b33fc5..83d917e1 100644 --- a/src/burn/engine/plan.cpp +++ b/src/burn/engine/plan.cpp @@ -163,7 +163,6 @@ extern "C" void PlanUninitializeExecuteAction( break; case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: - ReleaseStr(pExecuteAction->msiPackage.sczProductCode); ReleaseStr(pExecuteAction->msiPackage.sczLogPath); ReleaseMem(pExecuteAction->msiPackage.rgFeatures); ReleaseMem(pExecuteAction->msiPackage.rgOrderedPatches); @@ -190,6 +189,20 @@ extern "C" void PlanUninitializeExecuteAction( } } +extern "C" HRESULT PlanSetVariables( + __in BOOTSTRAPPER_ACTION action, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + + hr = VariableSetNumeric(pVariables, BURN_BUNDLE_ACTION, action, TRUE); + ExitOnFailure(hr, "Failed to set the bundle action built-in variable."); + +LExit: + return hr; +} + extern "C" HRESULT PlanDefaultPackageRequestState( __in BURN_PACKAGE_TYPE packageType, __in BOOTSTRAPPER_PACKAGE_STATE currentState, @@ -261,6 +274,7 @@ extern "C" HRESULT PlanDefaultPackageRequestState( extern "C" HRESULT PlanLayoutBundle( __in BURN_PLAN* pPlan, __in_z LPCWSTR wzExecutableName, + __in DWORD64 qwBundleSize, __in BURN_VARIABLES* pVariables, __in BURN_PAYLOADS* pPayloads, __out_z LPWSTR* psczLayoutDirectory @@ -268,14 +282,18 @@ extern "C" HRESULT PlanLayoutBundle( { HRESULT hr = S_OK; BURN_CACHE_ACTION* pCacheAction = NULL; + LPWSTR sczExecutablePath = NULL; LPWSTR sczLayoutDirectory = NULL; // Get the layout directory. hr = VariableGetString(pVariables, BURN_BUNDLE_LAYOUT_DIRECTORY, &sczLayoutDirectory); if (E_NOTFOUND == hr) // if not set, use the current directory as the layout directory. { - hr = CacheGetOriginalSourcePath(pVariables, L"", &sczLayoutDirectory); - ExitOnFailure(hr, "Failed to get original source path as layout directory."); + hr = PathForCurrentProcess(&sczExecutablePath, NULL); + ExitOnFailure(hr, "Failed to get path for current executing process as layout directory."); + + hr = PathGetDirectory(sczExecutablePath, &sczLayoutDirectory); + ExitOnFailure(hr, "Failed to get executing process as layout directory."); } ExitOnFailure(hr, "Failed to get bundle layout directory property."); @@ -297,6 +315,10 @@ extern "C" HRESULT PlanLayoutBundle( hr = CacheCalculateBundleLayoutWorkingPath(pPlan->wzBundleId, &pCacheAction->bundleLayout.sczUnverifiedPath); ExitOnFailure(hr, "Failed to calculate bundle layout working path."); + pCacheAction->bundleLayout.qwBundleSize = qwBundleSize; + + pPlan->qwCacheSizeTotal += qwBundleSize; + ++pPlan->cOverallProgressTicksTotal; // Plan the layout of layout-only payloads. @@ -317,6 +339,7 @@ extern "C" HRESULT PlanLayoutBundle( LExit: ReleaseStr(sczLayoutDirectory); + ReleaseStr(sczExecutablePath); return hr; } @@ -637,7 +660,7 @@ extern "C" HRESULT PlanCleanPackage( // The following is a complex set of logic that determines when a package should be cleaned // from the cache. Start by noting that we only clean if the package is being acquired or // already cached. - if (pPackage->fAcquire || pPackage->fCached) + if (pPackage->fAcquire || BURN_CACHE_STATE_PARTIAL == pPackage->cache || BURN_CACHE_STATE_COMPLETE == pPackage->cache) { // The following are all different reasons why the package should be cleaned from the cache. // The else-ifs are used to make the conditions easier to see (rather than have them combined @@ -647,22 +670,22 @@ extern "C" HRESULT PlanCleanPackage( fPlanCleanPackage = TRUE; } else if (BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested && // requested to be removed and - BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) // actually being removed. + BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) // actually being removed. { fPlanCleanPackage = TRUE; } else if (BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested && // requested to be removed but - BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is do nothing and - !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and - BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. + BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is do nothing and + !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and + BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. { fPlanCleanPackage = TRUE; } - else if (BOOTSTRAPPER_ACTION_UNINSTALL == action && // uninstalling and - BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and - BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and - !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and - BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. + else if (BOOTSTRAPPER_ACTION_UNINSTALL == action && // uninstalling and + BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and + BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and + !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and + BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. { fPlanCleanPackage = TRUE; } @@ -1069,6 +1092,7 @@ static void ResetPlannedPackageState( { // Reset package state that is a result of planning. pPackage->expected = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; + pPackage->defaultRequested = BOOTSTRAPPER_REQUEST_STATE_NONE; pPackage->requested = BOOTSTRAPPER_REQUEST_STATE_NONE; pPackage->fAcquire = FALSE; pPackage->fUncache = FALSE; @@ -1224,9 +1248,9 @@ static HRESULT AddCachePackage( ++pPlan->cOverallProgressTicksTotal; - // If the package was not already cached then note that we planned the cache here. Otherwise, we only did cache - // operations to verify the cache is valid so we did not plan the acquisition of the package. - pPackage->fAcquire = !pPackage->fCached; + // If the package was not already fully cached then note that we planned the cache here. Otherwise, we only + // did cache operations to verify the cache is valid so we did not plan the acquisition of the package. + pPackage->fAcquire = (BURN_CACHE_STATE_COMPLETE != pPackage->cache); LExit: return hr; @@ -1633,6 +1657,8 @@ static HRESULT AddExtractPayload( ExitOnFailure(hr, "Failed to copy unverified path for payload to extract."); ++pCacheAction->extractContainer.cPayloads; + pCacheAction->extractContainer.qwTotalExtractSize += pPayload->qwFileSize; + LExit: return hr; } @@ -1810,7 +1836,7 @@ static void ExecuteActionLog( break; case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: - LogStringLine(REPORT_STANDARD, "%ls action[%u]: MSI_PACKAGE package id: %ls, action: %hs, product code: %ls, ui level: %u, log path: %ls, logging attrib: %u", wzBase, iAction, pAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pAction->msiPackage.action), pAction->msiPackage.sczProductCode, pAction->msiPackage.uiLevel, pAction->msiPackage.sczLogPath, pAction->msiPackage.dwLoggingAttributes); + LogStringLine(REPORT_STANDARD, "%ls action[%u]: MSI_PACKAGE package id: %ls, action: %hs, ui level: %u, log path: %ls, logging attrib: %u", wzBase, iAction, pAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pAction->msiPackage.action), pAction->msiPackage.uiLevel, pAction->msiPackage.sczLogPath, pAction->msiPackage.dwLoggingAttributes); for (DWORD j = 0; j < pAction->msiPackage.cPatches; ++j) { LogStringLine(REPORT_STANDARD, " Patch[%u]: order: %u, msp package id: %ls", j, pAction->msiPackage.rgOrderedPatches->dwOrder, pAction->msiPackage.rgOrderedPatches[j].dwOrder, pAction->msiPackage.rgOrderedPatches[j].pPackage->sczId); diff --git a/src/burn/engine/plan.h b/src/burn/engine/plan.h index 48225e98..1e7690e5 100644 --- a/src/burn/engine/plan.h +++ b/src/burn/engine/plan.h @@ -94,6 +94,7 @@ typedef struct _BURN_CACHE_ACTION LPWSTR sczExecutableName; LPWSTR sczLayoutDirectory; LPWSTR sczUnverifiedPath; + DWORD64 qwBundleSize; } bundleLayout; struct { @@ -122,6 +123,7 @@ typedef struct _BURN_CACHE_ACTION struct { BURN_CONTAINER* pContainer; + DWORD64 qwTotalExtractSize; DWORD iSkipUntilAcquiredByAction; LPWSTR sczContainerUnverifiedPath; @@ -194,7 +196,6 @@ typedef struct _BURN_EXECUTE_ACTION struct { BURN_PACKAGE* pPackage; - LPWSTR sczProductCode; LPWSTR sczLogPath; DWORD dwLoggingAttributes; INSTALLUILEVEL uiLevel; @@ -294,6 +295,10 @@ void PlanUninitialize( void PlanUninitializeExecuteAction( __in BURN_EXECUTE_ACTION* pExecuteAction ); +HRESULT PlanSetVariables( + __in BOOTSTRAPPER_ACTION action, + __in BURN_VARIABLES* pVariables + ); HRESULT PlanDefaultPackageRequestState( __in BURN_PACKAGE_TYPE packageType, __in BOOTSTRAPPER_PACKAGE_STATE currentState, @@ -307,6 +312,7 @@ HRESULT PlanDefaultPackageRequestState( HRESULT PlanLayoutBundle( __in BURN_PLAN* pPlan, __in_z LPCWSTR wzExecutableName, + __in DWORD64 qwBundleSize, __in BURN_VARIABLES* pVariables, __in BURN_PAYLOADS* pPayloads, __out_z LPWSTR* psczLayoutDirectory diff --git a/src/burn/engine/registration.cpp b/src/burn/engine/registration.cpp index 96ec01ba..2fe6f591 100644 --- a/src/burn/engine/registration.cpp +++ b/src/burn/engine/registration.cpp @@ -428,6 +428,12 @@ extern "C" HRESULT RegistrationSetVariables( HRESULT hr = S_OK; LPWSTR scz = NULL; + if (pRegistration->fInstalled) + { + hr = VariableSetNumeric(pVariables, BURN_BUNDLE_INSTALLED, 1, TRUE); + ExitOnFailure(hr, "Failed to set the bundle installed built-in variable."); + } + // Ensure the registration bundle name is updated. hr = GetBundleName(pRegistration, pVariables, &scz); ExitOnFailure(hr, "Failed to intitialize bundle name."); diff --git a/src/burn/engine/variable.cpp b/src/burn/engine/variable.cpp index cd72b1f4..72c1e66d 100644 --- a/src/burn/engine/variable.cpp +++ b/src/burn/engine/variable.cpp @@ -56,6 +56,13 @@ enum OS_INFO_VARIABLE // internal function declarations +static HRESULT FormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt DWORD* pcchOut, + __in BOOL fObfuscateHiddenVariables + ); static HRESULT AddBuiltInVariable( __in BURN_VARIABLES* pVariables, __in LPCWSTR wzVariable, @@ -152,6 +159,11 @@ static HRESULT InitializeVariableLogonUser( __in DWORD_PTR dwpData, __inout BURN_VARIANT* pValue ); +static HRESULT IsVariableHidden( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BOOL* pfHidden + ); // function definitions @@ -219,6 +231,8 @@ extern "C" HRESULT VariableInitialize( {L"VersionNT64", InitializeVariableOsInfo, OS_INFO_VARIABLE_VersionNT64}, {L"WindowsFolder", InitializeVariableCsidlFolder, CSIDL_WINDOWS}, {L"WindowsVolume", InitializeVariableWindowsVolumeFolder, 0}, + {BURN_BUNDLE_ACTION, InitializeVariableNumeric, 0}, + {BURN_BUNDLE_INSTALLED, InitializeVariableNumeric, 0}, {BURN_BUNDLE_ELEVATED, InitializeVariableNumeric, 0}, {BURN_BUNDLE_PROVIDER_KEY, InitializeVariableString, (DWORD_PTR)L""}, {BURN_BUNDLE_TAG, InitializeVariableString, (DWORD_PTR)L""}, @@ -620,182 +634,17 @@ extern "C" HRESULT VariableFormatString( __out_opt DWORD* pcchOut ) { - HRESULT hr = S_OK; - DWORD er = ERROR_SUCCESS; - LPWSTR sczUnformatted = NULL; - LPWSTR sczFormat = NULL; - LPCWSTR wzRead = NULL; - LPCWSTR wzOpen = NULL; - LPCWSTR wzClose = NULL; - LPWSTR scz = NULL; - LPWSTR* rgVariables = NULL; - DWORD cVariables = 0; - DWORD cch = 0; - MSIHANDLE hRecord = NULL; - - ::EnterCriticalSection(&pVariables->csAccess); - - // allocate buffer for format string - hr = StrAlloc(&sczFormat, lstrlenW(wzIn) + 1); - ExitOnFailure(hr, "Failed to allocate buffer for format string."); - - // read out variables from the unformatted string and build a format string - wzRead = wzIn; - for (;;) - { - // scan for opening '[' - wzOpen = wcschr(wzRead, L'['); - if (!wzOpen) - { - // end reached, append the remainder of the string and end loop - hr = StrAllocConcat(&sczFormat, wzRead, 0); - ExitOnFailure(hr, "Failed to append string."); - break; - } - - // scan for closing ']' - wzClose = wcschr(wzOpen + 1, L']'); - if (!wzClose) - { - // end reached, treat unterminated expander as literal - hr = StrAllocConcat(&sczFormat, wzRead, 0); - ExitOnFailure(hr, "Failed to append string."); - break; - } - cch = wzClose - wzOpen - 1; - - if (0 == cch) - { - // blank, copy all text including the terminator - hr = StrAllocConcat(&sczFormat, wzRead, (DWORD_PTR)(wzClose - wzRead) + 1); - ExitOnFailure(hr, "Failed to append string."); - } - else - { - // append text preceding expander - if (wzOpen > wzRead) - { - hr = StrAllocConcat(&sczFormat, wzRead, (DWORD_PTR)(wzOpen - wzRead)); - ExitOnFailure(hr, "Failed to append string."); - } - - // get variable name - hr = StrAllocString(&scz, wzOpen + 1, cch); - ExitOnFailure(hr, "Failed to get variable name."); - - // allocate space in variable array - if (rgVariables) - { - LPVOID pv = MemReAlloc(rgVariables, sizeof(LPWSTR) * (cVariables + 1), TRUE); - ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate variable array."); - rgVariables = (LPWSTR*)pv; - } - else - { - rgVariables = (LPWSTR*)MemAlloc(sizeof(LPWSTR) * (cVariables + 1), TRUE); - ExitOnNull(rgVariables, hr, E_OUTOFMEMORY, "Failed to allocate variable array."); - } - - // set variable value - if (2 <= cch && L'\\' == wzOpen[1]) - { - // escape sequence, copy character - hr = StrAllocString(&rgVariables[cVariables], &wzOpen[2], 1); - } - else - { - // get formatted variable value - hr = VariableGetFormatted(pVariables, scz, &rgVariables[cVariables]); - if (E_NOTFOUND == hr) // variable not found - { - hr = StrAllocString(&rgVariables[cVariables], L"", 0); - } - } - ExitOnFailure(hr, "Failed to set variable value."); - ++cVariables; - - // append placeholder to format string - hr = StrAllocFormatted(&scz, L"[%d]", cVariables); - ExitOnFailure(hr, "Failed to format placeholder string."); - - hr = StrAllocConcat(&sczFormat, scz, 0); - ExitOnFailure(hr, "Failed to append placeholder."); - } - - // update read pointer - wzRead = wzClose + 1; - } - - // create record - hRecord = ::MsiCreateRecord(cVariables); - ExitOnNull(hRecord, hr, E_OUTOFMEMORY, "Failed to allocate record."); - - // set format string - er = ::MsiRecordSetStringW(hRecord, 0, sczFormat); - ExitOnWin32Error(er, hr, "Failed to set record format string."); - - // copy record fields - for (DWORD i = 0; i < cVariables; ++i) - { - if (*rgVariables[i]) // not setting if blank - { - er = ::MsiRecordSetStringW(hRecord, i + 1, rgVariables[i]); - ExitOnWin32Error(er, hr, "Failed to set record string."); - } - } - - // get formatted character count - cch = 0; -#pragma prefast(push) -#pragma prefast(disable:6298) - er = ::MsiFormatRecordW(NULL, hRecord, L"", &cch); -#pragma prefast(pop) - if (ERROR_MORE_DATA != er) - { - ExitOnWin32Error(er, hr, "Failed to get formatted length."); - } - - // return formatted string - if (psczOut) - { - hr = StrAlloc(&scz, ++cch); - ExitOnFailure(hr, "Failed to allocate string."); - - er = ::MsiFormatRecordW(NULL, hRecord, scz, &cch); - ExitOnWin32Error(er, hr, "Failed to format record."); - - hr = StrAllocString(psczOut, scz, 0); - ExitOnFailure(hr, "Failed to copy string."); - } - - // return character count - if (pcchOut) - { - *pcchOut = cch; - } - -LExit: - ::LeaveCriticalSection(&pVariables->csAccess); - - if (rgVariables) - { - for (DWORD i = 0; i < cVariables; ++i) - { - ReleaseStr(rgVariables[i]); - } - MemFree(rgVariables); - } - - if (hRecord) - { - ::MsiCloseHandle(hRecord); - } - - ReleaseStr(sczUnformatted); - ReleaseStr(sczFormat); - ReleaseStr(scz); + return FormatString(pVariables, wzIn, psczOut, pcchOut, FALSE); +} - return hr; +extern "C" HRESULT VariableFormatStringObfuscated( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt DWORD* pcchOut + ) +{ + return FormatString(pVariables, wzIn, psczOut, pcchOut, TRUE); } extern "C" HRESULT VariableEscapeString( @@ -1007,6 +856,206 @@ extern "C" HRESULT VariableDeserialize( // internal function definitions +static HRESULT FormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt DWORD* pcchOut, + __in BOOL fObfuscateHiddenVariables + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + LPWSTR sczUnformatted = NULL; + LPWSTR sczFormat = NULL; + LPCWSTR wzRead = NULL; + LPCWSTR wzOpen = NULL; + LPCWSTR wzClose = NULL; + LPWSTR scz = NULL; + LPWSTR* rgVariables = NULL; + DWORD cVariables = 0; + DWORD cch = 0; + BOOL fHidden = FALSE; + MSIHANDLE hRecord = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + // allocate buffer for format string + hr = StrAlloc(&sczFormat, lstrlenW(wzIn) + 1); + ExitOnFailure(hr, "Failed to allocate buffer for format string."); + + // read out variables from the unformatted string and build a format string + wzRead = wzIn; + for (;;) + { + // scan for opening '[' + wzOpen = wcschr(wzRead, L'['); + if (!wzOpen) + { + // end reached, append the remainder of the string and end loop + hr = StrAllocConcat(&sczFormat, wzRead, 0); + ExitOnFailure(hr, "Failed to append string."); + break; + } + + // scan for closing ']' + wzClose = wcschr(wzOpen + 1, L']'); + if (!wzClose) + { + // end reached, treat unterminated expander as literal + hr = StrAllocConcat(&sczFormat, wzRead, 0); + ExitOnFailure(hr, "Failed to append string."); + break; + } + cch = wzClose - wzOpen - 1; + + if (0 == cch) + { + // blank, copy all text including the terminator + hr = StrAllocConcat(&sczFormat, wzRead, (DWORD_PTR)(wzClose - wzRead) + 1); + ExitOnFailure(hr, "Failed to append string."); + } + else + { + // append text preceding expander + if (wzOpen > wzRead) + { + hr = StrAllocConcat(&sczFormat, wzRead, (DWORD_PTR)(wzOpen - wzRead)); + ExitOnFailure(hr, "Failed to append string."); + } + + // get variable name + hr = StrAllocString(&scz, wzOpen + 1, cch); + ExitOnFailure(hr, "Failed to get variable name."); + + // allocate space in variable array + if (rgVariables) + { + LPVOID pv = MemReAlloc(rgVariables, sizeof(LPWSTR) * (cVariables + 1), TRUE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate variable array."); + rgVariables = (LPWSTR*)pv; + } + else + { + rgVariables = (LPWSTR*)MemAlloc(sizeof(LPWSTR) * (cVariables + 1), TRUE); + ExitOnNull(rgVariables, hr, E_OUTOFMEMORY, "Failed to allocate variable array."); + } + + // set variable value + if (2 <= cch && L'\\' == wzOpen[1]) + { + // escape sequence, copy character + hr = StrAllocString(&rgVariables[cVariables], &wzOpen[2], 1); + } + else + { + if (fObfuscateHiddenVariables) + { + hr = IsVariableHidden(pVariables, scz, &fHidden); + ExitOnFailure1(hr, "Failed to determine variable visibility: '%ls'.", scz); + } + + if (fHidden) + { + hr = StrAllocString(&rgVariables[cVariables], L"*****", 0); + } + else + { + // get formatted variable value + hr = VariableGetFormatted(pVariables, scz, &rgVariables[cVariables]); + if (E_NOTFOUND == hr) // variable not found + { + hr = StrAllocString(&rgVariables[cVariables], L"", 0); + } + } + } + ExitOnFailure(hr, "Failed to set variable value."); + ++cVariables; + + // append placeholder to format string + hr = StrAllocFormatted(&scz, L"[%d]", cVariables); + ExitOnFailure(hr, "Failed to format placeholder string."); + + hr = StrAllocConcat(&sczFormat, scz, 0); + ExitOnFailure(hr, "Failed to append placeholder."); + } + + // update read pointer + wzRead = wzClose + 1; + } + + // create record + hRecord = ::MsiCreateRecord(cVariables); + ExitOnNull(hRecord, hr, E_OUTOFMEMORY, "Failed to allocate record."); + + // set format string + er = ::MsiRecordSetStringW(hRecord, 0, sczFormat); + ExitOnWin32Error(er, hr, "Failed to set record format string."); + + // copy record fields + for (DWORD i = 0; i < cVariables; ++i) + { + if (*rgVariables[i]) // not setting if blank + { + er = ::MsiRecordSetStringW(hRecord, i + 1, rgVariables[i]); + ExitOnWin32Error(er, hr, "Failed to set record string."); + } + } + + // get formatted character count + cch = 0; +#pragma prefast(push) +#pragma prefast(disable:6298) + er = ::MsiFormatRecordW(NULL, hRecord, L"", &cch); +#pragma prefast(pop) + if (ERROR_MORE_DATA != er) + { + ExitOnWin32Error(er, hr, "Failed to get formatted length."); + } + + // return formatted string + if (psczOut) + { + hr = StrAlloc(&scz, ++cch); + ExitOnFailure(hr, "Failed to allocate string."); + + er = ::MsiFormatRecordW(NULL, hRecord, scz, &cch); + ExitOnWin32Error(er, hr, "Failed to format record."); + + hr = StrAllocString(psczOut, scz, 0); + ExitOnFailure(hr, "Failed to copy string."); + } + + // return character count + if (pcchOut) + { + *pcchOut = cch; + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + if (rgVariables) + { + for (DWORD i = 0; i < cVariables; ++i) + { + ReleaseStr(rgVariables[i]); + } + MemFree(rgVariables); + } + + if (hRecord) + { + ::MsiCloseHandle(hRecord); + } + + ReleaseStr(sczUnformatted); + ReleaseStr(sczFormat); + ReleaseStr(scz); + + return hr; +} + static HRESULT AddBuiltInVariable( __in BURN_VARIABLES* pVariables, __in LPCWSTR wzVariable, @@ -1204,7 +1253,6 @@ static HRESULT SetVariableValue( AssertSz(FALSE, "Intent to overwrite non-built-in variable."); } - // log value when not overwriting a built-in variable if (fLog && !fOverwriteBuiltIn) { @@ -1779,3 +1827,34 @@ static HRESULT InitializeVariableLogonUser( LExit: return hr; } + +static HRESULT IsVariableHidden( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BOOL* pfHidden + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (E_NOTFOUND == hr) + { + // A missing variable does not need its data hidden. + *pfHidden = FALSE; + + hr = S_OK; + ExitFunction(); + } + ExitOnFailure1(hr, "Failed to get visibility of variable: %ls", wzVariable); + + *pfHidden = pVariable->fHidden; + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + diff --git a/src/burn/engine/variable.h b/src/burn/engine/variable.h index c89a01d8..47be53a0 100644 --- a/src/burn/engine/variable.h +++ b/src/burn/engine/variable.h @@ -133,6 +133,12 @@ HRESULT VariableFormatString( __out_z_opt LPWSTR* psczOut, __out_opt DWORD* pcchOut ); +HRESULT VariableFormatStringObfuscated( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt DWORD* pcchOut + ); HRESULT VariableEscapeString( __in_z LPCWSTR wzIn, __out_z LPWSTR* psczOut diff --git a/src/chm/html/bundle_built_in_variables.htm b/src/chm/html/bundle_built_in_variables.htm index 18edc5af..e68ebe68 100644 --- a/src/chm/html/bundle_built_in_variables.htm +++ b/src/chm/html/bundle_built_in_variables.htm @@ -57,9 +57,13 @@

Burn Built-in Variables

  • VersionNT64 - version value representing the OS version if 64-bit. Undefined if running a 32-bit operating system.
  • WindowsFolder - gets the well-known folder for CSIDL_WINDOWS.
  • WindowsVolume - gets the well-known folder for the windows volume.
  • +
  • WixBundleAction - set to the numeric value of BOOTSTRAPPER_ACTION from the command-line and updated during the call to IBootstrapperEngine::Plan().
  • WixBundleDirectoryLayout - set to the folder provided to the -layout switch (default is directory containin the bundle executable). This variable can also be set by the bootstrapper application to modify where files will be laid out.
  • WixBundleElevated - gets whether the bundle was launched elevated and will be set to 1 once the bundle is elevated. Use this variable, for example, to show or hide the elevation shield in the bootstrapper application UI.
  • +
  • WixBundleInstalled - gets whether the bundle was already installed and will be set to 1 once the bundle is installed.
  • +
  • WixBundleLastUsedSource - gets the path of the last successful source resolution for a container or payload.
  • WixBundleName - gets the name of the bundle (from Bundle/@Name). This variable can also be set by the bootstrapper application to modify the bundle name at runtime.
  • +
  • WixBundleOriginalSource - gets the source path from where the bundle was originally installed.
  • WixBundleProviderKey - gets the bundle dependency provider key.
  • WixBundleTag - gets the developer-defined tag string for this bundle (from Bundle/@Tag).
  • diff --git a/src/dutil/dirutil.cpp b/src/dutil/dirutil.cpp index eb1ac43e..85a26257 100644 --- a/src/dutil/dirutil.cpp +++ b/src/dutil/dirutil.cpp @@ -207,7 +207,13 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( if (-1 == (dwAttrib = ::GetFileAttributesW(wzPath))) { - ExitWithLastError1(hr, "Failed to get attributes for path: %ls", wzPath); + er = ::GetLastError(); + if (ERROR_FILE_NOT_FOUND == er) // change "file not found" to "path not found" since we were looking for a directory. + { + er = ERROR_PATH_NOT_FOUND; + } + hr = HRESULT_FROM_WIN32(er); + ExitOnRootFailure1(hr, "Failed to get attributes for path: %ls", wzPath); } if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) diff --git a/src/dutil/inc/locutil.h b/src/dutil/inc/locutil.h index a942c28c..bdd2c54a 100644 --- a/src/dutil/inc/locutil.h +++ b/src/dutil/inc/locutil.h @@ -28,10 +28,25 @@ struct LOC_STRING BOOL bOverridable; }; -struct LOC_STRINGSET +const int LOC_CONTROL_NOT_SET = INT_MAX; + +struct LOC_CONTROL +{ + LPWSTR wzControl; + int nX; + int nY; + int nWidth; + int nHeight; + LPWSTR wzText; +}; + +struct WIX_LOCALIZATION { DWORD cLocStrings; LOC_STRING* rgLocStrings; + + DWORD cLocControls; + LOC_CONTROL* rgLocControls; }; /******************************************************************** @@ -51,7 +66,7 @@ HRESULT DAPI LocProbeForFile( *******************************************************************/ HRESULT DAPI LocLoadFromFile( __in_z LPCWSTR wzWxlFile, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ); /******************************************************************** @@ -63,7 +78,7 @@ HRESULT DAPI LocLoadFromFile( HRESULT DAPI LocLoadFromResource( __in HMODULE hModule, __in_z LPCSTR szResource, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ); /******************************************************************** @@ -71,7 +86,7 @@ HRESULT DAPI LocLoadFromResource( *******************************************************************/ void DAPI LocFree( - __in_opt LOC_STRINGSET* pLocStringSet + __in_opt WIX_LOCALIZATION* pWixLoc ); /******************************************************************** @@ -79,10 +94,19 @@ void DAPI LocFree( correct sub string *******************************************************************/ HRESULT DAPI LocLocalizeString( - __in const LOC_STRINGSET* pLocStringSet, + __in const WIX_LOCALIZATION* pWixLoc, __inout LPWSTR* psczInput ); +/******************************************************************** + LocGetControl - returns a control's localization information +*******************************************************************/ +HRESULT DAPI LocGetControl( + __in const WIX_LOCALIZATION* pWixLoc, + __in_z LPCWSTR wzId, + __out LOC_CONTROL** ppLocControl + ); + #ifdef __cplusplus } #endif diff --git a/src/dutil/inc/thmutil.h b/src/dutil/inc/thmutil.h index ac5802b9..5365c702 100644 --- a/src/dutil/inc/thmutil.h +++ b/src/dutil/inc/thmutil.h @@ -258,7 +258,7 @@ DAPI_(void) ThemeUnloadControls( *******************************************************************/ DAPI_(HRESULT) ThemeLocalize( __in THEME *pTheme, - __in const LOC_STRINGSET *pLocStringSet + __in const WIX_LOCALIZATION *pLocStringSet ); DAPI_(HRESULT) ThemeLoadStrings( diff --git a/src/dutil/locutil.cpp b/src/dutil/locutil.cpp index 9792a15b..2cadb636 100644 --- a/src/dutil/locutil.cpp +++ b/src/dutil/locutil.cpp @@ -21,16 +21,25 @@ // prototypes static HRESULT ParseWxl( __in IXMLDOMDocument* pixd, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ); static HRESULT ParseWxlStrings( __in IXMLDOMElement* pElement, - __in LOC_STRINGSET* pLocStringSet + __in WIX_LOCALIZATION* pWixLoc + ); +static HRESULT ParseWxlControls( + __in IXMLDOMElement* pElement, + __in WIX_LOCALIZATION* pWixLoc ); static HRESULT ParseWxlString( __in IXMLDOMNode* pixn, __in DWORD dwIdx, - __in LOC_STRINGSET* pLocStringSet + __in WIX_LOCALIZATION* pWixLoc + ); +static HRESULT ParseWxlControl( + __in IXMLDOMNode* pixn, + __in DWORD dwIdx, + __in WIX_LOCALIZATION* pWixLoc ); extern "C" HRESULT DAPI LocProbeForFile( @@ -109,7 +118,7 @@ extern "C" HRESULT DAPI LocProbeForFile( extern "C" HRESULT DAPI LocLoadFromFile( __in_z LPCWSTR wzWxlFile, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ) { HRESULT hr = S_OK; @@ -118,7 +127,7 @@ extern "C" HRESULT DAPI LocLoadFromFile( hr = XmlLoadDocumentFromFile(wzWxlFile, &pixd); ExitOnFailure(hr, "Failed to load WXL file as XML document."); - hr = ParseWxl(pixd, ppLocStringSet); + hr = ParseWxl(pixd, ppWixLoc); ExitOnFailure(hr, "Failed to parse WXL."); LExit: @@ -130,7 +139,7 @@ extern "C" HRESULT DAPI LocLoadFromFile( extern "C" HRESULT DAPI LocLoadFromResource( __in HMODULE hModule, __in_z LPCSTR szResource, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ) { HRESULT hr = S_OK; @@ -148,7 +157,7 @@ extern "C" HRESULT DAPI LocLoadFromResource( hr = XmlLoadDocument(sczXml, &pixd); ExitOnFailure(hr, "Failed to load theme resource as XML document."); - hr = ParseWxl(pixd, ppLocStringSet); + hr = ParseWxl(pixd, ppWixLoc); ExitOnFailure(hr, "Failed to parse WXL."); LExit: @@ -159,33 +168,40 @@ extern "C" HRESULT DAPI LocLoadFromResource( } extern "C" void DAPI LocFree( - __in_opt LOC_STRINGSET* pLocStringSet + __in_opt WIX_LOCALIZATION* pWixLoc ) { - if (pLocStringSet) + if (pWixLoc) { - for (DWORD idx = 0; idx < pLocStringSet->cLocStrings; ++idx) + for (DWORD idx = 0; idx < pWixLoc->cLocStrings; ++idx) { - ReleaseStr(pLocStringSet->rgLocStrings[idx].wzId); - ReleaseStr(pLocStringSet->rgLocStrings[idx].wzText); + ReleaseStr(pWixLoc->rgLocStrings[idx].wzId); + ReleaseStr(pWixLoc->rgLocStrings[idx].wzText); } - ReleaseMem(pLocStringSet->rgLocStrings); - ReleaseMem(pLocStringSet); + for (DWORD idx = 0; idx < pWixLoc->cLocControls; ++idx) + { + ReleaseStr(pWixLoc->rgLocControls[idx].wzControl); + ReleaseStr(pWixLoc->rgLocControls[idx].wzText); + } + + ReleaseMem(pWixLoc->rgLocStrings); + ReleaseMem(pWixLoc->rgLocControls); + ReleaseMem(pWixLoc); } } extern "C" HRESULT DAPI LocLocalizeString( - __in const LOC_STRINGSET* pLocStringSet, + __in const WIX_LOCALIZATION* pWixLoc, __inout LPWSTR* ppsczInput ) { - Assert(ppsczInput && pLocStringSet); + Assert(ppsczInput && pWixLoc); HRESULT hr = S_OK; - for (DWORD i = 0; i < pLocStringSet->cLocStrings; ++i) + for (DWORD i = 0; i < pWixLoc->cLocStrings; ++i) { - hr = StrReplaceStringAll(ppsczInput, pLocStringSet->rgLocStrings[i].wzId, pLocStringSet->rgLocStrings[i].wzText); + hr = StrReplaceStringAll(ppsczInput, pWixLoc->rgLocStrings[i].wzId, pWixLoc->rgLocStrings[i].wzText); ExitOnFailure(hr, "Localizing string failed."); } @@ -193,34 +209,63 @@ extern "C" HRESULT DAPI LocLocalizeString( return hr; } +extern "C" HRESULT DAPI LocGetControl( + __in const WIX_LOCALIZATION* pWixLoc, + __in_z LPCWSTR wzId, + __out LOC_CONTROL** ppLocControl + ) +{ + HRESULT hr = S_OK; + LOC_CONTROL* pLocControl = NULL; + + for (DWORD i = 0; i < pWixLoc->cLocControls; ++i) + { + pLocControl = &pWixLoc->rgLocControls[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pLocControl->wzControl, -1, wzId, -1)) + { + *ppLocControl = pLocControl; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + // helper functions static HRESULT ParseWxl( __in IXMLDOMDocument* pixd, - __out LOC_STRINGSET** ppLocStringSet + __out WIX_LOCALIZATION** ppWixLoc ) { HRESULT hr = S_OK; IXMLDOMElement *pWxlElement = NULL; - LOC_STRINGSET* pLocStringSet = NULL; + WIX_LOCALIZATION* pWixLoc = NULL; - pLocStringSet = static_cast(MemAlloc(sizeof(LOC_STRINGSET), TRUE)); - ExitOnNull(pLocStringSet, hr, E_OUTOFMEMORY, "Failed to allocate memory for Wxl file."); + pWixLoc = static_cast(MemAlloc(sizeof(WIX_LOCALIZATION), TRUE)); + ExitOnNull(pWixLoc, hr, E_OUTOFMEMORY, "Failed to allocate memory for Wxl file."); // read the WixLocalization tag hr = pixd->get_documentElement(&pWxlElement); ExitOnFailure(hr, "Failed to get localization element."); - // store the strings in a node list - hr = ParseWxlStrings(pWxlElement, pLocStringSet); + // store the strings and controls in a node list + hr = ParseWxlStrings(pWxlElement, pWixLoc); ExitOnFailure(hr, "Parsing localization strings failed."); - *ppLocStringSet = pLocStringSet; - pLocStringSet = NULL; + hr = ParseWxlControls(pWxlElement, pWixLoc); + ExitOnFailure(hr, "Parsing localization controls failed."); + + *ppWixLoc = pWixLoc; + pWixLoc = NULL; LExit: ReleaseObject(pWxlElement); - ReleaseMem(pLocStringSet); + ReleaseMem(pWixLoc); return hr; } @@ -228,7 +273,7 @@ static HRESULT ParseWxl( static HRESULT ParseWxlStrings( __in IXMLDOMElement* pElement, - __in LOC_STRINGSET* pLocStringSet + __in WIX_LOCALIZATION* pWixLoc ) { HRESULT hr = S_OK; @@ -236,37 +281,92 @@ static HRESULT ParseWxlStrings( IXMLDOMNodeList* pixnl = NULL; DWORD dwIdx = 0; - hr = XmlSelectNodes(pElement, L"*", &pixnl); - ExitOnLastError(hr, "Failed to get child nodes of Wxl File."); + hr = XmlSelectNodes(pElement, L"String", &pixnl); + ExitOnLastError(hr, "Failed to get String child nodes of Wxl File."); - hr = pixnl->get_length(reinterpret_cast(&pLocStringSet->cLocStrings)); - ExitOnLastError(hr, "Failed to get number of child nodes in Wxl File."); + hr = pixnl->get_length(reinterpret_cast(&pWixLoc->cLocStrings)); + ExitOnLastError(hr, "Failed to get number of String child nodes in Wxl File."); - pLocStringSet->rgLocStrings = static_cast(MemAlloc(sizeof(LOC_STRING) * pLocStringSet->cLocStrings, TRUE)); - ExitOnNull(pLocStringSet->rgLocStrings, hr, E_OUTOFMEMORY, "Failed to allocate memory for localization strings."); + if (0 < pWixLoc->cLocStrings) + { + pWixLoc->rgLocStrings = static_cast(MemAlloc(sizeof(LOC_STRING) * pWixLoc->cLocStrings, TRUE)); + ExitOnNull(pWixLoc->rgLocStrings, hr, E_OUTOFMEMORY, "Failed to allocate memory for localization strings."); + + while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL))) + { + hr = ParseWxlString(pixn, dwIdx, pWixLoc); + ExitOnFailure(hr, "Failed to parse localization string."); - while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL))) + ++dwIdx; + ReleaseNullObject(pixn); + } + + hr = S_OK; + ExitOnFailure(hr, "Failed to enumerate all localization strings."); + } + +LExit: + if (FAILED(hr) && pWixLoc->rgLocStrings) { - hr = ParseWxlString(pixn, dwIdx, pLocStringSet); - ExitOnFailure(hr, "Failed to parse localization string."); + for (DWORD idx = 0; idx < pWixLoc->cLocStrings; ++idx) + { + ReleaseStr(pWixLoc->rgLocStrings[idx].wzId); + ReleaseStr(pWixLoc->rgLocStrings[idx].wzText); + } - ++dwIdx; - ReleaseNullObject(pixn); + ReleaseMem(pWixLoc->rgLocStrings); } - hr = S_OK; - ExitOnFailure(hr, "Failed to enumerate all localization strings."); + ReleaseObject(pixn); + ReleaseObject(pixnl); + + return hr; +} + +static HRESULT ParseWxlControls( + __in IXMLDOMElement* pElement, + __in WIX_LOCALIZATION* pWixLoc + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixn = NULL; + IXMLDOMNodeList* pixnl = NULL; + DWORD dwIdx = 0; + + hr = XmlSelectNodes(pElement, L"Control", &pixnl); + ExitOnLastError(hr, "Failed to get Control child nodes of Wxl File."); + + hr = pixnl->get_length(reinterpret_cast(&pWixLoc->cLocControls)); + ExitOnLastError(hr, "Failed to get number of Control child nodes in Wxl File."); + + if (0 < pWixLoc->cLocControls) + { + pWixLoc->rgLocControls = static_cast(MemAlloc(sizeof(LOC_CONTROL) * pWixLoc->cLocControls, TRUE)); + ExitOnNull(pWixLoc->rgLocControls, hr, E_OUTOFMEMORY, "Failed to allocate memory for localized controls."); + + while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL))) + { + hr = ParseWxlControl(pixn, dwIdx, pWixLoc); + ExitOnFailure(hr, "Failed to parse localized control."); + + ++dwIdx; + ReleaseNullObject(pixn); + } + + hr = S_OK; + ExitOnFailure(hr, "Failed to enumerate all localized controls."); + } LExit: - if (FAILED(hr) && pLocStringSet->rgLocStrings) + if (FAILED(hr) && pWixLoc->rgLocControls) { - for (DWORD idx = 0; idx < pLocStringSet->cLocStrings; ++idx) + for (DWORD idx = 0; idx < pWixLoc->cLocControls; ++idx) { - ReleaseStr(pLocStringSet->rgLocStrings[idx].wzId); - ReleaseStr(pLocStringSet->rgLocStrings[idx].wzText); + ReleaseStr(pWixLoc->rgLocControls[idx].wzControl); + ReleaseStr(pWixLoc->rgLocControls[idx].wzText); } - ReleaseMem(pLocStringSet->rgLocStrings); + ReleaseMem(pWixLoc->rgLocControls); } ReleaseObject(pixn); @@ -278,14 +378,14 @@ static HRESULT ParseWxlStrings( static HRESULT ParseWxlString( __in IXMLDOMNode* pixn, __in DWORD dwIdx, - __in LOC_STRINGSET* pLocStringSet + __in WIX_LOCALIZATION* pWixLoc ) { HRESULT hr = S_OK; LOC_STRING* pLocString = NULL; BSTR bstrText = NULL; - pLocString = pLocStringSet->rgLocStrings + dwIdx; + pLocString = pWixLoc->rgLocStrings + dwIdx; // Id hr = XmlGetAttribute(pixn, L"Id", &bstrText); @@ -319,3 +419,57 @@ static HRESULT ParseWxlString( return hr; } + +static HRESULT ParseWxlControl( + __in IXMLDOMNode* pixn, + __in DWORD dwIdx, + __in WIX_LOCALIZATION* pWixLoc + ) +{ + HRESULT hr = S_OK; + LOC_CONTROL* pLocControl = NULL; + BSTR bstrText = NULL; + + pLocControl = pWixLoc->rgLocControls + dwIdx; + + // Id + hr = XmlGetAttribute(pixn, L"Control", &bstrText); + ExitOnFailure(hr, "Failed to get Xml attribute Control in Wxl file."); + + hr = StrAllocString(&pLocControl->wzControl, bstrText, 0); + ExitOnFailure(hr, "Failed to duplicate Xml attribute Control in Wxl file."); + + ReleaseNullBSTR(bstrText); + + // X + pLocControl->nX = LOC_CONTROL_NOT_SET; + hr = XmlGetAttributeNumber(pixn, L"X", reinterpret_cast(&pLocControl->nX)); + ExitOnFailure(hr, "Failed to get control X attribute."); + + // Y + pLocControl->nY = LOC_CONTROL_NOT_SET; + hr = XmlGetAttributeNumber(pixn, L"Y", reinterpret_cast(&pLocControl->nY)); + ExitOnFailure(hr, "Failed to get control Y attribute."); + + // Width + pLocControl->nWidth = LOC_CONTROL_NOT_SET; + hr = XmlGetAttributeNumber(pixn, L"Width", reinterpret_cast(&pLocControl->nWidth)); + ExitOnFailure(hr, "Failed to get control width attribute."); + + // Height + pLocControl->nHeight = LOC_CONTROL_NOT_SET; + hr = XmlGetAttributeNumber(pixn, L"Height", reinterpret_cast(&pLocControl->nHeight)); + ExitOnFailure(hr, "Failed to get control height attribute."); + + // Text + hr = XmlGetText(pixn, &bstrText); + ExitOnFailure(hr, "Failed to get control text in Wxl file."); + + hr = StrAllocString(&pLocControl->wzText, bstrText, 0); + ExitOnFailure(hr, "Failed to duplicate control text in Wxl file."); + +LExit: + ReleaseBSTR(bstrText); + + return hr; +} diff --git a/src/dutil/thmutil.cpp b/src/dutil/thmutil.cpp index c5ad0de5..6f57b7a2 100644 --- a/src/dutil/thmutil.cpp +++ b/src/dutil/thmutil.cpp @@ -55,7 +55,7 @@ static HRESULT ParseTheme( ); static HRESULT LocalizeTheme( __in THEME *pTheme, - __in const LOC_STRINGSET *pLocStringSet + __in const WIX_LOCALIZATION *pWixLoc ); static HRESULT ParseImage( __in_opt HMODULE hModule, @@ -631,32 +631,68 @@ DAPI_(void) ThemeUnloadControls( DAPI_(HRESULT) ThemeLocalize( __in THEME *pTheme, - __in const LOC_STRINGSET *pLocStringSet + __in const WIX_LOCALIZATION *pWixLoc ) { HRESULT hr = S_OK; + LOC_CONTROL* pLocControl = NULL; - hr = LocLocalizeString(pLocStringSet, &pTheme->sczCaption); + hr = LocLocalizeString(pWixLoc, &pTheme->sczCaption); ExitOnFailure(hr, "Failed to localize theme caption."); for (DWORD i = 0; i < pTheme->cControls; ++i) { THEME_CONTROL* pControl = pTheme->rgControls + i; - hr = LocLocalizeString(pLocStringSet, &pControl->sczText); + hr = LocLocalizeString(pWixLoc, &pControl->sczText); ExitOnFailure(hr, "Failed to localize control text."); for (DWORD j = 0; j < pControl->cColumns; ++j) { - hr = LocLocalizeString(pLocStringSet, &pControl->ptcColumns[j].pszName); + hr = LocLocalizeString(pWixLoc, &pControl->ptcColumns[j].pszName); ExitOnFailure(hr, "Failed to localize column text."); } for (DWORD j = 0; j < pControl->cTabs; ++j) { - hr = LocLocalizeString(pLocStringSet, &pControl->pttTabs[j].pszName); + hr = LocLocalizeString(pWixLoc, &pControl->pttTabs[j].pszName); ExitOnFailure(hr, "Failed to localize tab text."); } + + // localize controls size, location, and text + hr = LocGetControl(pWixLoc, pControl->sczName, &pLocControl); + if (E_NOTFOUND == hr) + { + hr = S_OK; + continue; + } + ExitOnFailure(hr, "Failed to localize control."); + + if (LOC_CONTROL_NOT_SET != pLocControl->nX) + { + pControl->nX = pLocControl->nX; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nY) + { + pControl->nY = pLocControl->nY; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nWidth) + { + pControl->nWidth = pLocControl->nWidth; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nHeight) + { + pControl->nHeight = pLocControl->nHeight; + } + + if (pLocControl->wzText && 0 < wcslen(pLocControl->wzText)) + { + hr = StrAllocString(&pControl->sczText, pLocControl->wzText, 0); + ExitOnFailure(hr, "Failed to localize control text."); + } } LExit: @@ -739,8 +775,14 @@ DAPI_(HRESULT) ThemeLoadRichEditFromFile( } else { - EDITSTREAM es = { }; + LONGLONG llRtfSize; + hr = FileSizeByHandle(hFile, &llRtfSize); + if (SUCCEEDED(hr)) + { + ::SendMessageW(hWnd, EM_EXLIMITTEXT, 0, static_cast(llRtfSize)); + } + EDITSTREAM es = { }; es.pfnCallback = RichEditStreamFromFileHandleCallback; es.dwCookie = reinterpret_cast(hFile); @@ -1795,7 +1837,7 @@ static HRESULT ParseFonts( pTheme->rgFonts = static_cast(MemAlloc(sizeof(THEME_FONT) * pTheme->cFonts, TRUE)); ExitOnNull(pTheme->rgFonts, hr, E_OUTOFMEMORY, "Failed to allocate theme fonts."); - lf.lfQuality = ANTIALIASED_QUALITY; + lf.lfQuality = CLEARTYPE_QUALITY; while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL))) { diff --git a/src/ext/BalExtension/balutil/balcondition.cpp b/src/ext/BalExtension/balutil/balcondition.cpp index 26e99d15..0d50cc52 100644 --- a/src/ext/BalExtension/balutil/balcondition.cpp +++ b/src/ext/BalExtension/balutil/balcondition.cpp @@ -24,7 +24,7 @@ DAPI_(HRESULT) BalConditionsParseFromXml( __in BAL_CONDITIONS* pConditions, __in IXMLDOMDocument* pixdManifest, - __in_opt LOC_STRINGSET* pLocStringSet + __in_opt WIX_LOCALIZATION* pWixLoc ) { HRESULT hr = S_OK; @@ -56,9 +56,9 @@ DAPI_(HRESULT) BalConditionsParseFromXml( hr = XmlGetAttributeEx(pNode, L"Message", &prgConditions[iCondition].sczMessage); ExitOnFailure(hr, "Failed to get message for condition."); - if (pLocStringSet && prgConditions[iCondition].sczMessage && *prgConditions[iCondition].sczMessage) + if (pWixLoc && prgConditions[iCondition].sczMessage && *prgConditions[iCondition].sczMessage) { - hr = LocLocalizeString(pLocStringSet, &prgConditions[iCondition].sczMessage); + hr = LocLocalizeString(pWixLoc, &prgConditions[iCondition].sczMessage); ExitOnFailure(hr, "Failed to localize condition message."); } diff --git a/src/ext/BalExtension/balutil/inc/BalBaseBootstrapperApplication.h b/src/ext/BalExtension/balutil/inc/BalBaseBootstrapperApplication.h index 89167926..f697a0d5 100644 --- a/src/ext/BalExtension/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/ext/BalExtension/balutil/inc/BalBaseBootstrapperApplication.h @@ -421,14 +421,6 @@ class CBalBaseBootstrapperApplication : public IBootstrapperApplication return CheckCanceled() ? IDCANCEL : IDNOACTION; } - virtual STDMETHODIMP_(int) OnDownloadProgress( - __in DWORD /*dwProgressPercentage*/, - __in DWORD /*dwOverallProgressPercentage*/ - ) - { - return CheckCanceled() ? IDCANCEL : IDNOACTION; - } - virtual STDMETHODIMP_(int) OnExecuteProgress( __in_z LPCWSTR /*wzPackageId*/, __in DWORD /*dwProgressPercentage*/, diff --git a/src/ext/BalExtension/balutil/inc/balcondition.h b/src/ext/BalExtension/balutil/inc/balcondition.h index cf3b1ba4..bb5b2d9b 100644 --- a/src/ext/BalExtension/balutil/inc/balcondition.h +++ b/src/ext/BalExtension/balutil/inc/balcondition.h @@ -43,7 +43,7 @@ typedef struct _BAL_CONDITIONS DAPI_(HRESULT) BalConditionsParseFromXml( __in BAL_CONDITIONS* pConditions, __in IXMLDOMDocument* pixdManifest, - __in_opt LOC_STRINGSET* pLocStringSet + __in_opt WIX_LOCALIZATION* pWixLoc ); diff --git a/src/ext/BalExtension/wixext/BalCompiler.cs b/src/ext/BalExtension/wixext/BalCompiler.cs index 21513b60..afe3a8c1 100644 --- a/src/ext/BalExtension/wixext/BalCompiler.cs +++ b/src/ext/BalExtension/wixext/BalCompiler.cs @@ -77,6 +77,20 @@ public override void ParseElement(SourceLineNumberCollection sourceLineNumbers, break; } break; + case "BootstrapperApplicationRef": + switch (element.LocalName) + { + case "WixStandardBootstrapperApplication": + this.ParseWixStandardBootstrapperApplicationElement(element); + break; + case "WixManagedBootstrapperApplicationHost": + this.ParseWixManagedBootstrapperApplicationHostElement(element); + break; + default: + this.Core.UnexpectedElement(parentElement, element); + break; + } + break; default: this.Core.UnexpectedElement(parentElement, element); break; @@ -151,5 +165,224 @@ private void ParseConditionElement(XmlNode node) } } } + + /// + /// Parses a WixStandardBootstrapperApplication element for Bundles. + /// + /// The element to parse. + private void ParseWixStandardBootstrapperApplicationElement(XmlNode node) + { + SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string licenseFile = null; + string licenseUrl = null; + string logoFile = null; + string themeFile = null; + string localizationFile = null; + YesNoType suppressOptionsUI = YesNoType.NotSet; + YesNoType suppressDowngradeFailure = YesNoType.NotSet; + + foreach (XmlAttribute attrib in node.Attributes) + { + if (0 == attrib.NamespaceURI.Length || attrib.NamespaceURI == this.schema.TargetNamespace) + { + switch (attrib.LocalName) + { + case "LicenseFile": + licenseFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LicenseUrl": + licenseUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LogoFile": + logoFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "ThemeFile": + themeFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LocalizationFile": + localizationFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "SuppressOptionsUI": + suppressOptionsUI = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SuppressDowngradeFailure": + suppressDowngradeFailure = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(sourceLineNumbers, attrib); + break; + } + } + else + { + this.Core.UnsupportedExtensionAttribute(sourceLineNumbers, attrib); + } + } + + foreach (XmlNode child in node.ChildNodes) + { + if (XmlNodeType.Element == child.NodeType) + { + if (child.NamespaceURI == this.schema.TargetNamespace) + { + this.Core.UnexpectedElement(node, child); + } + else + { + this.Core.UnsupportedExtensionElement(node, child); + } + } + } + + if (String.IsNullOrEmpty(licenseFile) == String.IsNullOrEmpty(licenseUrl)) + { + this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name, "LicenseFile", "LicenseUrl", true)); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(licenseFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixStdbaLicenseRtf", licenseFile, false); + } + + if (!String.IsNullOrEmpty(licenseUrl)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixStdbaLicenseUrl", licenseUrl, false); + } + + if (!String.IsNullOrEmpty(logoFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixStdbaLogo", logoFile, false); + } + + if (!String.IsNullOrEmpty(themeFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixStdbaThemeXml", themeFile, false); + } + + if (!String.IsNullOrEmpty(localizationFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixStdbaThemeWxl", localizationFile, false); + } + + if (YesNoType.Yes == suppressOptionsUI || YesNoType.Yes == suppressDowngradeFailure) + { + Row row = this.Core.CreateRow(sourceLineNumbers, "WixStdbaOptions"); + if (YesNoType.Yes == suppressOptionsUI) + { + row[0] = 1; + } + + if (YesNoType.Yes == suppressDowngradeFailure) + { + row[1] = 1; + } + } + } + } + + /// + /// Parses a WixManagedBootstrapperApplicationHost element for Bundles. + /// + /// The element to parse. + private void ParseWixManagedBootstrapperApplicationHostElement(XmlNode node) + { + SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string licenseFile = null; + string licenseUrl = null; + string logoFile = null; + string themeFile = null; + string localizationFile = null; + string netFxPackageId = null; + + foreach (XmlAttribute attrib in node.Attributes) + { + if (0 == attrib.NamespaceURI.Length || attrib.NamespaceURI == this.schema.TargetNamespace) + { + switch (attrib.LocalName) + { + case "LicenseFile": + licenseFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LicenseUrl": + licenseUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LogoFile": + logoFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "ThemeFile": + themeFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "LocalizationFile": + localizationFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + case "NetFxPackageId": + netFxPackageId = this.Core.GetAttributeValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(sourceLineNumbers, attrib); + break; + } + } + else + { + this.Core.UnsupportedExtensionAttribute(sourceLineNumbers, attrib); + } + } + + foreach (XmlNode child in node.ChildNodes) + { + if (XmlNodeType.Element == child.NodeType) + { + if (child.NamespaceURI == this.schema.TargetNamespace) + { + this.Core.UnexpectedElement(node, child); + } + else + { + this.Core.UnsupportedExtensionElement(node, child); + } + } + } + + if (String.IsNullOrEmpty(licenseFile) == String.IsNullOrEmpty(licenseUrl)) + { + this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name, "LicenseFile", "LicenseUrl", true)); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(licenseFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixMbaPrereqLicenseRtf", licenseFile, false); + } + + if (!String.IsNullOrEmpty(licenseUrl)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixMbaPrereqLicenseUrl", licenseUrl, false); + } + + if (!String.IsNullOrEmpty(logoFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "PreqbaLogo", logoFile, false); + } + + if (!String.IsNullOrEmpty(themeFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "PreqbaThemeXml", themeFile, false); + } + + if (!String.IsNullOrEmpty(localizationFile)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "PreqbaThemeWxl", localizationFile, false); + } + + if (!String.IsNullOrEmpty(netFxPackageId)) + { + this.Core.CreateWixVariableRow(sourceLineNumbers, "WixMbaPrereqPackageId", localizationFile, false); + } + } + } } } diff --git a/src/ext/BalExtension/wixext/Xsd/bal.xsd b/src/ext/BalExtension/wixext/Xsd/bal.xsd index 5893e4c5..378fe2e7 100644 --- a/src/ext/BalExtension/wixext/Xsd/bal.xsd +++ b/src/ext/BalExtension/wixext/Xsd/bal.xsd @@ -51,4 +51,110 @@ + + + + + Configures WixStandardBootstrapperApplication for a Bundle. + + + + + + + + + Source file of the RTF license file. Cannot be used simultaneously with LicenseUrl. + + + + + URL target of the license link. Cannot be used simultaneously with LicenseFile. + + + + + Source file of the logo graphic. + + + + + Source file of the theme XML. + + + + + Source file of the theme localization .wxl file. + + + + + If set to "yes", the Options button will not be shown and the user will not be able to choose an installation directory. + + + + + If set to "yes", attempting to installer a downgraded version of a bundle will be treated as a successful do-nothing operation. + The default behavior (or when explicitly set to "no") is to treat downgrade attempts as failures. + + + + + + + + + Configures the ManagedBootstrapperApplicationHost for a Bundle. + + + + + + + + + Source file of the RTF license file. Cannot be used simultaneously with LicenseUrl. + + + + + URL target of the license link. Cannot be used simultaneously with LicenseFile. + + + + + Source file of the logo graphic. + + + + + Source file of the theme XML. + + + + + Source file of the theme localization .wxl file. + + + + + + Identifier of the bundle package that contains the .NET Framework. ManagedBootstrapperApplicationHost uses + this identifier to determine whether .NET needs to be installed before the managed bootstrapper application + can be launched. + + + + + + + + + Values of this type will either be "yes" or "no". + + + + + + diff --git a/src/ext/BalExtension/wixext/data/tables.xml b/src/ext/BalExtension/wixext/data/tables.xml index c5e82ab9..4e51ce70 100644 --- a/src/ext/BalExtension/wixext/data/tables.xml +++ b/src/ext/BalExtension/wixext/data/tables.xml @@ -19,4 +19,11 @@ + + + + + diff --git a/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.wxl b/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.wxl index da19b531..9bbeb578 100644 --- a/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.wxl +++ b/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.wxl @@ -10,11 +10,11 @@ You must not remove this notice, or any other, from this software. --> - + [WixBundleName] Setup [WixBundleName] Are you sure you want to cancel? - [WixBundleName] license terms. + [WixBundleName] <a href="#">license terms</a>. I &agree to the license terms and conditions &Options &Install @@ -22,10 +22,11 @@ Setup Options Install location: &Browse - &OK + &OK &Cancel Setup Progress Processing: + [ProgressPackageName] &Cancel Modify Setup &Repair @@ -36,11 +37,9 @@ You must restart your computer before you can use the software. &Restart &Close - Setup Failed - One or more issues caused the setup to fail. Please fix the issues and then retry setup. - For more information see the - log file. + Setup 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 + &Restart + &Close \ No newline at end of file diff --git a/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.xml b/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.xml index 71eaf7c7..b13f5e3f 100644 --- a/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.xml +++ b/src/ext/BalExtension/wixstdba/Resources/HyperlinkTheme.xml @@ -11,7 +11,7 @@ #(loc.Title) - #(loc.InstallLicenseLinkText) + #(loc.InstallLicenseLinkText) #(loc.InstallAcceptCheckbox) @@ -22,13 +22,13 @@ #(loc.OptionsLocationLabel) - + #(loc.ProgressHeader) #(loc.ProgressLabel) - [ProgressPackageName] + #(loc.OverallProgressPackageText) @@ -46,13 +46,11 @@ - #(loc.FailedHeader) - #(loc.FailedText) - #(loc.FailedMoreInfo) - #(loc.FailedHyperlinkLogText) - + #(loc.FailureHeader) + #(loc.FailureHyperlinkLogText) + #(loc.FailureRestartText) - - + + \ No newline at end of file diff --git a/src/ext/BalExtension/wixstdba/Resources/RtfTheme.wxl b/src/ext/BalExtension/wixstdba/Resources/RtfTheme.wxl index 25e2a4cb..13de70ca 100644 --- a/src/ext/BalExtension/wixstdba/Resources/RtfTheme.wxl +++ b/src/ext/BalExtension/wixstdba/Resources/RtfTheme.wxl @@ -14,13 +14,13 @@ [WixBundleName] Setup [WixBundleName] Are you sure you want to cancel? - I &agree to the license terms and conditions - &Options - &Install - &Close + I &agree to the license terms and conditions + &Options + &Install + &Close Setup Options - Install location: - &Browse + Install location: + &Browse &OK &Cancel Setup Progress @@ -28,19 +28,17 @@ [ProgressPackageName] &Cancel Modify Setup - &Repair - &Uninstall - &Close + &Repair + &Uninstall + &Close Setup Successful - &Launch + &Launch You must restart your computer before you can use the software. &Restart - &Close + &Close Setup Failed - One or more issues caused the setup to fail. Please fix the issues and then retry setup. - For more information see the - log file. + 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 + &Close diff --git a/src/ext/BalExtension/wixstdba/Resources/RtfTheme.xml b/src/ext/BalExtension/wixstdba/Resources/RtfTheme.xml index ec012d93..b2384c4f 100644 --- a/src/ext/BalExtension/wixstdba/Resources/RtfTheme.xml +++ b/src/ext/BalExtension/wixstdba/Resources/RtfTheme.xml @@ -12,16 +12,16 @@ - #(loc.EulaAcceptCheckbox) - - - + #(loc.InstallAcceptCheckbox) + + + #(loc.OptionsHeader) - #(loc.InstallLocationLabel) + #(loc.OptionsLocationLabel) - + @@ -34,25 +34,23 @@ #(loc.ModifyHeader) - - - + + + #(loc.SuccessHeader) + #(loc.SuccessRestartText) - - + #(loc.FailureHeader) - #(loc.FailureLabel) - #(loc.FailureLogFileText) - #(loc.FailureLogFileLabel) - + #(loc.FailureHyperlinkLogText) + #(loc.FailureRestartText) - + \ No newline at end of file diff --git a/src/ext/BalExtension/wixstdba/Resources/mbapreq.thm b/src/ext/BalExtension/wixstdba/Resources/mbapreq.thm index 9dbc6894..4d006ddb 100644 --- a/src/ext/BalExtension/wixstdba/Resources/mbapreq.thm +++ b/src/ext/BalExtension/wixstdba/Resources/mbapreq.thm @@ -11,8 +11,7 @@ #(loc.Title) - #(loc.InstallLicenseLabel) - #(loc.InstallLicenseTerms) + #(loc.InstallLicenseTerms) @@ -24,13 +23,11 @@ - #(loc.FailedHeader) - #(loc.FailureLabel) - #(loc.FailureLabel2) - #(loc.FailureLogLinkText) - + #(loc.FailureHeader) + #(loc.FailureLogLinkText) + #(loc.FailureRestartText) - + diff --git a/src/ext/BalExtension/wixstdba/Resources/mbapreq.wxl b/src/ext/BalExtension/wixstdba/Resources/mbapreq.wxl index 234eba90..f6c2bb34 100644 --- a/src/ext/BalExtension/wixstdba/Resources/mbapreq.wxl +++ b/src/ext/BalExtension/wixstdba/Resources/mbapreq.wxl @@ -14,17 +14,14 @@ [WixBundleName] Setup Microsoft .NET Framework required for [WixBundleName] setup Are you sure you want to cancel? - Click the "Accept && Install" button to accept the Microsoft .NET Framework - license terms. + Click the "Accept && Install" button to accept the Microsoft .NET Framework <a href="#">license terms</a>. &Accept && Install &Decline Setup Progress Processing: &Cancel Setup Failed - One or more issues caused the setup to fail. Please fix the issues and then retry setup. - For more information see the - log file. + 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 diff --git a/src/ext/BalExtension/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/BalExtension/wixstdba/WixStandardBootstrapperApplication.cpp index 76566b26..9ab2154e 100644 --- a/src/ext/BalExtension/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/BalExtension/wixstdba/WixStandardBootstrapperApplication.cpp @@ -124,7 +124,6 @@ enum WIXSTDBA_CONTROL WIXSTDBA_CONTROL_SUCCESS_CANCEL_BUTTON, // Failure page - WIXSTDBA_CONTROL_FAILURE_LOGFILE_TEXT, WIXSTDBA_CONTROL_FAILURE_LOGFILE_LINK, WIXSTDBA_CONTROL_FAILURE_MESSAGE_TEXT, WIXSTDBA_CONTROL_FAILURE_RESTART_TEXT, @@ -169,12 +168,11 @@ static THEME_ASSIGN_CONTROL_ID vrgInitControls[] = { { WIXSTDBA_CONTROL_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" }, { WIXSTDBA_CONTROL_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" }, - { WIXSTDBA_CONTROL_FAILURE_LOGFILE_TEXT, L"FailureLogFileText" }, { WIXSTDBA_CONTROL_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" }, { WIXSTDBA_CONTROL_FAILURE_MESSAGE_TEXT, L"FailureMessageText" }, { WIXSTDBA_CONTROL_FAILURE_RESTART_TEXT, L"FailureRestartText" }, { WIXSTDBA_CONTROL_FAILURE_RESTART_BUTTON, L"FailureRestartButton" }, - { WIXSTDBA_CONTROL_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" }, + { WIXSTDBA_CONTROL_FAILURE_CANCEL_BUTTON, L"FailureCloseButton" }, }; class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplication @@ -372,15 +370,19 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati virtual STDMETHODIMP_(int) OnCacheAcquireProgress( - __in_z LPCWSTR /*wzPackageOrContainerId*/, - __in_z_opt LPCWSTR /*wzPayloadId*/, - __in DWORD64 /*dw64Progress*/, - __in DWORD64 /*dw64Total*/, + __in_z LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, __in DWORD dwOverallPercentage ) { WCHAR wzProgress[5] = { }; +#ifdef DEBUG + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "WIXSTDBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); +#endif + ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage); ThemeSetTextControl(m_pTheme, WIXSTDBA_CONTROL_CACHE_PROGRESS_TEXT, wzProgress); @@ -389,7 +391,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati m_dwCalculatedCacheProgress = dwOverallPercentage * WIXSTDBA_ACQUIRE_PERCENTAGE / 100; ThemeSetProgressControl(m_pTheme, WIXSTDBA_CONTROL_OVERALL_CALCULATED_PROGRESS_BAR, m_dwCalculatedCacheProgress + m_dwCalculatedExecuteProgress); - return CheckCanceled() ? IDCANCEL : IDNOACTION; + return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); } @@ -457,6 +459,10 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati WCHAR wzProgress[5] = { }; int nResult = IDNOACTION; +#ifdef DEBUG + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "WIXSTDBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage); +#endif + if (BOOTSTRAPPER_DISPLAY_EMBEDDED == m_command.display) { hr = m_pEngine->SendEmbeddedProgress(dwProgressPercentage, dwOverallProgressPercentage, &nResult); @@ -495,13 +501,17 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati virtual int __stdcall OnExecuteProgress( - __in_z LPCWSTR /*wzPackageId*/, - __in DWORD /*dwProgressPercentage*/, + __in_z LPCWSTR wzPackageId, + __in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage ) { WCHAR wzProgress[5] = { }; +#ifdef DEBUG + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "WIXSTDBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); +#endif + ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage); ThemeSetTextControl(m_pTheme, WIXSTDBA_CONTROL_EXECUTE_PROGRESS_TEXT, wzProgress); @@ -510,7 +520,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati m_dwCalculatedExecuteProgress = dwOverallProgressPercentage * (100 - WIXSTDBA_ACQUIRE_PERCENTAGE) / 100; ThemeSetProgressControl(m_pTheme, WIXSTDBA_CONTROL_OVERALL_CALCULATED_PROGRESS_BAR, m_dwCalculatedCacheProgress + m_dwCalculatedExecuteProgress); - return CheckCanceled() ? IDCANCEL : IDNOACTION; + return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); } @@ -614,6 +624,12 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati // If a restart is required and we're not displaying a UI or we are not supposed to prompt for restart then allow the restart. m_fAllowRestart = m_fRestartRequired && (BOOTSTRAPPER_DISPLAY_FULL > m_command.display || BOOTSTRAPPER_RESTART_PROMPT < m_command.restart); + // If we are showing UI, wait a beat before moving to the final screen. + if (BOOTSTRAPPER_DISPLAY_NONE < m_command.display) + { + ::Sleep(250); + } + SetState(WIXSTDBA_STATE_APPLIED, hrStatus); return IDNOACTION; @@ -726,7 +742,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati hr = BalInfoParseFromXml(&m_Bundle, pixdManifest); BalExitOnFailure(hr, "Failed to load bundle information."); - hr = BalConditionsParseFromXml(&m_Conditions, pixdManifest, m_pLocStrings); + hr = BalConditionsParseFromXml(&m_Conditions, pixdManifest, m_pWixLoc); BalExitOnFailure(hr, "Failed to load conditions from XML."); if (m_fPrereq) @@ -806,13 +822,13 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath); BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath); - hr = LocLoadFromFile(sczLocPath, &m_pLocStrings); + hr = LocLoadFromFile(sczLocPath, &m_pWixLoc); BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath); hr = StrAllocString(&m_sczConfirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0); ExitOnFailure(hr, "Failed to initialize confirm message loc identifier."); - hr = LocLocalizeString(m_pLocStrings, &m_sczConfirmCloseMessage); + hr = LocLocalizeString(m_pWixLoc, &m_sczConfirmCloseMessage); BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", m_sczConfirmCloseMessage); LExit: @@ -838,10 +854,10 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati hr = ThemeLoadFromFile(sczThemePath, &m_pTheme); BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath); - hr = ThemeLocalize(m_pTheme, m_pLocStrings); + hr = ThemeLocalize(m_pTheme, m_pWixLoc); BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath); - // Update the caption if there are any formated strings in it. + // Update the caption if there are any formatted strings in it. hr = BalFormatString(m_pTheme->sczCaption, &sczCaption); if (SUCCEEDED(hr)) { @@ -899,6 +915,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati { HRESULT hr = S_OK; IXMLDOMNode* pNode = NULL; + DWORD dwBool = 0; hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixStdbaInformation", &pNode); if (S_FALSE == hr) @@ -921,6 +938,38 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati } BalExitOnFailure(hr, "Failed to get license URL."); + ReleaseObject(pNode); + + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOptions", &pNode); + if (S_FALSE == hr) + { + ExitFunction1(hr = S_OK); + } + BalExitOnFailure(hr, "Failed to read wixstdba options from BootstrapperApplication.xml manifest."); + + hr = XmlGetAttributeNumber(pNode, L"SuppressOptionsUI", &dwBool); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (SUCCEEDED(hr)) + { + m_fSuppressOptionsUI = 0 < dwBool; + } + BalExitOnFailure(hr, "Failed to get SuppressOptionsUI value."); + + dwBool = 0; + hr = XmlGetAttributeNumber(pNode, L"SuppressDowngradeFailure", &dwBool); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (SUCCEEDED(hr)) + { + m_fSuppressDowngradeFailure = 0 < dwBool; + } + BalExitOnFailure(hr, "Failed to get SuppressDowngradeFailure value."); + LExit: ReleaseObject(pNode); return hr; @@ -1059,10 +1108,6 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati case WM_COMMAND: switch (LOWORD(wParam)) { - case WIXSTDBA_CONTROL_EULA_LINK: - pBA->OnClickEulaLink(); - return 0; - case WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX: pBA->OnClickAcceptCheckbox(); return 0; @@ -1099,10 +1144,6 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati pBA->OnClickLaunchButton(); return 0; - case WIXSTDBA_CONTROL_FAILURE_LOGFILE_LINK: - pBA->OnClickLogFileLink(); - return 0; - case WIXSTDBA_CONTROL_SUCCESS_RESTART_BUTTON: __fallthrough; case WIXSTDBA_CONTROL_FAILURE_RESTART_BUTTON: pBA->OnClickRestartButton(); @@ -1118,6 +1159,27 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati return 0; } break; + + case WM_NOTIFY: + if (lParam) + { + LPNMHDR pnmhdr = reinterpret_cast(lParam); + switch (pnmhdr->code) + { + case NM_CLICK: __fallthrough; + case NM_RETURN: + switch (static_cast(pnmhdr->idFrom)) + { + case WIXSTDBA_CONTROL_EULA_LINK: + pBA->OnClickEulaLink(); + return 1; + case WIXSTDBA_CONTROL_FAILURE_LOGFILE_LINK: + pBA->OnClickLogFileLink(); + return 1; + } + } + } + break; } return ThemeDefWindowProc(pBA ? pBA->m_pTheme : NULL, hWnd, uMsg, wParam, lParam); @@ -1174,22 +1236,30 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati hr = (m_sczLicenseFile && *m_sczLicenseFile) ? S_OK : E_INVALIDDATA; if (SUCCEEDED(hr)) { - hr = BalFormatString(m_sczLicenseFile, &sczLicenseFormatted); + hr = StrAllocString(&sczLicenseFormatted, m_sczLicenseFile, 0); if (SUCCEEDED(hr)) { - hr = PathRelativeToModule(&sczLicensePath, sczLicenseFormatted, m_hModule); + hr = LocLocalizeString(m_pWixLoc, &sczLicenseFormatted); if (SUCCEEDED(hr)) { - hr = PathGetDirectory(sczLicensePath, &sczLicenseDirectory); + hr = BalFormatString(sczLicenseFormatted, &sczLicenseFormatted); if (SUCCEEDED(hr)) { - hr = StrAllocString(&sczLicenseFilename, PathFile(sczLicenseFormatted), 0); + hr = PathRelativeToModule(&sczLicensePath, sczLicenseFormatted, m_hModule); if (SUCCEEDED(hr)) { - hr = LocProbeForFile(sczLicenseDirectory, sczLicenseFilename, m_sczLanguage, &sczLicensePath); + hr = PathGetDirectory(sczLicensePath, &sczLicenseDirectory); if (SUCCEEDED(hr)) { - hr = ThemeLoadRichEditFromFile(m_pTheme, WIXSTDBA_CONTROL_EULA_RICHEDIT, sczLicensePath, m_hModule); + hr = StrAllocString(&sczLicenseFilename, PathFile(sczLicenseFormatted), 0); + if (SUCCEEDED(hr)) + { + hr = LocProbeForFile(sczLicenseDirectory, sczLicenseFilename, m_sczLanguage, &sczLicensePath); + if (SUCCEEDED(hr)) + { + hr = ThemeLoadRichEditFromFile(m_pTheme, WIXSTDBA_CONTROL_EULA_RICHEDIT, sczLicensePath, m_hModule); + } + } } } } @@ -1252,8 +1322,15 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati // If we are going to apply a downgrade, bail. if (m_fDowngrading && BOOTSTRAPPER_ACTION_UNINSTALL < action) { - hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION); - BalExitOnFailure(hr, "Cannot install a product when a newer version is installed."); + if (m_fSuppressDowngradeFailure) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing..."); + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION); + BalExitOnFailure(hr, "Cannot install a product when a newer version is installed."); + } } SetState(WIXSTDBA_STATE_PLANNING, hr); @@ -1341,8 +1418,8 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati BOOL fAcceptedLicense = !ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || !ThemeControlEnabled(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX); ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, fAcceptedLicense); - // If there is an "Options" page and the "Options" button exists then enable the button. - BOOL fOptionsEnabled = m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON); + // If there is an "Options" page, the "Options" button exists, and it hasn't been suppressed, then enable the button. + BOOL fOptionsEnabled = m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON) && !m_fSuppressOptionsUI; ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON, fOptionsEnabled); } else if (m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] == dwNewPageId) @@ -1409,7 +1486,6 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati } } - ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_FAILURE_LOGFILE_TEXT, fShowLogLink); ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_FAILURE_LOGFILE_LINK, fShowLogLink); ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_FAILURE_MESSAGE_TEXT, fShowErrorMessage); ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_FAILURE_RESTART_TEXT, fShowRestartButton); @@ -1634,8 +1710,14 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati LPWSTR sczLicensePath = NULL; URI_PROTOCOL protocol = URI_PROTOCOL_UNKNOWN; - hr = BalFormatString(m_sczLicenseUrl, &sczLicenseUrl); - BalExitOnFailure1(hr, "Failed to get format license URL: %ls", m_sczLicenseUrl); + hr = StrAllocString(&sczLicenseUrl, m_sczLicenseUrl, 0); + BalExitOnFailure1(hr, "Failed to copy license URL: %ls", m_sczLicenseUrl); + + hr = LocLocalizeString(m_pWixLoc, &sczLicenseUrl); + BalExitOnFailure1(hr, "Failed to localize license URL: %ls", m_sczLicenseUrl); + + hr = BalFormatString(sczLicenseUrl, &sczLicenseUrl); + BalExitOnFailure1(hr, "Failed to get formatted license URL: %ls", m_sczLicenseUrl); hr = UriProtocol(sczLicenseUrl, &protocol); if (FAILED(hr) || URI_PROTOCOL_UNKNOWN == protocol) @@ -1874,8 +1956,21 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati { m_command.action = BOOTSTRAPPER_ACTION_INSTALL; } + else // maybe modify the action state if the bundle is or is not already installed. + { + LONGLONG llInstalled = 0; + HRESULT hr = pEngine->GetVariableNumeric(L"WixBundleInstalled", &llInstalled); + if (SUCCEEDED(hr) && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == m_command.action) + { + m_command.action = BOOTSTRAPPER_ACTION_MODIFY; + } + else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == m_command.action || BOOTSTRAPPER_ACTION_REPAIR == m_command.action)) + { + m_command.action = BOOTSTRAPPER_ACTION_INSTALL; + } + } - m_pLocStrings = NULL; + m_pWixLoc = NULL; memset(&m_Bundle, 0, sizeof(m_Bundle)); memset(&m_Conditions, 0, sizeof(m_Conditions)); m_sczConfirmCloseMessage = NULL; @@ -1897,6 +1992,8 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati m_sczLicenseFile = NULL; m_sczLicenseUrl = NULL; + m_fSuppressOptionsUI = FALSE; + m_fSuppressDowngradeFailure = FALSE; m_fPrereq = fPrereq; m_sczPrereqPackage = NULL; @@ -1920,7 +2017,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati ReleaseStr(m_sczConfirmCloseMessage); BalConditionsUninitialize(&m_Conditions); BalInfoUninitialize(&m_Bundle); - LocFree(m_pLocStrings); + LocFree(m_pWixLoc); ReleaseStr(m_sczLanguage); ReleaseStr(m_sczLicenseFile); @@ -1934,7 +2031,7 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati BOOTSTRAPPER_COMMAND m_command; IBootstrapperEngine* m_pEngine; - LOC_STRINGSET* m_pLocStrings; + WIX_LOCALIZATION* m_pWixLoc; BAL_INFO_BUNDLE m_Bundle; BAL_CONDITIONS m_Conditions; LPWSTR m_sczFailedMessage; @@ -1960,6 +2057,8 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati LPWSTR m_sczLicenseFile; LPWSTR m_sczLicenseUrl; + BOOL m_fSuppressOptionsUI; + BOOL m_fSuppressDowngradeFailure; BOOL m_fPrereq; LPWSTR m_sczPrereqPackage; diff --git a/src/ext/UIExtension/wixlib/WixUI_fi-FI.wxl b/src/ext/UIExtension/wixlib/WixUI_fi-FI.wxl index 07dc56fe..394c58b4 100644 --- a/src/ext/UIExtension/wixlib/WixUI_fi-FI.wxl +++ b/src/ext/UIExtension/wixlib/WixUI_fi-FI.wxl @@ -14,6 +14,11 @@ You must not remove this notice, or any other, from this software. --> + + + + + &Edellinen &Seuraava Peruuta diff --git a/src/wix/Binder.cs b/src/wix/Binder.cs index a2993326..d56099a9 100644 --- a/src/wix/Binder.cs +++ b/src/wix/Binder.cs @@ -100,7 +100,7 @@ public sealed class Binder : WixBinder, IDisposable // The following constants must stay in sync with src\burn\engine\core.h private const string BURN_BUNDLE_NAME = "WixBundleName"; private const string BURN_BUNDLE_ORIGINAL_SOURCE = "WixBundleOriginalSource"; - private const string BURN_BUNDLE_PROVIDER_KEY = "WixBundleProviderKey"; + private const string BURN_BUNDLE_LAST_USED_SOURCE = "WixBundleLastUsedSource"; private string emptyFile; @@ -2976,6 +2976,11 @@ private bool BindBundle(Output bundle, string bundleFile) wellKnownVariable[3] = 0; wellKnownVariable[4] = 1; + wellKnownVariable = variableTable.CreateRow(null); + wellKnownVariable[0] = Binder.BURN_BUNDLE_LAST_USED_SOURCE; + wellKnownVariable[3] = 0; + wellKnownVariable[4] = 1; + // To make lookups easier, we load the variable table bottom-up, so // that we can index by ID. List allVariables = new List(variableTable.Rows.Count); @@ -4120,14 +4125,14 @@ private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string e writer.WriteAttributeString("Id", container.Id); if (container.Type == "detached") { - writer.WriteAttributeString("SourcePath", container.Name); + writer.WriteAttributeString("FilePath", container.Name); writer.WriteAttributeString("FileSize", container.FileInfo.Length.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("Hash", Common.GetFileHash(container.FileInfo)); writer.WriteAttributeString("Packaging", "detached"); } else if (container.Type == "attached") { - writer.WriteAttributeString("SourcePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. + writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. writer.WriteAttributeString("AttachedIndex", containerIndex.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("Attached", "yes"); writer.WriteAttributeString("Primary", "yes"); @@ -6975,9 +6980,10 @@ public BundleInfo(string bundleFile, Row row) this.Platform = (Platform)Enum.Parse(typeof(Platform), (string)row[17]); this.RegistrationInfo.ParentName = (string)row[18]; + this.UpgradeCode = (string)row[19]; - // Default provider key is the Id. - this.ProviderKey = this.Id.ToString("B"); + // Default provider key is the UpgradeCode and Version. + this.ProviderKey = String.Concat(this.UpgradeCode, "v", this.Version); } public YesNoDefaultType Compressed = YesNoDefaultType.Default; @@ -7006,6 +7012,7 @@ public PayloadInfo.PackagingType DefaultPackagingType public string Tag { get; private set; } public Platform Platform { get; private set; } public string Version { get; private set; } + public string UpgradeCode { get; private set; } public string ProviderKey { get; internal set; } } @@ -7110,12 +7117,7 @@ public RollbackBoundaryInfo(Row row) { this.Id = row[0].ToString(); - this.Vital = YesNoType.NotSet; - if (null != row[10]) - { - this.Vital = (1 == (int)row[10]) ? YesNoType.Yes : YesNoType.No; - } - + this.Vital = (null == row[10] || 1 == (int)row[10]) ? YesNoType.Yes : YesNoType.No; this.SourceLineNumbers = row.SourceLineNumbers; } @@ -7146,28 +7148,20 @@ public ChainPackageInfo(Row row, Table wixGroupTable, Dictionary allPayloads, BinderFileManager fileManager, BinderCore core) { + BundlePackageAttributes attributes = (null == attributesData) ? 0 : (BundlePackageAttributes)attributesData; + YesNoType cache = YesNoType.NotSet; if (null != cacheData) { cache = (1 == (int)cacheData) ? YesNoType.Yes : YesNoType.No; } - YesNoType permanent = YesNoType.NotSet; - if (null != permanentData) - { - permanent = (1 == (int)permanentData) ? YesNoType.Yes : YesNoType.No; - } - - YesNoType vital = YesNoType.NotSet; - if (null != vitalData) - { - vital = (1 == (int)vitalData) ? YesNoType.Yes : YesNoType.No; - } + YesNoType vital = (null == vitalData || 1 == (int)vitalData) ? YesNoType.Yes : YesNoType.No; YesNoType perMachine = YesNoType.NotSet; if (null != perMachineData) @@ -7223,7 +7217,8 @@ public ChainPackageInfo(Row row, Table wixGroupTable, Dictionary diff --git a/src/wix/BundlePackageAttributes.cs b/src/wix/BundlePackageAttributes.cs new file mode 100644 index 00000000..f34350e2 --- /dev/null +++ b/src/wix/BundlePackageAttributes.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// +// Bit flags for a bundle package in the Windows Installer Xml toolset. +// +//------------------------------------------------------------------------------------------------- + +namespace Microsoft.Tools.WindowsInstallerXml +{ + using System; + + /// + /// Attributes available for a bundle package. + /// + [Flags] + internal enum BundlePackageAttributes : int + { + None = 0x0, + Permanent = 0x1, + Visible = 0x2, + } +} diff --git a/src/wix/Compiler.cs b/src/wix/Compiler.cs index c3c02cbd..8087c6ac 100644 --- a/src/wix/Compiler.cs +++ b/src/wix/Compiler.cs @@ -19772,6 +19772,11 @@ private void ParseBundleElement(XmlNode node) this.core.OnMessage(WixWarnings.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); } + if (String.IsNullOrEmpty(upgradeCode)) + { + this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.LocalName, "UpgradeCode")); + } + if (String.IsNullOrEmpty(copyright)) { if (String.IsNullOrEmpty(manufacturer)) @@ -19938,6 +19943,7 @@ private void ParseBundleElement(XmlNode node) row[16] = tag; row[17] = this.currentPlatform.ToString(); row[18] = parentName; + row[19] = upgradeCode; } } @@ -21081,6 +21087,7 @@ private string ParseChainPackage(XmlNode node, ChainPackageType packageType, Com string logPathVariable = (packageType == ChainPackageType.Msu) ? String.Empty : null; string rollbackPathVariable = (packageType == ChainPackageType.Msu) ? String.Empty : null; YesNoType permanent = YesNoType.NotSet; + YesNoType visible = YesNoType.NotSet; YesNoType vital = YesNoType.Yes; string installCommand = null; string repairCommand = null; @@ -21097,6 +21104,7 @@ private string ParseChainPackage(XmlNode node, ChainPackageType packageType, Com YesNoType displayInternalUI = YesNoType.NotSet; YesNoType enableFeatureSelection = YesNoType.NotSet; YesNoType forcePerMachine = YesNoType.NotSet; + BundlePackageAttributes attributes = BundlePackageAttributes.None; string[] expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" }; @@ -21161,6 +21169,10 @@ private string ParseChainPackage(XmlNode node, ChainPackageType packageType, Com case "Permanent": permanent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; + case "Visible": + visible = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == ChainPackageType.Msi); + break; case "Vital": vital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; @@ -21367,6 +21379,16 @@ private string ParseChainPackage(XmlNode node, ChainPackageType packageType, Com if (!this.core.EncounteredError) { + if (YesNoType.Yes == permanent) + { + attributes |= BundlePackageAttributes.Permanent; + } + + if (YesNoType.Yes == visible) + { + attributes |= BundlePackageAttributes.Visible; + } + // We create the package contents as a payload with this package as the parent this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null, compressed, suppressSignatureVerification); @@ -21387,10 +21409,7 @@ private string ParseChainPackage(XmlNode node, ChainPackageType packageType, Com row[8] = cacheId; - if (YesNoType.NotSet != permanent) - { - row[9] = (YesNoType.Yes == permanent) ? 1 : 0; - } + row[9] = (int)attributes; if (YesNoType.NotSet != vital) { @@ -22138,10 +22157,7 @@ private void ParseWixVariableElement(XmlNode node) if (!this.core.EncounteredError) { - WixVariableRow wixVariableRow = (WixVariableRow)this.core.CreateRow(sourceLineNumbers, "WixVariable"); - wixVariableRow.Id = id; - wixVariableRow.Value = value; - wixVariableRow.Overridable = overridable; + this.core.CreateWixVariableRow(sourceLineNumbers, id, value, overridable); } } diff --git a/src/wix/CompilerCore.cs b/src/wix/CompilerCore.cs index 199698b4..6d942bd1 100644 --- a/src/wix/CompilerCore.cs +++ b/src/wix/CompilerCore.cs @@ -661,6 +661,18 @@ public Row CreateRow(SourceLineNumberCollection sourceLineNumbers, string tableN return table.CreateRow(sourceLineNumbers); } + /// + /// Creates a WixVariable row in the active section. + /// + /// Source and line number of current row. + public void CreateWixVariableRow(SourceLineNumberCollection sourceLineNumbers, string id, string value, bool overridable) + { + WixVariableRow wixVariableRow = (WixVariableRow)this.CreateRow(sourceLineNumbers, "WixVariable"); + wixVariableRow.Id = id; + wixVariableRow.Value = value; + wixVariableRow.Overridable = overridable; + } + /// /// Adds a patch resource reference to the list of resoures to be filtered when producing a patch. This method should only be used when processing children of a patch family. /// diff --git a/src/wix/Data/messages.xml b/src/wix/Data/messages.xml index 01e89c4f..795f2b3e 100644 --- a/src/wix/Data/messages.xml +++ b/src/wix/Data/messages.xml @@ -2952,7 +2952,7 @@ - The {0}/@{1} attribute's value, '{2}', is not a valid external cabinet name. Legal cabinet names should follow 8.3 format: they should contain no more than 8 characters followed by an optional extension of no more than 3 characters. Any character except for the follow may be used: \ ? | > < : / * " + , ; = [ ] (space). The Windows Installer team has recommended following the 8.3 format for external cabinet files and any other naming scheme is officially unsupported (which means it is not guaranteed to work on all platforms). + The {0}/@{1} attribute's value, '{2}', is not a valid external cabinet name. Legal cabinet names should follow 8.3 format: they should contain no more than 8 characters followed by an optional extension of no more than 3 characters. Any character except for the following may be used: \ ? | > < : / * " + , ; = [ ] (space). The Windows Installer team has recommended following the 8.3 format for external cabinet files and any other naming scheme is officially unsupported (which means it is not guaranteed to work on all platforms). diff --git a/src/wix/Data/tables.xml b/src/wix/Data/tables.xml index 96dbd237..ae7423de 100644 --- a/src/wix/Data/tables.xml +++ b/src/wix/Data/tables.xml @@ -1625,6 +1625,7 @@ + @@ -1658,7 +1659,7 @@ - + diff --git a/src/wix/Localization.cs b/src/wix/Localization.cs index 0a12461e..38f8f0d6 100644 --- a/src/wix/Localization.cs +++ b/src/wix/Localization.cs @@ -142,6 +142,78 @@ public void Persist(XmlWriter writer) writer.WriteEndElement(); } + foreach (string controlKey in this.localizedControls.Keys) + { + writer.WriteStartElement("Control", XmlNamespaceUri); + + string dialog = null; + string control = null; + if (0 < controlKey.IndexOf('/')) + { + string[] controlKeys = controlKey.Split('/'); + dialog = controlKeys[0]; + control = controlKeys[1]; + } + else + { + control = controlKey; + } + + if (!String.IsNullOrEmpty(dialog)) + { + writer.WriteAttributeString("Dialog", dialog); + } + + if (!String.IsNullOrEmpty(control)) + { + writer.WriteAttributeString("Control", control); + } + + LocalizedControl localizedControl = this.localizedControls[controlKey]; + + if (CompilerCore.IntegerNotSet != localizedControl.X) + { + writer.WriteAttributeString("X", localizedControl.X.ToString()); + } + + if (CompilerCore.IntegerNotSet != localizedControl.Y) + { + writer.WriteAttributeString("Y", localizedControl.Y.ToString()); + } + + if (CompilerCore.IntegerNotSet != localizedControl.Width) + { + writer.WriteAttributeString("Width", localizedControl.Width.ToString()); + } + + if (CompilerCore.IntegerNotSet != localizedControl.Height) + { + writer.WriteAttributeString("Height", localizedControl.Height.ToString()); + } + + if (MsiInterop.MsidbControlAttributesRTLRO == (localizedControl.Attributes & MsiInterop.MsidbControlAttributesRTLRO)) + { + writer.WriteAttributeString("RightToLeft", "yes"); + } + + if (MsiInterop.MsidbControlAttributesRightAligned == (localizedControl.Attributes & MsiInterop.MsidbControlAttributesRightAligned)) + { + writer.WriteAttributeString("RightAligned", "yes"); + } + + if (MsiInterop.MsidbControlAttributesLeftScroll == (localizedControl.Attributes & MsiInterop.MsidbControlAttributesLeftScroll)) + { + writer.WriteAttributeString("LeftScroll", "yes"); + } + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + writer.WriteCData(localizedControl.Text); + } + + writer.WriteEndElement(); + } + writer.WriteEndElement(); } diff --git a/src/wix/TableDefinition.cs b/src/wix/TableDefinition.cs index 7eb8e287..cded62e8 100644 --- a/src/wix/TableDefinition.cs +++ b/src/wix/TableDefinition.cs @@ -259,7 +259,7 @@ internal static TableDefinition Parse(XmlReader reader) } } - if (!unreal && !hasPrimaryKeyColumn) + if (!unreal && !bootstrapperApplicationData && !hasPrimaryKeyColumn) { throw new WixException(WixErrors.RealTableMissingPrimaryKeyColumn(SourceLineNumberCollection.FromUri(reader.BaseURI), name)); } diff --git a/src/wix/Wix.csproj b/src/wix/Wix.csproj index 4d489bf1..b4401b0e 100644 --- a/src/wix/Wix.csproj +++ b/src/wix/Wix.csproj @@ -20,6 +20,7 @@ + diff --git a/src/wix/Xsd/wix.xsd b/src/wix/Xsd/wix.xsd index 4ad5689f..ace32bff 100644 --- a/src/wix/Xsd/wix.xsd +++ b/src/wix/Xsd/wix.xsd @@ -216,7 +216,7 @@ - + Unique identifier for a family of bundles. If two bundles have the same UpgradeCode the @@ -581,6 +581,15 @@ + + + + Specifies whether the MSI will be displayed in Programs and Features (also known as Add/Remove Programs). If "yes" is + specified the MSI package information will be displayed in Programs and Features. The default "no" indicates the MSI + will not be displayed. + + + @@ -757,10 +766,9 @@ - Specifies whether the rollback boundary aborts the chain. If "yes" is specified and - the rollback boundary is encountered then the chain will fail and rollback or stop. The - default "no" indicates that the chain should continue successfuly at the next rollback - boundary. + Specifies whether the rollback boundary aborts the chain. The default "yes" indicates that if + the rollback boundary is encountered then the chain will fail and rollback or stop. If "no" + is specified then the chain should continue successfuly at the next rollback boundary. @@ -864,10 +872,9 @@ - Specifies whether the package must succeed for the chain to continue. If "yes" is - specified and the package fails then the chain will fail and rollback or stop. The - default "no" indicates that the chain should continue even if the package reports - a failure. + Specifies whether the package must succeed for the chain to continue. The default "yes" + inidicates that if the package fails then the chain will fail and rollback or stop. If + "no" is specified then the chain will continue even if the package reports failure. diff --git a/test/data/Burn/DependencyTests/A.wxs b/test/data/Burn/DependencyTests/A.wxs new file mode 100644 index 00000000..a238b5e2 --- /dev/null +++ b/test/data/Burn/DependencyTests/A.wxs @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/B.wxs b/test/data/Burn/DependencyTests/B.wxs new file mode 100644 index 00000000..41b6a63f --- /dev/null +++ b/test/data/Burn/DependencyTests/B.wxs @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleA.wxs b/test/data/Burn/DependencyTests/BundleA.wxs new file mode 100644 index 00000000..f113ad11 --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleA.wxs @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleB.wxs b/test/data/Burn/DependencyTests/BundleB.wxs new file mode 100644 index 00000000..97d2749e --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleB.wxs @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleC.wxs b/test/data/Burn/DependencyTests/BundleC.wxs new file mode 100644 index 00000000..8fa4dda3 --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleC.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleD.wxs b/test/data/Burn/DependencyTests/BundleD.wxs new file mode 100644 index 00000000..4c23d708 --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleD.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleE.wxs b/test/data/Burn/DependencyTests/BundleE.wxs new file mode 100644 index 00000000..85f72787 --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleE.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleF.wxs b/test/data/Burn/DependencyTests/BundleF.wxs new file mode 100644 index 00000000..85c93279 --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleF.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/BundleG.wxs b/test/data/Burn/DependencyTests/BundleG.wxs new file mode 100644 index 00000000..ea16c17c --- /dev/null +++ b/test/data/Burn/DependencyTests/BundleG.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/DependencyTests/C.wxs b/test/data/Burn/DependencyTests/C.wxs new file mode 100644 index 00000000..c6b317cd --- /dev/null +++ b/test/data/Burn/DependencyTests/C.wxs @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/PatchA.wxs b/test/data/Burn/DependencyTests/PatchA.wxs similarity index 60% rename from test/data/Extensions/DependencyExtension/DependencyExtensionTests/PatchA.wxs rename to test/data/Burn/DependencyTests/PatchA.wxs index 08f2b869..b876609f 100644 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/PatchA.wxs +++ b/test/data/Burn/DependencyTests/PatchA.wxs @@ -1,14 +1,6 @@ @@ -24,8 +16,10 @@ + - + + diff --git a/test/data/Burn/FailureTests/A.wxs b/test/data/Burn/FailureTests/A.wxs new file mode 100644 index 00000000..7ddeb003 --- /dev/null +++ b/test/data/Burn/FailureTests/A.wxs @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/FailureTests/B.wxs b/test/data/Burn/FailureTests/B.wxs new file mode 100644 index 00000000..e2f1f5f4 --- /dev/null +++ b/test/data/Burn/FailureTests/B.wxs @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/FailureTests/BundleA.wxs b/test/data/Burn/FailureTests/BundleA.wxs new file mode 100644 index 00000000..a5df567e --- /dev/null +++ b/test/data/Burn/FailureTests/BundleA.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/FailureTests/BundleB.wxs b/test/data/Burn/FailureTests/BundleB.wxs new file mode 100644 index 00000000..94a35c80 --- /dev/null +++ b/test/data/Burn/FailureTests/BundleB.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Burn/RelatedBundleTests/BundleE.wxs b/test/data/Burn/RelatedBundleTests/BundleE.wxs new file mode 100644 index 00000000..965e13d7 --- /dev/null +++ b/test/data/Burn/RelatedBundleTests/BundleE.wxs @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleA.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleA.wxs deleted file mode 100644 index cb880f77..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleA.wxs +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleB.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleB.wxs deleted file mode 100644 index 099f5aa3..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleB.wxs +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleC.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleC.wxs deleted file mode 100644 index b867ae95..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleC.wxs +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleD.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleD.wxs deleted file mode 100644 index 13636f07..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleD.wxs +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleE.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleE.wxs deleted file mode 100644 index fb8c3ca3..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleE.wxs +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleF.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleF.wxs deleted file mode 100644 index 51ce070a..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleF.wxs +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleG.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleG.wxs deleted file mode 100644 index b552ed34..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/BundleG.wxs +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/Fail.wxs b/test/data/Extensions/DependencyExtension/DependencyExtensionTests/Fail.wxs deleted file mode 100644 index 223def80..00000000 --- a/test/data/Extensions/DependencyExtension/DependencyExtensionTests/Fail.wxs +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/src/Burn/TestBA/TestBA.cs b/test/src/Burn/TestBA/TestBA.cs index 9479dfad..1cfd0c7d 100644 --- a/test/src/Burn/TestBA/TestBA.cs +++ b/test/src/Burn/TestBA/TestBA.cs @@ -89,6 +89,8 @@ protected override void OnPlanComplete(PlanCompleteEventArgs args) protected override void OnCachePackageBegin(CachePackageBeginEventArgs args) { + this.Engine.Log(LogLevel.Standard, String.Format("TESTBA: OnCachePackageBegin() - package: {0}, payloads to cache: {1}", args.PackageId, args.CachePayloads)); + string slowProgress = ReadPackageAction(args.PackageId, "SlowCache"); if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringCache)) { @@ -104,6 +106,8 @@ protected override void OnCachePackageBegin(CachePackageBeginEventArgs args) protected override void OnCacheAcquireProgress(CacheAcquireProgressEventArgs args) { + this.Engine.Log(LogLevel.Standard, String.Format("TESTBA: OnCacheAcquireProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage)); + if (this.cancelCacheAtProgress > 0 && this.cancelCacheAtProgress <= args.Progress) { args.Result = Result.Cancel; @@ -116,6 +120,8 @@ protected override void OnCacheAcquireProgress(CacheAcquireProgressEventArgs arg protected override void OnExecutePackageBegin(ExecutePackageBeginEventArgs args) { + this.Engine.Log(LogLevel.Standard, String.Format("TESTBA: OnExecutePackageBegin() - package: {0}, rollback: {1}", args.PackageId, !args.ShouldExecute)); + string slowProgress = ReadPackageAction(args.PackageId, "SlowExecute"); if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringExecute)) { @@ -131,6 +137,8 @@ protected override void OnExecutePackageBegin(ExecutePackageBeginEventArgs args) protected override void OnExecuteProgress(ExecuteProgressEventArgs args) { + this.Engine.Log(LogLevel.Standard, String.Format("TESTBA: OnExecuteProgress() - package: {0}, progress: {1}%, overall progress: {2}%", args.PackageId, args.ProgressPercentage, args.OverallPercentage)); + if (this.cancelExecuteAtProgress > 0 && this.cancelExecuteAtProgress <= args.ProgressPercentage) { args.Result = Result.Cancel; @@ -146,6 +154,11 @@ protected override void OnExecutePatchTarget(ExecutePatchTargetEventArgs args) this.Engine.Log(LogLevel.Verbose, String.Format("TEST: OnExecutePatchTarget - Patch Package: {0}, Target Product Code: {1}", args.PackageId, args.TargetProductCode)); } + protected override void OnProgress(ProgressEventArgs args) + { + this.Engine.Log(LogLevel.Standard, String.Format("TESTBA: OnProgress() - progress: {0}%, overall progress: {1}%", args.ProgressPercentage, args.OverallPercentage)); + } + protected override void OnApplyComplete(ApplyCompleteEventArgs args) { // Output what the privileges are now. diff --git a/test/src/WixTests/Burn/Burn.DependencyTests.cs b/test/src/WixTests/Burn/Burn.DependencyTests.cs new file mode 100644 index 00000000..287feba7 --- /dev/null +++ b/test/src/WixTests/Burn/Burn.DependencyTests.cs @@ -0,0 +1,490 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// Contains methods test Burn. +// +//----------------------------------------------------------------------- + +namespace Microsoft.Tools.WindowsInstallerXml.Test.Tests.Burn +{ + using System.Collections.Generic; + using Microsoft.Tools.WindowsInstallerXml.Test.Utilities; + using Microsoft.Tools.WindowsInstallerXml.Test.Verifiers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.Win32; + + [TestClass] + public class DependencyTests : BurnTests + { + [TestMethod] + [Priority(2)] + [Description("Installs bundle A then bundle B, and removes them in reverse order.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_InstallBundles() + { + const string expectedVersion = "1.0.0.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleB = new BundleBuilder(this, "BundleB") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the bundles. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + BundleInstaller installerB = new BundleInstaller(this, bundleB).Install(); + + // Make sure the MSIs are installed. + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("A") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Uninstall in reverse order. + installerB.Uninstall(); + installerA.Uninstall(); + + // Make sure the MSIs are not installed. + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Installs bundle A then bundle B, attempts to remove bundle A, then removes them in reverse order.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_UninstallBundleWithDependent() + { + const string expectedVersion = "1.0.0.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleB = new BundleBuilder(this, "BundleB") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the bundles. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + BundleInstaller installerB = new BundleInstaller(this, bundleB).Install(); + + // Make sure the MSIs and EXE are installed. + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Attempt to uninstall bundleA. + installerA.Uninstall(); + + // Verify packageA and ExeA are still installed. + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Uninstall bundleB now. + installerB.Uninstall(); + + // Make sure the MSIs are installed. + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Install bundle A then B, upgrades A, then attempts to uninstall A while B is still present.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_UninstallUpgradedBundle() + { + const string expectedVersion1 = "1.0.0.0"; + const string expectedVersion2 = "1.0.1.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + string packageA1 = new PackageBuilder(this, "A") { Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } } }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleB = new BundleBuilder(this, "BundleB") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Override the path for A to A1. + bindPaths["packageA"] = packageA1; + string bundleA1 = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } } }.Build().Output; + + // Install the bundles. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + BundleInstaller installerB = new BundleInstaller(this, bundleB).Install(); + + // Make sure the MSIs and EXE are installed. + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion1, actualVersion); + } + + // Attempt to upgrade bundleA. + BundleInstaller installerA1 = new BundleInstaller(this, bundleA1).Install(); + + // Verify packageA1 was installed and packageA was uninstalled. + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA1)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion2, actualVersion); + } + + // Uninstall bundleA1 and verify that packageA1 is still installed. + installerA1.Uninstall(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA1)); + + // Uninstall bundleB now. + installerB.Uninstall(); + + // BUG: BundleB does not know about PackageA1 (A,v2), so remove it explicitly (SFBUG:3307315). + MSIExec.UninstallProduct(packageA1, MSIExec.MSIExecReturnCode.SUCCESS); + + // Make sure the MSIs are not installed. + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA1)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Install bundle A, then upgrade it with a slipstream of package A and patch A.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_InstallUpgradeSlipstreamBundle() + { + const string expectedVersion1 = "1.0.0.0"; + const string expectedVersion2 = "1.0.1.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageA1 = new PackageBuilder(this, "A") { Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } }, NeverGetsInstalled = true }.Build().Output; + string patchA = new PatchBuilder(this, "PatchA") { PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } }, TargetPath = packageA, UpgradePath = packageA1 }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("patchA", patchA); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleC = new BundleBuilder(this, "BundleC") { BindPaths = bindPaths, Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } } }.Build().Output; + + // Install the base bundle and make sure it's installed. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion1, actualVersion); + } + + // Install the upgrade bundle with a slipstreamed patch and make sure the patch is installed. + // SFBUG:3387046 - Uninstalling bundle registers a dependency on a package + BundleInstaller installerC = new BundleInstaller(this, bundleC).Install(); + Assert.IsTrue(MsiUtils.IsPatchInstalled(patchA)); + + // BundleC doesn't carry the EXE, so make sure it's removed. + using (RegistryKey root = this.GetTestRegistryRoot()) + { + Assert.IsNull(root.GetValue("Version")); + } + + // Repair the upgrade bundle to make sure it does not prompt for source. + // SFBUG:3386927 - MSIs get removed from cache during upgrade + installerC.Repair(); + + // Uninstall the slipstream bundle and make sure both packages are uninstalled. + installerC.Uninstall(); + Assert.IsFalse(MsiUtils.IsPatchInstalled(patchA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + } + + [TestMethod] + [Priority(2)] + [Description("Install bundle A, then install upgrade bundle D which will fail and roll back.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_RollbackUpgradeBundle() + { + const string expectedVersion1 = "1.0.0.0"; + const string expectedVersion2 = "1.0.1.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleD = new BundleBuilder(this, "BundleD") { BindPaths = bindPaths, Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion2 } } }.Build().Output; + + // Install the base bundle and make sure it's installed. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion1, actualVersion); + } + + // Install the upgrade bundle that will fail and rollback. Make sure packageA is still present. + // SFBUG:3405221 - pkg dependecy not removed in rollback if pkg already present + BundleInstaller installerD = new BundleInstaller(this, bundleD).Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion1, actualVersion); + } + + // Uninstall the first bundle and make sure packageA is uninstalled. + installerA.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Installs an MSI then fails a non-vital package to test that the bundle still installs successfully.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_FailNonVitalPackage() + { + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageC = new PackageBuilder(this, "C") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageC); + + // Build the bundle. + string bundleE = new BundleBuilder(this, "BundleE") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the bundle and make sure packageA is installed. + // SFBUG:3435047 - Make sure during install we don't fail for non-vital packages. + BundleInstaller installerE = new BundleInstaller(this, bundleE).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageC)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + Assert.IsNull(root.GetValue("Version")); + } + + // Repair the bundle. + // SFBUG:3435047 - Make sure during repair we don't fail for the same reason in a different code path. + installerE.Repair(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageC)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + Assert.IsNull(root.GetValue("Version")); + } + + // Uninstall the bundle and make sure packageA is uninstalled. + installerE.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Installs a bundle, then an addon bundle, and uninstalls the main bundle.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_UninstallAddonBundle() + { + const string expectedVersion = "1.0.0.0"; + + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundles. + string bundleA1 = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleA2 = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleF = new BundleBuilder(this, "BundleF") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the base bundle and make sure all packages are installed. + BundleInstaller installerF = new BundleInstaller(this, bundleF).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + + // Install an addon bundle and make sure all packages are installed. + BundleInstaller installerA1 = new BundleInstaller(this, bundleA1).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Install a second addon bundle and make sure all packages are installed. + BundleInstaller installerA2 = new BundleInstaller(this, bundleA2).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Uninstall the base bundle and make sure all packages are uninstalled. + installerF.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsNull(this.GetTestRegistryRoot()); + } + + [TestMethod] + [Priority(2)] + [Description("Installs a bundle, then an addon bundle, and uninstalls the main bundle.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_InstallPatchBundle() + { + const string expectedVersion = "1.0.1.0"; + + // Build the packages. + string packageA1 = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageA2 = new PackageBuilder(this, "A") { Extensions = Extensions, PreprocessorVariables = new Dictionary() { { "Version", expectedVersion } }, NeverGetsInstalled = true }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + string patchA = new PatchBuilder(this, "PatchA") { PreprocessorVariables = new Dictionary() { { "Version", expectedVersion } }, TargetPath = packageA1, UpgradePath = packageA2 }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA1); + bindPaths.Add("packageB", packageB); + bindPaths.Add("patchA", patchA); + + // Build the bundles. + string bundleF = new BundleBuilder(this, "BundleF") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleG = new BundleBuilder(this, "BundleG") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the base bundle and make sure all packages are installed. + BundleInstaller installerF = new BundleInstaller(this, bundleF).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA1)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + + // Install patch bundle and make sure all packages are installed. + BundleInstaller installerG = new BundleInstaller(this, bundleG).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA1)); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsTrue(MsiUtils.IsPatchInstalled(patchA)); + + // Uninstall the base bundle and make sure all packages are uninstalled. + installerF.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA1)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + Assert.IsFalse(MsiUtils.IsPatchInstalled(patchA)); + } + + [TestMethod] + [Priority(2)] + [Description("Installs two bundles with one bundle not requesting a shared package be installed, then uninstalls.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_DifferentPackageRequestStates() + { + const string expectedVersion = "1.0.0.0"; + + // Build the package. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundles. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + string bundleB = new BundleBuilder(this, "BundleB") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Install the base bundle and make sure it's installed. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(); + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // SFBUG:3469206 - install a bundle without installing the shared package, which should not be ref-counted. + this.SetPackageRequestedState("PackageA", Bootstrapper.RequestState.None); + this.SetPackageRequestedState("PackageB", Bootstrapper.RequestState.None); + BundleInstaller installerB = new BundleInstaller(this, bundleB).Install(); + + Assert.IsTrue(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Uninstall the first bundle and make sure packageA is uninstalled. + this.ResetPackageStates("PackageA"); + installerA.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + string actualVersion = root.GetValue("Version") as string; + Assert.AreEqual(expectedVersion, actualVersion); + } + + // Uninstall the second bundle and make sure all packages are uninstalled. + installerB.Uninstall(); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + using (RegistryKey root = this.GetTestRegistryRoot()) + { + Assert.IsNull(root.GetValue("Version")); + } + } + } +} diff --git a/test/src/WixTests/Burn/Burn.FailureTests.cs b/test/src/WixTests/Burn/Burn.FailureTests.cs new file mode 100644 index 00000000..e32b958f --- /dev/null +++ b/test/src/WixTests/Burn/Burn.FailureTests.cs @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// Contains methods test Burn failure scenarios. +// +//----------------------------------------------------------------------- + +namespace Microsoft.Tools.WindowsInstallerXml.Test.Tests.Burn +{ + using System; + using System.Collections.Generic; + using System.IO; + using Microsoft.Deployment.WindowsInstaller; + using Microsoft.Tools.WindowsInstallerXml.Test.Utilities; + using Microsoft.Tools.WindowsInstallerXml.Test.Verifiers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.Win32; + + [TestClass] + public class FailureTests : BurnTests + { + [TestMethod] + [Priority(2)] + [Description("Cancels Package B very, very early.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_CancelVeryEarly() + { + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundle. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Cancel package B right away. + this.SetPackageCancelExecuteAtProgress("PackageB", 1); + + // Install the bundle and hopefully it fails. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(expectedExitCode: ErrorCodes.ERROR_INSTALL_USEREXIT); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + + this.CleanTestArtifacts = true; + } + + [TestMethod] + [Priority(2)] + [Description("Cancels Package B very, very late.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_CancelVeryLate() + { + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundle. + string bundleA = new BundleBuilder(this, "BundleA") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Cancel package B at the last moment possible.. + this.SetPackageCancelExecuteAtProgress("PackageB", 100); + + // Install the bundle and hopefully it fails. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(expectedExitCode: ErrorCodes.ERROR_INSTALL_USEREXIT); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + + this.CleanTestArtifacts = true; + } + + [TestMethod] + [Priority(2)] + [Description("Cancels Package A being executed while Package B still being cached.")] + [TestProperty("IsRuntimeTest", "true")] + public void Burn_CancelExecuteWhileCaching() + { + // Build the packages. + string packageA = new PackageBuilder(this, "A") { Extensions = Extensions }.Build().Output; + string packageB = new PackageBuilder(this, "B") { Extensions = Extensions }.Build().Output; + + // Create the named bind paths to the packages. + Dictionary bindPaths = new Dictionary(); + bindPaths.Add("packageA", packageA); + bindPaths.Add("packageB", packageB); + + // Build the bundle. + string bundleA = new BundleBuilder(this, "BundleB") { BindPaths = bindPaths, Extensions = Extensions }.Build().Output; + + // Slow the caching of package B to ensure that package A starts installing and cancels. + this.SetPackageCancelExecuteAtProgress("PackageA", 50); + this.SetPackageSlowCache("PackageB", 1000); + + // Install the bundle and hopefully it fails. + BundleInstaller installerA = new BundleInstaller(this, bundleA).Install(expectedExitCode: ErrorCodes.ERROR_INSTALL_USEREXIT); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageA)); + Assert.IsFalse(MsiVerifier.IsPackageInstalled(packageB)); + + this.CleanTestArtifacts = true; + } + } +} diff --git a/test/test.bat b/test/test.bat index d2339b88..bae65f20 100644 --- a/test/test.bat +++ b/test/test.bat @@ -136,6 +136,10 @@ if %VERBOSE% == true ( ) REM -- call mstest and capture the output --- +echo. +echo Backing up dependency registration to Dependencies.hiv +reg.exe save HKLM\Software\Classes\Installer\Dependencies Dependencies.hiv /y > nul 2>&1 + echo. echo Running tests @@ -166,6 +170,11 @@ goto :Cleanup REM -- Test Run Completed Successfully --- :TestRunPassed +echo. +echo Restoring dependency registration +if exist "Dependencies.hiv" reg.exe restore HKLM\Software\Classes\Installer\Dependencies Dependencies.hiv > nul 2>&1 +if errorlevel 0 del /q Dependencies.hiv + echo. echo ------------------------------- echo Test Run Result: Passed @@ -292,4 +301,4 @@ REM -- Cleanup at the end of the script --- REM -- revert directory --- popd -endlocal & Exit /B %EXITCODE% \ No newline at end of file +endlocal & Exit /B %EXITCODE%