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">&Restart</String>
65
<String Id="FailureCloseButton">&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 &applications and attempt to restart them.</String>
69
+ <String Id="FilesInUseDontCloseRadioButton">&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">&Restart</String>
62
<String Id="FailureCloseButton">&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 &applications and attempt to restart them.</String>
66
+ <String Id="FilesInUseDontCloseRadioButton">&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">&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">&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
);