@joebigelow / wix-1 / commits / f1c74f3f

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.

Bob Arnson committed Jul 23, 2022 at 00:03 UTC f1c74f3f60a0b1845806a2c6f7082692a8f37b7e
10 files changed +214 -24
src/clean.cmd
+3
@@ -2,7 +2,10 @@
2
3 setlocal
4 pushd %~dp0
5 +
6 set _NUGET_CACHE=%USERPROFILE%\.nuget\packages
7 +if "%NUGET_PACKAGES%" NEQ "" set _NUGET_CACHE=%NUGET_PACKAGES%
8 +
9 echo Cleaning...
10
11 if exist ..\build rd /s/q ..\build
src/ext/Bal/wixstdba/Resources/HyperlinkTheme.wxl
+4 -1
@@ -63,5 +63,8 @@
63 <String Id="FailureRestartText">You must restart your computer to complete the rollback of the software.</String>
64 <String Id="FailureRestartButton">&amp;Restart</String>
65 <String Id="FailureCloseButton">&amp;Close</String>
66 - <String Id="ErrorFailNoActionReboot">No action was taken as a system reboot is required.</String>
66 + <String Id="FilesInUseTitle">Files In Use</String>
67 + <String Id="FilesInUseLabel">The following applications are using files that need to be updated:</String>
68 + <String Id="FilesInUseCloseRadioButton">Close the &amp;applications and attempt to restart them.</String>
69 + <String Id="FilesInUseDontCloseRadioButton">&amp;Do not close applications. A reboot will be required.</String>
70 </WixLocalization>
src/ext/Bal/wixstdba/Resources/RtfTheme.wxl
+4 -1
@@ -60,5 +60,8 @@
60 <String Id="FailureRestartText">You must restart your computer to complete the rollback of the software.</String>
61 <String Id="FailureRestartButton">&amp;Restart</String>
62 <String Id="FailureCloseButton">&amp;Close</String>
63 - <String Id="ErrorFailNoActionReboot">No action was taken as a system reboot is required.</String>
63 + <String Id="FilesInUseTitle">Files In Use</String>
64 + <String Id="FilesInUseLabel">The following applications are using files that need to be updated:</String>
65 + <String Id="FilesInUseCloseRadioButton">Close the &amp;applications and attempt to restart them.</String>
66 + <String Id="FilesInUseDontCloseRadioButton">&amp;Do not close applications. A reboot will be required.</String>
67 </WixLocalization>
src/ext/Bal/wixstdba/Resources/dncpreq.wxl
-1
@@ -30,5 +30,4 @@
30 <String Id="FailureCloseButton">&amp;Close</String>
31 <String Id="SCDRUNTIMEFAILUREErrorMessage">[WixBundleName] cannot run on this machine. Install the latest updates and/or the latest OS to run in a supported environment.</String>
32 <String Id="PREREQBAINFINITELOOPErrorMessage">[WixBundleName] failed to load the .NET Core runtime even though all of the prerequisites are installed.</String>
33 - <String Id="ErrorFailNoActionReboot">No action was taken as a system reboot is required.</String>
33 </WixLocalization>
src/ext/Bal/wixstdba/Resources/mbapreq.wxl
-1
@@ -30,5 +30,4 @@
30 <String Id="FailureCloseButton">&amp;Close</String>
31 <String Id="NET452WIN7RTMErrorMessage">[WixBundleName] cannot run on Windows 7 RTM with .NET 4.5.2 installed. Install Windows 7 SP1 to run in a supported environment.</String>
32 <String Id="PREREQBAINFINITELOOPErrorMessage">[WixBundleName] failed to load the .NET Framework runtime even though all of the prerequisites are installed.</String>
33 - <String Id="ErrorFailNoActionReboot">No action was taken as a system reboot is required.</String>
33 </WixLocalization>
src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp
+155 -19
@@ -867,25 +867,9 @@ public: // IBootstrapperApplication
867 hr = StrAllocFromError(&sczError, dwCode, NULL);
868 if (FAILED(hr) || !sczError || !*sczError)
869 {
870 - // special case for ERROR_FAIL_NOACTION_REBOOT: use loc string for Windows XP
871 - if (ERROR_FAIL_NOACTION_REBOOT == dwCode)
872 - {
873 - LOC_STRING* pLocString = NULL;
874 - hr = LocGetString(m_pWixLoc, L"#(loc.ErrorFailNoActionReboot)", &pLocString);
875 - if (SUCCEEDED(hr))
876 - {
877 - StrAllocString(&sczError, pLocString->wzText, 0);
878 - }
879 - else
880 - {
881 - StrAllocFormatted(&sczError, L"0x%x", dwCode);
882 - }
883 - }
884 - else
885 - {
886 - StrAllocFormatted(&sczError, L"0x%x", dwCode);
887 - }
870 + StrAllocFormatted(&sczError, L"0x%x", dwCode);
871 }
872 +
873 hr = S_OK;
874 }
875
@@ -1053,6 +1037,34 @@ public: // IBootstrapperApplication
1037 }
1038
1039
1040 + virtual STDMETHODIMP OnExecuteFilesInUse(
1041 + __in_z LPCWSTR wzPackageId,
1042 + __in DWORD cFiles,
1043 + __in_ecount_z(cFiles) LPCWSTR* rgwzFiles,
1044 + __in int nRecommendation,
1045 + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source,
1046 + __inout int* pResult
1047 + )
1048 + {
1049 +
1050 + if (!m_fShowingInternalUiThisPackage && !m_fPrereq && wzPackageId && *wzPackageId)
1051 + {
1052 + // If this is an MSI package, display the files-in-use dialog.
1053 + BAL_INFO_PACKAGE* pPackage = NULL;
1054 + BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage);
1055 +
1056 + if (pPackage && BAL_INFO_PACKAGE_TYPE_MSI == pPackage->type)
1057 + {
1058 + BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Package %ls has %d applications holding files in use.", wzPackageId, cFiles);
1059 +
1060 + return ShowFilesInUse(cFiles, rgwzFiles, source);
1061 + }
1062 + }
1063 +
1064 + return __super::OnExecuteFilesInUse(wzPackageId, cFiles, rgwzFiles, nRecommendation, source, pResult);
1065 + }
1066 +
1067 +
1068 virtual STDMETHODIMP OnExecutePackageComplete(
1069 __in_z LPCWSTR wzPackageId,
1070 __in HRESULT hrStatus,
@@ -2201,6 +2213,126 @@ private: // privates
2213 }
2214
2215
2216 + int ShowFilesInUse(
2217 + __in DWORD cFiles,
2218 + __in_ecount_z(cFiles) LPCWSTR* rgwzFiles,
2219 + __in BOOTSTRAPPER_FILES_IN_USE_TYPE /*source*/
2220 + )
2221 + {
2222 + HRESULT hr = S_OK;
2223 + LPWSTR sczFilesInUse = NULL;
2224 + DWORD_PTR cchLen = 0;
2225 + int nResult = IDERROR;
2226 +
2227 + // If the user has choosen to ignore on a previously displayed "files in use" page,
2228 + // we will return the same result for other cases. No need to display the page again.
2229 + if (IDIGNORE == m_nLastFilesInUseResult)
2230 + {
2231 + nResult = m_nLastFilesInUseResult;
2232 + }
2233 + else if (BOOTSTRAPPER_DISPLAY_FULL == m_command.display) // Only show files in use when using full display mode.
2234 + {
2235 + // Show applications using the files.
2236 + if (cFiles > 0)
2237 + {
2238 + // See https://msdn.microsoft.com/en-us/library/aa371614%28v=vs.85%29.aspx for details.
2239 + for (DWORD i = 1; i < cFiles; i += 2)
2240 + {
2241 + hr = ::StringCchLengthW(rgwzFiles[i], STRSAFE_MAX_CCH, reinterpret_cast<UINT_PTR*>(&cchLen));
2242 + BalExitOnFailure(hr, "Failed to calculate length of string");
2243 +
2244 + if (cchLen > 0)
2245 + {
2246 + hr = StrAllocConcat(&sczFilesInUse, rgwzFiles[i], 0);
2247 + BalExitOnFailure(hr, "Failed to concat files in use");
2248 +
2249 + hr = StrAllocConcat(&sczFilesInUse, L"\r\n", 2);
2250 + BalExitOnFailure(hr, "Failed to concat files in use");
2251 + }
2252 + }
2253 + }
2254 +
2255 + hr = ShowFilesInUseDialog(sczFilesInUse, &nResult);
2256 + ExitOnFailure(hr, "Failed to show files-in-use task dialog.");
2257 + }
2258 + else
2259 + {
2260 + // Silent UI level installations always shut down applications and services,
2261 + // and on Windows Vista and later, use Restart Manager unless disabled.
2262 + nResult = IDOK;
2263 + }
2264 +
2265 + LExit:
2266 + ReleaseStr(sczFilesInUse);
2267 +
2268 + // Remember the answer from the user.
2269 + m_nLastFilesInUseResult = FAILED(hr) ? IDERROR : nResult;
2270 +
2271 + return m_nLastFilesInUseResult;
2272 + }
2273 +
2274 +
2275 + int ShowFilesInUseDialog(
2276 + __in_z_opt LPCWSTR sczFilesInUse,
2277 + __out int* pnResult
2278 + )
2279 + {
2280 + HRESULT hr = S_OK;
2281 + TASKDIALOGCONFIG config = { };
2282 + LPWSTR sczTitle = NULL;
2283 + LPWSTR sczLabel = NULL;
2284 + LPWSTR sczCloseRadioButton = NULL;
2285 + LPWSTR sczDontCloseRadioButton = NULL;
2286 + LOC_STRING* pLocString = NULL;
2287 +
2288 + // Get the loc strings for the files-in-use task dialog text.
2289 + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseTitle)", &pLocString);
2290 + ExitOnFailure(hr, "Failed to get FilesInUseTitle loc string.");
2291 +
2292 + hr = StrAllocString(&sczTitle, pLocString->wzText, 0);
2293 + ExitOnFailure(hr, "Failed to copy FilesInUseTitle loc string.");
2294 +
2295 + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseLabel)", &pLocString);
2296 + ExitOnFailure(hr, "Failed to get FilesInUseLabel loc string.");
2297 +
2298 + hr = StrAllocString(&sczLabel, pLocString->wzText, 0);
2299 + ExitOnFailure(hr, "Failed to copy FilesInUseLabel loc string.");
2300 +
2301 + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseCloseRadioButton)", &pLocString);
2302 + ExitOnFailure(hr, "Failed to get FilesInUseCloseRadioButton loc string.");
2303 +
2304 + hr = StrAllocString(&sczCloseRadioButton, pLocString->wzText, 0);
2305 + ExitOnFailure(hr, "Failed to copy FilesInUseCloseRadioButton loc string.");
2306 +
2307 + hr = LocGetString(m_pWixLoc, L"#(loc.FilesInUseDontCloseRadioButton)", &pLocString);
2308 + ExitOnFailure(hr, "Failed to get FilesInUseDontCloseRadioButton loc string.");
2309 +
2310 + hr = StrAllocString(&sczDontCloseRadioButton, pLocString->wzText, 0);
2311 + ExitOnFailure(hr, "Failed to copy FilesInUseDontCloseRadioButton loc string.");
2312 +
2313 + const TASKDIALOG_BUTTON buttons[] = {
2314 + { IDOK, sczCloseRadioButton },
2315 + { IDIGNORE, sczDontCloseRadioButton },
2316 + };
2317 +
2318 + config.cbSize = sizeof(config);
2319 + config.hInstance = m_hModule;
2320 + config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS | TDF_SIZE_TO_CONTENT;
2321 + config.dwCommonButtons = TDCBF_CANCEL_BUTTON;
2322 + config.pszWindowTitle = sczTitle;
2323 + config.pszMainInstruction = sczLabel;
2324 + config.pszContent = sczFilesInUse ? sczFilesInUse : L"";
2325 + config.pButtons = buttons;
2326 + config.cButtons = 2;
2327 +
2328 + hr = TaskDialogIndirect(&config, pnResult, NULL, NULL);
2329 + ExitOnFailure(hr, "Failed to show files-in-use task dialog.");
2330 +
2331 + LExit:
2332 + return hr;
2333 + }
2334 +
2335 +
2336 public: //CBalBaseBootstrapperApplication
2337 virtual STDMETHODIMP Initialize(
2338 __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs
@@ -4135,7 +4267,7 @@ LExit:
4267 if (m_fTaskbarButtonOK)
4268 {
4269 hr = m_pTaskbarList->SetProgressState(m_hWnd, tbpFlags);
4138 - BalExitOnFailure(hr, "Failed to set taskbar button state.", tbpFlags);
4270 + BalExitOnFailure(hr, "Failed to set taskbar button state: %d.", tbpFlags);
4271 }
4272
4273 LExit:
@@ -4286,6 +4418,8 @@ public:
4418 m_fPrereqInstalled = FALSE;
4419 m_fPrereqSkipped = FALSE;
4420
4421 + m_nLastFilesInUseResult = IDNOACTION;
4422 +
4423 pEngine->AddRef();
4424 m_pEngine = pEngine;
4425
@@ -4578,6 +4712,8 @@ private:
4712 BOOL m_fShowingInternalUiThisPackage;
4713 BOOL m_fTriedToLaunchElevated;
4714
4715 + int m_nLastFilesInUseResult;
4716 +
4717 HMODULE m_hBAFModule;
4718 PFN_BA_FUNCTIONS_PROC m_pfnBAFunctionsProc;
4719 LPVOID m_pvBAFunctionsProcContext;
src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wixproj new
+17
@@ -0,0 +1,17 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <OutputType>Bundle</OutputType>
5 + <UpgradeCode>{6A348108-8ACE-4D13-A352-D8F76785BFE4}</UpgradeCode>
6 + <DefineConstants>$(DefineConstants);BaseOutputPath=$(BaseOutputPath);Version=1.1</DefineConstants>
7 + </PropertyGroup>
8 + <ItemGroup>
9 + <Compile Include="..\..\WixStdBaTests\BundleA\Bundle.wxs" Link="Bundle.wxs" />
10 + </ItemGroup>
11 + <ItemGroup>
12 + <ProjectReference Include="..\PackageA\PackageA.wixproj" />
13 + </ItemGroup>
14 + <ItemGroup>
15 + <PackageReference Include="WixToolset.Bal.wixext" />
16 + </ItemGroup>
17 +</Project>
\ No newline at end of file
src/test/burn/TestData/FilesInUseTests/WixStdBaBundle/WixStdBaBundle.wxs new
+10
@@ -0,0 +1,10 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +
4 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
5 + <Fragment>
6 + <PackageGroup Id="BundlePackages">
7 + <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)" />
8 + </PackageGroup>
9 + </Fragment>
10 +</Wix>
src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs
+20
@@ -33,5 +33,25 @@ namespace WixToolsetTest.BurnE2E
33
34 packageA.VerifyInstalled(false);
35 }
36 +
37 + [RuntimeFact]
38 + public void WixStdBAFailsWithLockedFile()
39 + {
40 + var packageA = this.CreatePackageInstaller("PackageA");
41 + var bundleA = this.CreateBundleInstaller("WixStdBaBundle");
42 +
43 + packageA.VerifyInstalled(false);
44 +
45 + bundleA.Install();
46 +
47 + packageA.VerifyInstalled(true);
48 +
49 + // Lock the file that will be uninstalled.
50 + var targetInstallFile = packageA.GetInstalledFilePath("Package.wxs");
51 + using (var lockTargetFile = new FileStream(targetInstallFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
52 + {
53 + bundleA.Uninstall(expectedExitCode: (int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
54 + }
55 + }
56 }
57 }
src/tools/thmviewer/precomp.h
+1 -1
@@ -55,7 +55,7 @@ extern "C" HRESULT DisplayStart(
55 );
56 extern "C" HRESULT LoadStart(
57 __in_z LPCWSTR wzThemePath,
58 - __in_z LPCWSTR wzWxlPath,
58 + __in_z_opt LPCWSTR wzWxlPath,
59 __in HWND hWnd,
60 __out HANDLE* phThread
61 );