| 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 | #include "precomp.h" |
| 4 | |
| 5 | #define WIXCA_UITHREAD_CLASS_WINDOW L"WixCaMessageWindow" |
| 6 | |
| 7 | extern HMODULE g_hInstCADLL; |
| 8 | |
| 9 | |
| 10 | // structs |
| 11 | |
| 12 | struct UITHREAD_CONTEXT |
| 13 | { |
| 14 | HANDLE hInitializedEvent; |
| 15 | HINSTANCE hInstance; |
| 16 | HWND hWnd; |
| 17 | }; |
| 18 | |
| 19 | |
| 20 | // internal function declarations |
| 21 | |
| 22 | static HRESULT CreateMessageWindow( |
| 23 | __out HWND* phWnd |
| 24 | ); |
| 25 | |
| 26 | static void CloseMessageWindow( |
| 27 | __in HWND hWnd |
| 28 | ); |
| 29 | |
| 30 | static DWORD WINAPI ThreadProc( |
| 31 | __in LPVOID pvContext |
| 32 | ); |
| 33 | |
| 34 | static LRESULT CALLBACK WndProc( |
| 35 | __in HWND hWnd, |
| 36 | __in UINT uMsg, |
| 37 | __in WPARAM wParam, |
| 38 | __in LPARAM lParam |
| 39 | ); |
| 40 | |
| 41 | |
| 42 | /****************************************************************** |
| 43 | WixFailWhenDeferred - entry point for WixFailWhenDeferred |
| 44 | custom action which always fails when running as a deferred |
| 45 | custom action (otherwise it blindly succeeds). It's useful when |
| 46 | testing the rollback of deferred custom actions: Schedule it |
| 47 | immediately after the rollback/deferred CA pair you're testing |
| 48 | and it will fail, causing your rollback CA to get invoked. |
| 49 | ********************************************************************/ |
| 50 | extern "C" UINT __stdcall WixFailWhenDeferred( |
| 51 | __in MSIHANDLE hInstall |
| 52 | ) |
| 53 | { |
| 54 | return ::MsiGetMode(hInstall, MSIRUNMODE_SCHEDULED) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS; |
| 55 | } |
| 56 | |
| 57 | /****************************************************************** |
| 58 | WixWaitForEvent - entry point for WixWaitForEvent custom action |
| 59 | which waits for either the WixWaitForEventFail or |
| 60 | WixWaitForEventSucceed named auto reset events. Signaling the |
| 61 | WixWaitForEventFail event will return ERROR_INSTALL_FAILURE or |
| 62 | signaling the WixWaitForEventSucceed event will return |
| 63 | ERROR_SUCCESS. Both events are declared in the Global\ namespace. |
| 64 | ********************************************************************/ |
| 65 | extern "C" UINT __stdcall WixWaitForEvent( |
| 66 | __in MSIHANDLE hInstall |
| 67 | ) |
| 68 | { |
| 69 | HRESULT hr = S_OK; |
| 70 | UINT er = ERROR_SUCCESS; |
| 71 | HWND hMessageWindow = NULL; |
| 72 | LPCWSTR wzSDDL = L"D:(A;;GA;;;WD)"; |
| 73 | OS_VERSION version = OS_VERSION_UNKNOWN; |
| 74 | DWORD dwServicePack = 0; |
| 75 | PSECURITY_DESCRIPTOR pSD = NULL; |
| 76 | SECURITY_ATTRIBUTES sa = { }; |
| 77 | HANDLE rghEvents[2]; |
| 78 | |
| 79 | hr = WcaInitialize(hInstall, "WixWaitForEvent"); |
| 80 | ExitOnFailure(hr, "Failed to initialize."); |
| 81 | |
| 82 | // Create a window to prevent shutdown requests. |
| 83 | hr = CreateMessageWindow(&hMessageWindow); |
| 84 | ExitOnFailure(hr, "Failed to create message window."); |
| 85 | |
| 86 | // If running on Vista/2008 or newer use integrity enhancements. |
| 87 | OsGetVersion(&version, &dwServicePack); |
| 88 | if (OS_VERSION_VISTA <= version) |
| 89 | { |
| 90 | // Add SACL to allow Everyone to signal from a medium integrity level. |
| 91 | wzSDDL = L"D:(A;;GA;;;WD)S:(ML;;NW;;;ME)"; |
| 92 | } |
| 93 | |
| 94 | // Create the security descriptor and attributes for the events. |
| 95 | if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(wzSDDL, SDDL_REVISION_1, &pSD, NULL)) |
| 96 | { |
| 97 | ExitWithLastError(hr, "Failed to create the security descriptor for the events."); |
| 98 | } |
| 99 | |
| 100 | sa.nLength = sizeof(sa); |
| 101 | sa.lpSecurityDescriptor = pSD; |
| 102 | sa.bInheritHandle = FALSE; |
| 103 | |
| 104 | rghEvents[0] = ::CreateEventW(&sa, FALSE, FALSE, L"Global\\WixWaitForEventFail"); |
| 105 | ExitOnNullWithLastError(rghEvents[0], hr, "Failed to create the Global\\WixWaitForEventFail event."); |
| 106 | |
| 107 | rghEvents[1] = ::CreateEventW(&sa, FALSE, FALSE, L"Global\\WixWaitForEventSucceed"); |
| 108 | ExitOnNullWithLastError(rghEvents[1], hr, "Failed to create the Global\\WixWaitForEventSucceed event."); |
| 109 | |
| 110 | // Wait for either of the events to be signaled and handle accordingly. |
| 111 | er = ::WaitForMultipleObjects(countof(rghEvents), rghEvents, FALSE, INFINITE); |
| 112 | switch (er) |
| 113 | { |
| 114 | case WAIT_OBJECT_0 + 0: |
| 115 | er = ERROR_INSTALL_FAILURE; |
| 116 | break; |
| 117 | case WAIT_OBJECT_0 + 1: |
| 118 | er = ERROR_SUCCESS; |
| 119 | break; |
| 120 | default: |
| 121 | ExitOnWin32Error(er, hr, "Unexpected failure."); |
| 122 | } |
| 123 | |
| 124 | LExit: |
| 125 | ReleaseHandle(rghEvents[1]); |
| 126 | ReleaseHandle(rghEvents[0]); |
| 127 | |
| 128 | if (pSD) |
| 129 | { |
| 130 | ::LocalFree(pSD); |
| 131 | } |
| 132 | |
| 133 | if (hMessageWindow) |
| 134 | { |
| 135 | CloseMessageWindow(hMessageWindow); |
| 136 | } |
| 137 | |
| 138 | if (FAILED(hr)) |
| 139 | { |
| 140 | er = ERROR_INSTALL_FAILURE; |
| 141 | } |
| 142 | |
| 143 | return WcaFinalize(er); |
| 144 | } |
| 145 | |
| 146 | |
| 147 | // internal function definitions |
| 148 | |
| 149 | static HRESULT CreateMessageWindow( |
| 150 | __out HWND* phWnd |
| 151 | ) |
| 152 | { |
| 153 | HRESULT hr = S_OK; |
| 154 | HANDLE rgWaitHandles[2] = { }; |
| 155 | UITHREAD_CONTEXT context = { }; |
| 156 | |
| 157 | // Create event to signal after the UI thread / window is initialized. |
| 158 | rgWaitHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL); |
| 159 | ExitOnNullWithLastError(rgWaitHandles[0], hr, "Failed to create initialization event."); |
| 160 | |
| 161 | // Pass necessary information to create the window. |
| 162 | context.hInitializedEvent = rgWaitHandles[0]; |
| 163 | context.hInstance = (HINSTANCE)g_hInstCADLL; |
| 164 | |
| 165 | // Create our separate UI thread. |
| 166 | rgWaitHandles[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, NULL); |
| 167 | ExitOnNullWithLastError(rgWaitHandles[1], hr, "Failed to create the UI thread."); |
| 168 | |
| 169 | // Wait for either the thread to be initialized or the window to exit / fail prematurely. |
| 170 | ::WaitForMultipleObjects(countof(rgWaitHandles), rgWaitHandles, FALSE, INFINITE); |
| 171 | |
| 172 | // Pass the window back to the caller. |
| 173 | *phWnd = context.hWnd; |
| 174 | |
| 175 | LExit: |
| 176 | ReleaseHandle(rgWaitHandles[1]); |
| 177 | ReleaseHandle(rgWaitHandles[0]); |
| 178 | |
| 179 | return hr; |
| 180 | } |
| 181 | |
| 182 | static void CloseMessageWindow( |
| 183 | __in HWND hWnd |
| 184 | ) |
| 185 | { |
| 186 | if (::IsWindow(hWnd)) |
| 187 | { |
| 188 | ::PostMessageW(hWnd, WM_CLOSE, 0, 0); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | static DWORD WINAPI ThreadProc( |
| 193 | __in LPVOID pvContext |
| 194 | ) |
| 195 | { |
| 196 | HRESULT hr = S_OK; |
| 197 | UITHREAD_CONTEXT* pContext = static_cast<UITHREAD_CONTEXT*>(pvContext); |
| 198 | |
| 199 | WNDCLASSW wc = { }; |
| 200 | BOOL fRegistered = TRUE; |
| 201 | HWND hWnd = NULL; |
| 202 | |
| 203 | BOOL fRet = FALSE; |
| 204 | MSG msg = { }; |
| 205 | |
| 206 | wc.lpfnWndProc = WndProc; |
| 207 | wc.hInstance = pContext->hInstance; |
| 208 | wc.lpszClassName = WIXCA_UITHREAD_CLASS_WINDOW; |
| 209 | |
| 210 | if (!::RegisterClassW(&wc)) |
| 211 | { |
| 212 | ExitWithLastError(hr, "Failed to register window."); |
| 213 | } |
| 214 | |
| 215 | fRegistered = TRUE; |
| 216 | |
| 217 | // Create the window to handle reboots without activating it. |
| 218 | hWnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, wc.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, CW_USEDEFAULT, SW_SHOWNA, 0, 0, HWND_DESKTOP, NULL, pContext->hInstance, NULL); |
| 219 | ExitOnNullWithLastError(hWnd, hr, "Failed to create test UI thread window."); |
| 220 | |
| 221 | // Persist the window handle and let the caller know we've initialized. |
| 222 | pContext->hWnd = hWnd; |
| 223 | ::SetEvent(pContext->hInitializedEvent); |
| 224 | |
| 225 | // Pump messages until the window is closed. |
| 226 | while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) |
| 227 | { |
| 228 | if (-1 == fRet) |
| 229 | { |
| 230 | hr = E_UNEXPECTED; |
| 231 | ExitOnFailure(hr, "Unexpected return value from message pump."); |
| 232 | } |
| 233 | else if (!::IsDialogMessageW(msg.hwnd, &msg)) |
| 234 | { |
| 235 | ::TranslateMessage(&msg); |
| 236 | ::DispatchMessageW(&msg); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | LExit: |
| 241 | if (fRegistered) |
| 242 | { |
| 243 | ::UnregisterClassW(WIXCA_UITHREAD_CLASS_WINDOW, pContext->hInstance); |
| 244 | } |
| 245 | |
| 246 | return hr; |
| 247 | } |
| 248 | |
| 249 | static LRESULT CALLBACK WndProc( |
| 250 | __in HWND hWnd, |
| 251 | __in UINT uMsg, |
| 252 | __in WPARAM wParam, |
| 253 | __in LPARAM lParam |
| 254 | ) |
| 255 | { |
| 256 | switch (uMsg) |
| 257 | { |
| 258 | case WM_QUERYENDSESSION: |
| 259 | // Prevent the process from being shut down. |
| 260 | WcaLog(LOGMSG_VERBOSE, "Disallowed system request to shut down the custom action server."); |
| 261 | return FALSE; |
| 262 | |
| 263 | case WM_DESTROY: |
| 264 | ::PostQuitMessage(0); |
| 265 | return 0; |
| 266 | } |
| 267 | |
| 268 | return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); |
| 269 | } |