1 /* 2 * Copyright (C) 2002 Andreas Mohr 3 * Copyright (C) 2002 Shachar Shemesh 4 * Copyright (C) 2013 Edijs Kolesnikovics 5 * Copyright (C) 2018 Katayama Hirofumi MZ 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22 /* Based on the Wine "bootup" handler application 23 * 24 * This app handles the various "hooks" windows allows for applications to perform 25 * as part of the bootstrap process. Theses are roughly devided into three types. 26 * Knowledge base articles that explain this are 137367, 179365, 232487 and 232509. 27 * Also, 119941 has some info on grpconv.exe 28 * The operations performed are (by order of execution): 29 * 30 * After log in 31 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx (synch, no imp) 32 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce (synch) 33 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run (asynch) 34 * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run (asynch) 35 * - All users Startup folder "%ALLUSERSPROFILE%\Start Menu\Programs\Startup" (asynch, no imp) 36 * - Current user Startup folder "%USERPROFILE%\Start Menu\Programs\Startup" (asynch, no imp) 37 * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce (asynch) 38 * 39 * None is processed in Safe Mode // FIXME: Check RunOnceEx in Safe Mode 40 */ 41 42 #include "precomp.h" 43 44 // For the auto startup process 45 static HANDLE s_hStartupMutex = NULL; 46 47 #define INVALID_RUNCMD_RETURN -1 48 /** 49 * This function runs the specified command in the specified dir. 50 * [in,out] cmdline - the command line to run. The function may change the passed buffer. 51 * [in] dir - the dir to run the command in. If it is NULL, then the current dir is used. 52 * [in] wait - whether to wait for the run program to finish before returning. 53 * [in] minimized - Whether to ask the program to run minimized. 54 * 55 * Returns: 56 * If running the process failed, returns INVALID_RUNCMD_RETURN. Use GetLastError to get the error code. 57 * If wait is FALSE - returns 0 if successful. 58 * If wait is TRUE - returns the program's return value. 59 */ 60 static int runCmd(LPWSTR cmdline, LPCWSTR dir, BOOL wait, BOOL minimized) 61 { 62 STARTUPINFOW si; 63 PROCESS_INFORMATION info; 64 DWORD exit_code = 0; 65 66 memset(&si, 0, sizeof(si)); 67 si.cb = sizeof(si); 68 if (minimized) 69 { 70 si.dwFlags = STARTF_USESHOWWINDOW; 71 si.wShowWindow = SW_MINIMIZE; 72 } 73 memset(&info, 0, sizeof(info)); 74 75 if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, dir, &si, &info)) 76 { 77 TRACE("Failed to run command (%lu)\n", GetLastError()); 78 79 return INVALID_RUNCMD_RETURN; 80 } 81 82 TRACE("Successfully ran command\n"); 83 84 if (wait) 85 { 86 HANDLE Handles[] = { info.hProcess }; 87 DWORD nCount = _countof(Handles); 88 DWORD dwWait; 89 MSG msg; 90 91 /* wait for the process to exit */ 92 for (;;) 93 { 94 /* We need to keep processing messages, 95 otherwise we will hang anything that is trying to send a message to us */ 96 dwWait = MsgWaitForMultipleObjects(nCount, Handles, FALSE, INFINITE, QS_ALLINPUT); 97 98 /* WAIT_OBJECT_0 + nCount signals an event in the message queue, 99 so anything other than that means we are done. */ 100 if (dwWait != WAIT_OBJECT_0 + nCount) 101 { 102 if (dwWait >= WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + nCount) 103 TRACE("Event %u signaled\n", dwWait - WAIT_OBJECT_0); 104 else 105 WARN("Return code: %u\n", dwWait); 106 break; 107 } 108 109 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 110 { 111 TranslateMessage(&msg); 112 DispatchMessageW(&msg); 113 } 114 } 115 116 GetExitCodeProcess(info.hProcess, &exit_code); 117 } 118 119 CloseHandle(info.hThread); 120 CloseHandle(info.hProcess); 121 122 return exit_code; 123 } 124 125 126 /** 127 * Process a "Run" type registry key. 128 * hkRoot is the HKEY from which "Software\Microsoft\Windows\CurrentVersion" is 129 * opened. 130 * szKeyName is the key holding the actual entries. 131 * bDelete tells whether we should delete each value right before executing it. 132 * bSynchronous tells whether we should wait for the prog to complete before 133 * going on to the next prog. 134 */ 135 static BOOL ProcessRunKeys(HKEY hkRoot, LPCWSTR szKeyName, BOOL bDelete, 136 BOOL bSynchronous) 137 { 138 HKEY hkWin = NULL, hkRun = NULL; 139 LONG res = ERROR_SUCCESS; 140 DWORD i, cbMaxCmdLine = 0, cchMaxValue = 0; 141 WCHAR *szCmdLine = NULL; 142 WCHAR *szValue = NULL; 143 144 if (hkRoot == HKEY_LOCAL_MACHINE) 145 TRACE("processing %ls entries under HKLM\n", szKeyName); 146 else 147 TRACE("processing %ls entries under HKCU\n", szKeyName); 148 149 res = RegOpenKeyExW(hkRoot, 150 L"Software\\Microsoft\\Windows\\CurrentVersion", 151 0, 152 KEY_READ, 153 &hkWin); 154 if (res != ERROR_SUCCESS) 155 { 156 TRACE("RegOpenKeyW failed on Software\\Microsoft\\Windows\\CurrentVersion (%ld)\n", res); 157 158 goto end; 159 } 160 161 res = RegOpenKeyExW(hkWin, 162 szKeyName, 163 0, 164 bDelete ? KEY_ALL_ACCESS : KEY_READ, 165 &hkRun); 166 if (res != ERROR_SUCCESS) 167 { 168 if (res == ERROR_FILE_NOT_FOUND) 169 { 170 TRACE("Key doesn't exist - nothing to be done\n"); 171 172 res = ERROR_SUCCESS; 173 } 174 else 175 TRACE("RegOpenKeyExW failed on run key (%ld)\n", res); 176 177 goto end; 178 } 179 180 res = RegQueryInfoKeyW(hkRun, 181 NULL, 182 NULL, 183 NULL, 184 NULL, 185 NULL, 186 NULL, 187 &i, 188 &cchMaxValue, 189 &cbMaxCmdLine, 190 NULL, 191 NULL); 192 if (res != ERROR_SUCCESS) 193 { 194 TRACE("Couldn't query key info (%ld)\n", res); 195 196 goto end; 197 } 198 199 if (i == 0) 200 { 201 TRACE("No commands to execute.\n"); 202 203 res = ERROR_SUCCESS; 204 goto end; 205 } 206 207 szCmdLine = (WCHAR*)HeapAlloc(hProcessHeap, 208 0, 209 cbMaxCmdLine); 210 if (szCmdLine == NULL) 211 { 212 TRACE("Couldn't allocate memory for the commands to be executed\n"); 213 214 res = ERROR_NOT_ENOUGH_MEMORY; 215 goto end; 216 } 217 218 ++cchMaxValue; 219 szValue = (WCHAR*)HeapAlloc(hProcessHeap, 220 0, 221 cchMaxValue * sizeof(*szValue)); 222 if (szValue == NULL) 223 { 224 TRACE("Couldn't allocate memory for the value names\n"); 225 226 res = ERROR_NOT_ENOUGH_MEMORY; 227 goto end; 228 } 229 230 while (i > 0) 231 { 232 WCHAR *szCmdLineExp = NULL; 233 DWORD cchValLength = cchMaxValue, cbDataLength = cbMaxCmdLine; 234 DWORD type; 235 236 --i; 237 238 res = RegEnumValueW(hkRun, 239 i, 240 szValue, 241 &cchValLength, 242 0, 243 &type, 244 (PBYTE)szCmdLine, 245 &cbDataLength); 246 if (res != ERROR_SUCCESS) 247 { 248 TRACE("Couldn't read in value %lu - %ld\n", i, res); 249 250 continue; 251 } 252 253 /* safe mode - force to run if prefixed with asterisk */ 254 if (GetSystemMetrics(SM_CLEANBOOT) && (szValue[0] != L'*')) continue; 255 256 if (bDelete && (res = RegDeleteValueW(hkRun, szValue)) != ERROR_SUCCESS) 257 { 258 TRACE("Couldn't delete value - %lu, %ld. Running command anyways.\n", i, res); 259 } 260 261 if (type != REG_SZ && type != REG_EXPAND_SZ) 262 { 263 TRACE("Incorrect type of value #%lu (%lu)\n", i, type); 264 265 continue; 266 } 267 268 if (type == REG_EXPAND_SZ) 269 { 270 DWORD dwNumOfChars; 271 272 dwNumOfChars = ExpandEnvironmentStringsW(szCmdLine, NULL, 0); 273 if (dwNumOfChars) 274 { 275 szCmdLineExp = (WCHAR *)HeapAlloc(hProcessHeap, 0, dwNumOfChars * sizeof(*szCmdLineExp)); 276 277 if (szCmdLineExp == NULL) 278 { 279 TRACE("Couldn't allocate memory for the commands to be executed\n"); 280 281 res = ERROR_NOT_ENOUGH_MEMORY; 282 goto end; 283 } 284 285 ExpandEnvironmentStringsW(szCmdLine, szCmdLineExp, dwNumOfChars); 286 } 287 } 288 289 res = runCmd(szCmdLineExp ? szCmdLineExp : szCmdLine, NULL, bSynchronous, FALSE); 290 if (res == INVALID_RUNCMD_RETURN) 291 { 292 TRACE("Error running cmd #%lu (%lu)\n", i, GetLastError()); 293 } 294 295 if (szCmdLineExp != NULL) 296 { 297 HeapFree(hProcessHeap, 0, szCmdLineExp); 298 szCmdLineExp = NULL; 299 } 300 301 TRACE("Done processing cmd #%lu\n", i); 302 } 303 304 res = ERROR_SUCCESS; 305 end: 306 if (szValue != NULL) 307 HeapFree(hProcessHeap, 0, szValue); 308 if (szCmdLine != NULL) 309 HeapFree(hProcessHeap, 0, szCmdLine); 310 if (hkRun != NULL) 311 RegCloseKey(hkRun); 312 if (hkWin != NULL) 313 RegCloseKey(hkWin); 314 315 TRACE("done\n"); 316 317 return res == ERROR_SUCCESS ? TRUE : FALSE; 318 } 319 320 static BOOL 321 AutoStartupApplications(INT nCSIDL_Folder) 322 { 323 WCHAR szPath[MAX_PATH] = { 0 }; 324 HRESULT hResult; 325 HANDLE hFind; 326 WIN32_FIND_DATAW FoundData; 327 size_t cchPathLen; 328 329 TRACE("(%d)\n", nCSIDL_Folder); 330 331 // Get the special folder path 332 hResult = SHGetFolderPathW(NULL, nCSIDL_Folder, NULL, SHGFP_TYPE_CURRENT, szPath); 333 cchPathLen = wcslen(szPath); 334 if (!SUCCEEDED(hResult) || cchPathLen == 0) 335 { 336 WARN("SHGetFolderPath() failed with error %lu\n", GetLastError()); 337 return FALSE; 338 } 339 340 // Build a path with wildcard 341 StringCbCatW(szPath, sizeof(szPath), L"\\*"); 342 343 // Start enumeration of files 344 hFind = FindFirstFileW(szPath, &FoundData); 345 if (hFind == INVALID_HANDLE_VALUE) 346 { 347 WARN("FindFirstFile(%s) failed with error %lu\n", debugstr_w(szPath), GetLastError()); 348 return FALSE; 349 } 350 351 // Enumerate the files 352 do 353 { 354 // Ignore "." and ".." 355 if (wcscmp(FoundData.cFileName, L".") == 0 || 356 wcscmp(FoundData.cFileName, L"..") == 0) 357 { 358 continue; 359 } 360 361 // Don't run hidden files 362 if (FoundData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) 363 continue; 364 365 // Build the path 366 szPath[cchPathLen + 1] = UNICODE_NULL; 367 StringCbCatW(szPath, sizeof(szPath), FoundData.cFileName); 368 369 TRACE("Executing %s in directory %s\n", debugstr_w(FoundData.cFileName), debugstr_w(szPath)); 370 371 DWORD dwType; 372 if (GetBinaryTypeW(szPath, &dwType)) 373 { 374 runCmd(szPath, NULL, TRUE, FALSE); 375 } 376 else 377 { 378 SHELLEXECUTEINFOW ExecInfo; 379 ZeroMemory(&ExecInfo, sizeof(ExecInfo)); 380 ExecInfo.cbSize = sizeof(ExecInfo); 381 ExecInfo.lpFile = szPath; 382 ShellExecuteExW(&ExecInfo); 383 } 384 } while (FindNextFileW(hFind, &FoundData)); 385 386 FindClose(hFind); 387 return TRUE; 388 } 389 390 INT ProcessStartupItems(VOID) 391 { 392 /* TODO: ProcessRunKeys already checks SM_CLEANBOOT -- items prefixed with * should probably run even in safe mode */ 393 BOOL bNormalBoot = GetSystemMetrics(SM_CLEANBOOT) == 0; /* Perform the operations that are performed every boot */ 394 /* First, set the current directory to SystemRoot */ 395 WCHAR gen_path[MAX_PATH]; 396 DWORD res; 397 398 res = GetWindowsDirectoryW(gen_path, _countof(gen_path)); 399 if (res == 0) 400 { 401 TRACE("Couldn't get the windows directory - error %lu\n", GetLastError()); 402 403 return 100; 404 } 405 406 if (!SetCurrentDirectoryW(gen_path)) 407 { 408 TRACE("Cannot set the dir to %ls (%lu)\n", gen_path, GetLastError()); 409 410 return 100; 411 } 412 413 /* Perform the operations by order checking if policy allows it, checking if this is not Safe Mode, 414 * stopping if one fails, skipping if necessary. 415 */ 416 res = TRUE; 417 /* TODO: RunOnceEx */ 418 419 if (res && (SHRestricted(REST_NOLOCALMACHINERUNONCE) == 0)) 420 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"RunOnce", TRUE, TRUE); 421 422 if (res && bNormalBoot && (SHRestricted(REST_NOLOCALMACHINERUN) == 0)) 423 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"Run", FALSE, FALSE); 424 425 if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0)) 426 res = ProcessRunKeys(HKEY_CURRENT_USER, L"Run", FALSE, FALSE); 427 428 /* All users Startup folder */ 429 AutoStartupApplications(CSIDL_COMMON_STARTUP); 430 431 /* Current user Startup folder */ 432 AutoStartupApplications(CSIDL_STARTUP); 433 434 /* TODO: HKCU\RunOnce runs even if StartupHasBeenRun exists */ 435 if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0)) 436 res = ProcessRunKeys(HKEY_CURRENT_USER, L"RunOnce", TRUE, FALSE); 437 438 TRACE("Operation done\n"); 439 440 return res ? 0 : 101; 441 } 442 443 BOOL DoFinishStartupItems(VOID) 444 { 445 if (s_hStartupMutex) 446 { 447 ReleaseMutex(s_hStartupMutex); 448 CloseHandle(s_hStartupMutex); 449 s_hStartupMutex = NULL; 450 } 451 return TRUE; 452 } 453 454 BOOL DoStartStartupItems(ITrayWindow *Tray) 455 { 456 DWORD dwWait; 457 458 if (!bExplorerIsShell) 459 return FALSE; 460 461 if (!s_hStartupMutex) 462 { 463 // Accidentally, there is possibility that the system starts multiple Explorers 464 // before startup of shell. We use a mutex to match the timing of shell initialization. 465 s_hStartupMutex = CreateMutexW(NULL, FALSE, L"ExplorerIsShellMutex"); 466 if (s_hStartupMutex == NULL) 467 return FALSE; 468 } 469 470 dwWait = WaitForSingleObject(s_hStartupMutex, INFINITE); 471 TRACE("dwWait: 0x%08lX\n", dwWait); 472 if (dwWait != WAIT_OBJECT_0) 473 { 474 TRACE("LastError: %ld\n", GetLastError()); 475 476 DoFinishStartupItems(); 477 return FALSE; 478 } 479 480 const DWORD dwWaitTotal = 3000; // in milliseconds 481 DWORD dwTick = GetTickCount(); 482 while (GetShellWindow() == NULL && GetTickCount() - dwTick < dwWaitTotal) 483 { 484 TrayProcessMessages(Tray); 485 } 486 487 if (GetShellWindow() == NULL) 488 { 489 DoFinishStartupItems(); 490 return FALSE; 491 } 492 493 // Check the volatile "StartupHasBeenRun" key 494 HKEY hSessionKey, hKey; 495 HRESULT hr = SHCreateSessionKey(KEY_WRITE, &hSessionKey); 496 if (SUCCEEDED(hr)) 497 { 498 ASSERT(hSessionKey); 499 500 DWORD dwDisp; 501 LONG Error = RegCreateKeyExW(hSessionKey, L"StartupHasBeenRun", 0, NULL, 502 REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisp); 503 RegCloseKey(hSessionKey); 504 RegCloseKey(hKey); 505 if (Error == ERROR_SUCCESS && dwDisp == REG_OPENED_EXISTING_KEY) 506 { 507 return FALSE; // Startup programs has already been run 508 } 509 } 510 511 return TRUE; 512 } 513