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