From f1c74f3f60a0b1845806a2c6f7082692a8f37b7e Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sat, 23 Jul 2022 00:03:58 -0400 Subject: Add files-in-use task dialog. Remove ErrorFailNoActionReboot loc string, now that XP is dead (RIP). Clean up some extra stuff, because I'm all up in the code. Resolves https://github.com/wixtoolset/issues/issues/6545. --- src/clean.cmd | 3 + src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl | 5 +- src/ext/Bal/wixstdba/Resources/RtfTheme.wxl | 5 +- src/ext/Bal/wixstdba/Resources/dncpreq.wxl | 1 - src/ext/Bal/wixstdba/Resources/mbapreq.wxl | 1 - .../WixStandardBootstrapperApplication.cpp | 174 ++++++++++++++++++--- .../WixStdBaBundle/WixStdBaBundle.wixproj | 17 ++ .../WixStdBaBundle/WixStdBaBundle.wxs | 10 ++ .../burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs | 20 +++ src/tools/thmviewer/precomp.h | 2 +- 10 files changed, 214 insertions(+), 24 deletions(-) create mode 100644 src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wixproj create mode 100644 src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wxs diff --git a/src/clean.cmd b/src/clean.cmd index bd44bed4..9c4fa0dc 100644 --- a/src/clean.cmd +++ b/src/clean.cmd @@ -2,7 +2,10 @@ setlocal pushd %~dp0 + set _NUGET_CACHE=%USERPROFILE%\.nuget\packages +if "%NUGET_PACKAGES%" NEQ "" set _NUGET_CACHE=%NUGET_PACKAGES% + echo Cleaning... if exist ..\build rd /s/q ..\build diff --git a/src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl b/src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl index 43b43970..9fd0be43 100644 --- a/src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl +++ b/src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl @@ -63,5 +63,8 @@ You must restart your computer to complete the rollback of the software. &Restart &Close - No action was taken as a system reboot is required. + Files In Use + The following applications are using files that need to be updated: + Close the &applications and attempt to restart them. + &Do not close applications. A reboot will be required. diff --git a/src/ext/Bal/wixstdba/Resources/RtfTheme.wxl b/src/ext/Bal/wixstdba/Resources/RtfTheme.wxl index 7b8e1c65..4948cd75 100644 --- a/src/ext/Bal/wixstdba/Resources/RtfTheme.wxl +++ b/src/ext/Bal/wixstdba/Resources/RtfTheme.wxl @@ -60,5 +60,8 @@ You must restart your computer to complete the rollback of the software. &Restart &Close - No action was taken as a system reboot is required. + Files In Use + The following applications are using files that need to be updated: + Close the &applications and attempt to restart them. + &Do not close applications. A reboot will be required. diff --git a/src/ext/Bal/wixstdba/Resources/dncpreq.wxl b/src/ext/Bal/wixstdba/Resources/dncpreq.wxl index 22fcd3dc..734de0ed 100644 --- a/src/ext/Bal/wixstdba/Resources/dncpreq.wxl +++ b/src/ext/Bal/wixstdba/Resources/dncpreq.wxl @@ -30,5 +30,4 @@ &Close [WixBundleName] cannot run on this machine. Install the latest updates and/or the latest OS to run in a supported environment. [WixBundleName] failed to load the .NET Core runtime even though all of the prerequisites are installed. - No action was taken as a system reboot is required. diff --git a/src/ext/Bal/wixstdba/Resources/mbapreq.wxl b/src/ext/Bal/wixstdba/Resources/mbapreq.wxl index 77859d31..3fec4254 100644 --- a/src/ext/Bal/wixstdba/Resources/mbapreq.wxl +++ b/src/ext/Bal/wixstdba/Resources/mbapreq.wxl @@ -30,5 +30,4 @@ &Close [WixBundleName] cannot run on Windows 7 RTM with .NET 4.5.2 installed. Install Windows 7 SP1 to run in a supported environment. [WixBundleName] failed to load the .NET Framework runtime even though all of the prerequisites are installed. - No action was taken as a system reboot is required. diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 1af1abeb..bd0f5a3b 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -867,25 +867,9 @@ public: // IBootstrapperApplication hr = StrAllocFromError(&sczError, dwCode, NULL); if (FAILED(hr) || !sczError || !*sczError) { - // special case for ERROR_FAIL_NOACTION_REBOOT: use loc string for Windows XP - if (ERROR_FAIL_NOACTION_REBOOT == dwCode) - { - LOC_STRING* pLocString = NULL; - hr = LocGetString(m_pWixLoc, L"#(loc.ErrorFailNoActionReboot)", &pLocString); - if (SUCCEEDED(hr)) - { - StrAllocString(&sczError, pLocString->wzText, 0); - } - else - { - StrAllocFormatted(&sczError, L"0x%x", dwCode); - } - } - else - { - StrAllocFormatted(&sczError, L"0x%x", dwCode); - } + StrAllocFormatted(&sczError, L"0x%x", dwCode); } + hr = S_OK; } @@ -1053,6 +1037,34 @@ public: // IBootstrapperApplication } + virtual STDMETHODIMP OnExecuteFilesInUse( + __in_z LPCWSTR wzPackageId, + __in DWORD cFiles, + __in_ecount_z(cFiles) LPCWSTR* rgwzFiles, + __in int nRecommendation, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source, + __inout int* pResult + ) + { + + if (!m_fShowingInternalUiThisPackage && !m_fPrereq && wzPackageId && *wzPackageId) + { + // If this is an MSI package, display the files-in-use dialog. + BAL_INFO_PACKAGE* pPackage = NULL; + BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); + + if (pPackage && BAL_INFO_PACKAGE_TYPE_MSI == pPackage->type) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Package %ls has %d applications holding files in use.", wzPackageId, cFiles); + + return ShowFilesInUse(cFiles, rgwzFiles, source); + } + } + + return __super::OnExecuteFilesInUse(wzPackageId, cFiles, rgwzFiles, nRecommendation, source, pResult); + } + + virtual STDMETHODIMP OnExecutePackageComplete( __in_z LPCWSTR wzPackageId, __in HRESULT hrStatus, @@ -2201,6 +2213,126 @@ private: // privates } + int ShowFilesInUse( + __in DWORD cFiles, + __in_ecount_z(cFiles) LPCWSTR* rgwzFiles, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE /*source*/ + ) + { + HRESULT hr = S_OK; + LPWSTR sczFilesInUse = NULL; + DWORD_PTR cchLen = 0; + int nResult = IDERROR; + + // If the user has choosen to ignore on a previously displayed "files in use" page, + // we will return the same result for other cases. No need to display the page again. + if (IDIGNORE == m_nLastFilesInUseResult) + { + nResult = m_nLastFilesInUseResult; + } + else if (BOOTSTRAPPER_DISPLAY_FULL == m_command.display) // Only show files in use when using full display mode. + { + // Show applications using the files. + if (cFiles > 0) + { + // See https://msdn.microsoft.com/en-us/library/aa371614%28v=vs.85%29.aspx for details. + for (DWORD i = 1; i < cFiles; i += 2) + { + hr = ::StringCchLengthW(rgwzFiles[i], STRSAFE_MAX_CCH, reinterpret_cast(&cchLen)); + BalExitOnFailure(hr, "Failed to calculate length of string"); + + if (cchLen > 0) + { + hr = StrAllocConcat(&sczFilesInUse, rgwzFiles[i], 0); + BalExitOnFailure(hr, "Failed to concat files in use"); + + hr = StrAllocConcat(&sczFilesInUse, L"\r\n", 2); + BalExitOnFailure(hr, "Failed to concat files in use"); + } + } + } + + hr = ShowFilesInUseDialog(sczFilesInUse, &nResult); + ExitOnFailure(hr, "Failed to show files-in-use task dialog."); + } + else + { + // Silent UI level installations always shut down applications and services, + // and on Windows Vista and later, use Restart Manager unless disabled. + nResult = IDOK; + } + + LExit: + ReleaseStr(sczFilesInUse); + + // Remember the answer from the user. + m_nLastFilesInUseResult = FAILED(hr) ? IDERROR : nResult; + + return m_nLastFilesInUseResult; + } + + + int ShowFilesInUseDialog( + __in_z_opt LPCWSTR sczFilesInUse, + __out int* pnResult + ) + { + HRESULT hr = S_OK; + TASKDIALOGCONFIG config = { }; + LPWSTR sczTitle = NULL; + LPWSTR sczLabel = NULL; + LPWSTR sczCloseRadioButton = NULL; + LPWSTR sczDontCloseRadioButton = NULL; + LOC_STRING* pLocString = NULL; + + // Get the loc strings for the files-in-use task dialog text. + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseTitle)", &pLocString); + ExitOnFailure(hr, "Failed to get FilesInUseTitle loc string."); + + hr = StrAllocString(&sczTitle, pLocString->wzText, 0); + ExitOnFailure(hr, "Failed to copy FilesInUseTitle loc string."); + + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseLabel)", &pLocString); + ExitOnFailure(hr, "Failed to get FilesInUseLabel loc string."); + + hr = StrAllocString(&sczLabel, pLocString->wzText, 0); + ExitOnFailure(hr, "Failed to copy FilesInUseLabel loc string."); + + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseCloseRadioButton)", &pLocString); + ExitOnFailure(hr, "Failed to get FilesInUseCloseRadioButton loc string."); + + hr = StrAllocString(&sczCloseRadioButton, pLocString->wzText, 0); + ExitOnFailure(hr, "Failed to copy FilesInUseCloseRadioButton loc string."); + + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseDontCloseRadioButton)", &pLocString); + ExitOnFailure(hr, "Failed to get FilesInUseDontCloseRadioButton loc string."); + + hr = StrAllocString(&sczDontCloseRadioButton, pLocString->wzText, 0); + ExitOnFailure(hr, "Failed to copy FilesInUseDontCloseRadioButton loc string."); + + const TASKDIALOG_BUTTON buttons[] = { + { IDOK, sczCloseRadioButton }, + { IDIGNORE, sczDontCloseRadioButton }, + }; + + config.cbSize = sizeof(config); + config.hInstance = m_hModule; + config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS | TDF_SIZE_TO_CONTENT; + config.dwCommonButtons = TDCBF_CANCEL_BUTTON; + config.pszWindowTitle = sczTitle; + config.pszMainInstruction = sczLabel; + config.pszContent = sczFilesInUse ? sczFilesInUse : L""; + config.pButtons = buttons; + config.cButtons = 2; + + hr = TaskDialogIndirect(&config, pnResult, NULL, NULL); + ExitOnFailure(hr, "Failed to show files-in-use task dialog."); + + LExit: + return hr; + } + + public: //CBalBaseBootstrapperApplication virtual STDMETHODIMP Initialize( __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs @@ -4135,7 +4267,7 @@ LExit: if (m_fTaskbarButtonOK) { hr = m_pTaskbarList->SetProgressState(m_hWnd, tbpFlags); - BalExitOnFailure(hr, "Failed to set taskbar button state.", tbpFlags); + BalExitOnFailure(hr, "Failed to set taskbar button state: %d.", tbpFlags); } LExit: @@ -4286,6 +4418,8 @@ public: m_fPrereqInstalled = FALSE; m_fPrereqSkipped = FALSE; + m_nLastFilesInUseResult = IDNOACTION; + pEngine->AddRef(); m_pEngine = pEngine; @@ -4578,6 +4712,8 @@ private: BOOL m_fShowingInternalUiThisPackage; BOOL m_fTriedToLaunchElevated; + int m_nLastFilesInUseResult; + HMODULE m_hBAFModule; PFN_BA_FUNCTIONS_PROC m_pfnBAFunctionsProc; LPVOID m_pvBAFunctionsProcContext; diff --git a/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wixproj b/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wixproj new file mode 100644 index 00000000..e0860665 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wixproj @@ -0,0 +1,17 @@ + + + + Bundle + {6A348108-8ACE-4D13-A352-D8F76785BFE4} + $(DefineConstants);BaseOutputPath=$(BaseOutputPath);Version=1.1 + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wxs b/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wxs new file mode 100644 index 00000000..bd164a29 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs index 042175f0..12eca77d 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs @@ -33,5 +33,25 @@ namespace WixToolsetTest.BurnE2E packageA.VerifyInstalled(false); } + + [RuntimeFact] + public void WixStdBAFailsWithLockedFile() + { + var packageA = this.CreatePackageInstaller("PackageA"); + var bundleA = this.CreateBundleInstaller("WixStdBaBundle"); + + packageA.VerifyInstalled(false); + + bundleA.Install(); + + packageA.VerifyInstalled(true); + + // Lock the file that will be uninstalled. + var targetInstallFile = packageA.GetInstalledFilePath("Package.wxs"); + using (var lockTargetFile = new FileStream(targetInstallFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) + { + bundleA.Uninstall(expectedExitCode: (int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE); + } + } } } diff --git a/src/tools/thmviewer/precomp.h b/src/tools/thmviewer/precomp.h index 762a0623..41d2e9fb 100644 --- a/src/tools/thmviewer/precomp.h +++ b/src/tools/thmviewer/precomp.h @@ -55,7 +55,7 @@ extern "C" HRESULT DisplayStart( ); extern "C" HRESULT LoadStart( __in_z LPCWSTR wzThemePath, - __in_z LPCWSTR wzWxlPath, + __in_z_opt LPCWSTR wzWxlPath, __in HWND hWnd, __out HANDLE* phThread ); -- cgit v1.2.3-55-g6feb