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 WCHAR szCmdLineExp[MAX_PATH+1] = L"\0"; 66 67 ExpandEnvironmentStringsW(cmdline, szCmdLineExp, _countof(szCmdLineExp)); 68 69 memset(&si, 0, sizeof(si)); 70 si.cb = sizeof(si); 71 if (minimized) 72 { 73 si.dwFlags = STARTF_USESHOWWINDOW; 74 si.wShowWindow = SW_MINIMIZE; 75 } 76 memset(&info, 0, sizeof(info)); 77 78 if (!CreateProcessW(NULL, szCmdLineExp, NULL, NULL, FALSE, 0, NULL, dir, &si, &info)) 79 { 80 TRACE("Failed to run command (%lu)\n", GetLastError()); 81 82 return INVALID_RUNCMD_RETURN; 83 } 84 85 TRACE("Successfully ran command\n"); 86 87 if (wait) 88 { /* wait for the process to exit */ 89 WaitForSingleObject(info.hProcess, INFINITE); 90 GetExitCodeProcess(info.hProcess, &exit_code); 91 } 92 93 CloseHandle(info.hThread); 94 CloseHandle(info.hProcess); 95 96 return exit_code; 97 } 98 99 100 /** 101 * Process a "Run" type registry key. 102 * hkRoot is the HKEY from which "Software\Microsoft\Windows\CurrentVersion" is 103 * opened. 104 * szKeyName is the key holding the actual entries. 105 * bDelete tells whether we should delete each value right before executing it. 106 * bSynchronous tells whether we should wait for the prog to complete before 107 * going on to the next prog. 108 */ 109 static BOOL ProcessRunKeys(HKEY hkRoot, LPCWSTR szKeyName, BOOL bDelete, 110 BOOL bSynchronous) 111 { 112 HKEY hkWin = NULL, hkRun = NULL; 113 LONG res = ERROR_SUCCESS; 114 DWORD i, cbMaxCmdLine = 0, cchMaxValue = 0; 115 WCHAR *szCmdLine = NULL; 116 WCHAR *szValue = NULL; 117 118 if (hkRoot == HKEY_LOCAL_MACHINE) 119 TRACE("processing %ls entries under HKLM\n", szKeyName); 120 else 121 TRACE("processing %ls entries under HKCU\n", szKeyName); 122 123 res = RegOpenKeyExW(hkRoot, 124 L"Software\\Microsoft\\Windows\\CurrentVersion", 125 0, 126 KEY_READ, 127 &hkWin); 128 if (res != ERROR_SUCCESS) 129 { 130 TRACE("RegOpenKeyW failed on Software\\Microsoft\\Windows\\CurrentVersion (%ld)\n", res); 131 132 goto end; 133 } 134 135 res = RegOpenKeyExW(hkWin, 136 szKeyName, 137 0, 138 bDelete ? KEY_ALL_ACCESS : KEY_READ, 139 &hkRun); 140 if (res != ERROR_SUCCESS) 141 { 142 if (res == ERROR_FILE_NOT_FOUND) 143 { 144 TRACE("Key doesn't exist - nothing to be done\n"); 145 146 res = ERROR_SUCCESS; 147 } 148 else 149 TRACE("RegOpenKeyExW failed on run key (%ld)\n", res); 150 151 goto end; 152 } 153 154 res = RegQueryInfoKeyW(hkRun, 155 NULL, 156 NULL, 157 NULL, 158 NULL, 159 NULL, 160 NULL, 161 &i, 162 &cchMaxValue, 163 &cbMaxCmdLine, 164 NULL, 165 NULL); 166 if (res != ERROR_SUCCESS) 167 { 168 TRACE("Couldn't query key info (%ld)\n", res); 169 170 goto end; 171 } 172 173 if (i == 0) 174 { 175 TRACE("No commands to execute.\n"); 176 177 res = ERROR_SUCCESS; 178 goto end; 179 } 180 181 szCmdLine = (WCHAR*)HeapAlloc(hProcessHeap, 182 0, 183 cbMaxCmdLine); 184 if (szCmdLine == NULL) 185 { 186 TRACE("Couldn't allocate memory for the commands to be executed\n"); 187 188 res = ERROR_NOT_ENOUGH_MEMORY; 189 goto end; 190 } 191 192 ++cchMaxValue; 193 szValue = (WCHAR*)HeapAlloc(hProcessHeap, 194 0, 195 cchMaxValue * sizeof(*szValue)); 196 if (szValue == NULL) 197 { 198 TRACE("Couldn't allocate memory for the value names\n"); 199 200 res = ERROR_NOT_ENOUGH_MEMORY; 201 goto end; 202 } 203 204 while (i > 0) 205 { 206 DWORD cchValLength = cchMaxValue, cbDataLength = cbMaxCmdLine; 207 DWORD type; 208 209 --i; 210 211 res = RegEnumValueW(hkRun, 212 i, 213 szValue, 214 &cchValLength, 215 0, 216 &type, 217 (PBYTE)szCmdLine, 218 &cbDataLength); 219 if (res != ERROR_SUCCESS) 220 { 221 TRACE("Couldn't read in value %lu - %ld\n", i, res); 222 223 continue; 224 } 225 226 /* safe mode - force to run if prefixed with asterisk */ 227 if (GetSystemMetrics(SM_CLEANBOOT) && (szValue[0] != L'*')) continue; 228 229 if (bDelete && (res = RegDeleteValueW(hkRun, szValue)) != ERROR_SUCCESS) 230 { 231 TRACE("Couldn't delete value - %lu, %ld. Running command anyways.\n", i, res); 232 } 233 234 if (type != REG_SZ) 235 { 236 TRACE("Incorrect type of value #%lu (%lu)\n", i, type); 237 238 continue; 239 } 240 241 res = runCmd(szCmdLine, NULL, bSynchronous, FALSE); 242 if (res == INVALID_RUNCMD_RETURN) 243 { 244 TRACE("Error running cmd #%lu (%lu)\n", i, GetLastError()); 245 } 246 247 TRACE("Done processing cmd #%lu\n", i); 248 } 249 250 res = ERROR_SUCCESS; 251 end: 252 if (szValue != NULL) 253 HeapFree(hProcessHeap, 0, szValue); 254 if (szCmdLine != NULL) 255 HeapFree(hProcessHeap, 0, szCmdLine); 256 if (hkRun != NULL) 257 RegCloseKey(hkRun); 258 if (hkWin != NULL) 259 RegCloseKey(hkWin); 260 261 TRACE("done\n"); 262 263 return res == ERROR_SUCCESS ? TRUE : FALSE; 264 } 265 266 static BOOL 267 AutoStartupApplications(INT nCSIDL_Folder) 268 { 269 WCHAR szPath[MAX_PATH] = { 0 }; 270 HRESULT hResult; 271 HANDLE hFind; 272 WIN32_FIND_DATAW FoundData; 273 size_t cchPathLen; 274 275 TRACE("(%d)\n", nCSIDL_Folder); 276 277 // Get the special folder path 278 hResult = SHGetFolderPathW(NULL, nCSIDL_Folder, NULL, SHGFP_TYPE_CURRENT, szPath); 279 cchPathLen = wcslen(szPath); 280 if (!SUCCEEDED(hResult) || cchPathLen == 0) 281 { 282 WARN("SHGetFolderPath() failed with error %lu\n", GetLastError()); 283 return FALSE; 284 } 285 286 // Build a path with wildcard 287 StringCbCatW(szPath, sizeof(szPath), L"\\*"); 288 289 // Start enumeration of files 290 hFind = FindFirstFileW(szPath, &FoundData); 291 if (hFind == INVALID_HANDLE_VALUE) 292 { 293 WARN("FindFirstFile(%s) failed with error %lu\n", debugstr_w(szPath), GetLastError()); 294 return FALSE; 295 } 296 297 // Enumerate the files 298 do 299 { 300 // Ignore "." and ".." 301 if (wcscmp(FoundData.cFileName, L".") == 0 || 302 wcscmp(FoundData.cFileName, L"..") == 0) 303 { 304 continue; 305 } 306 307 // Don't run hidden files 308 if (FoundData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) 309 continue; 310 311 // Build the path 312 szPath[cchPathLen + 1] = UNICODE_NULL; 313 StringCbCatW(szPath, sizeof(szPath), FoundData.cFileName); 314 315 TRACE("Executing %s in directory %s\n", debugstr_w(FoundData.cFileName), debugstr_w(szPath)); 316 317 DWORD dwType; 318 if (GetBinaryTypeW(szPath, &dwType)) 319 { 320 runCmd(szPath, NULL, TRUE, FALSE); 321 } 322 else 323 { 324 SHELLEXECUTEINFOW ExecInfo; 325 ZeroMemory(&ExecInfo, sizeof(ExecInfo)); 326 ExecInfo.cbSize = sizeof(ExecInfo); 327 ExecInfo.lpFile = szPath; 328 ShellExecuteExW(&ExecInfo); 329 } 330 } while (FindNextFileW(hFind, &FoundData)); 331 332 FindClose(hFind); 333 return TRUE; 334 } 335 336 INT ProcessStartupItems(VOID) 337 { 338 /* TODO: ProcessRunKeys already checks SM_CLEANBOOT -- items prefixed with * should probably run even in safe mode */ 339 BOOL bNormalBoot = GetSystemMetrics(SM_CLEANBOOT) == 0; /* Perform the operations that are performed every boot */ 340 /* First, set the current directory to SystemRoot */ 341 WCHAR gen_path[MAX_PATH]; 342 DWORD res; 343 344 res = GetWindowsDirectoryW(gen_path, _countof(gen_path)); 345 if (res == 0) 346 { 347 TRACE("Couldn't get the windows directory - error %lu\n", GetLastError()); 348 349 return 100; 350 } 351 352 if (!SetCurrentDirectoryW(gen_path)) 353 { 354 TRACE("Cannot set the dir to %ls (%lu)\n", gen_path, GetLastError()); 355 356 return 100; 357 } 358 359 /* Perform the operations by order checking if policy allows it, checking if this is not Safe Mode, 360 * stopping if one fails, skipping if necessary. 361 */ 362 res = TRUE; 363 /* TODO: RunOnceEx */ 364 365 if (res && (SHRestricted(REST_NOLOCALMACHINERUNONCE) == 0)) 366 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"RunOnce", TRUE, TRUE); 367 368 if (res && bNormalBoot && (SHRestricted(REST_NOLOCALMACHINERUN) == 0)) 369 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"Run", FALSE, FALSE); 370 371 if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0)) 372 res = ProcessRunKeys(HKEY_CURRENT_USER, L"Run", FALSE, FALSE); 373 374 /* All users Startup folder */ 375 AutoStartupApplications(CSIDL_COMMON_STARTUP); 376 377 /* Current user Startup folder */ 378 AutoStartupApplications(CSIDL_STARTUP); 379 380 /* TODO: HKCU\RunOnce runs even if StartupHasBeenRun exists */ 381 if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0)) 382 res = ProcessRunKeys(HKEY_CURRENT_USER, L"RunOnce", TRUE, FALSE); 383 384 TRACE("Operation done\n"); 385 386 return res ? 0 : 101; 387 } 388 389 BOOL DoFinishStartupItems(VOID) 390 { 391 if (s_hStartupMutex) 392 { 393 ReleaseMutex(s_hStartupMutex); 394 CloseHandle(s_hStartupMutex); 395 s_hStartupMutex = NULL; 396 } 397 return TRUE; 398 } 399 400 BOOL DoStartStartupItems(ITrayWindow *Tray) 401 { 402 DWORD dwWait; 403 404 if (!bExplorerIsShell) 405 return FALSE; 406 407 if (!s_hStartupMutex) 408 { 409 // Accidentally, there is possibility that the system starts multiple Explorers 410 // before startup of shell. We use a mutex to match the timing of shell initialization. 411 s_hStartupMutex = CreateMutexW(NULL, FALSE, L"ExplorerIsShellMutex"); 412 if (s_hStartupMutex == NULL) 413 return FALSE; 414 } 415 416 dwWait = WaitForSingleObject(s_hStartupMutex, INFINITE); 417 TRACE("dwWait: 0x%08lX\n", dwWait); 418 if (dwWait != WAIT_OBJECT_0) 419 { 420 TRACE("LastError: %ld\n", GetLastError()); 421 422 DoFinishStartupItems(); 423 return FALSE; 424 } 425 426 const DWORD dwWaitTotal = 3000; // in milliseconds 427 DWORD dwTick = GetTickCount(); 428 while (GetShellWindow() == NULL && GetTickCount() - dwTick < dwWaitTotal) 429 { 430 TrayProcessMessages(Tray); 431 } 432 433 if (GetShellWindow() == NULL) 434 { 435 DoFinishStartupItems(); 436 return FALSE; 437 } 438 439 // Check the volatile "StartupHasBeenRun" key 440 HKEY hSessionKey, hKey; 441 HRESULT hr = SHCreateSessionKey(KEY_WRITE, &hSessionKey); 442 if (SUCCEEDED(hr)) 443 { 444 ASSERT(hSessionKey); 445 446 DWORD dwDisp; 447 LONG Error = RegCreateKeyExW(hSessionKey, L"StartupHasBeenRun", 0, NULL, 448 REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisp); 449 RegCloseKey(hSessionKey); 450 RegCloseKey(hKey); 451 if (Error == ERROR_SUCCESS && dwDisp == REG_OPENED_EXISTING_KEY) 452 { 453 return FALSE; // Startup programs has already been run 454 } 455 } 456 457 return TRUE; 458 } 459