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 */
runCmd(LPWSTR cmdline,LPCWSTR dir,BOOL wait,BOOL minimized)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 */
ProcessRunKeys(HKEY hkRoot,LPCWSTR szKeyName,BOOL bDelete,BOOL bSynchronous)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 */
ProcessRunOnceEx(HKEY hkRoot)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
AutoStartupApplications(INT nCSIDL_Folder)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
ProcessStartupItems(BOOL bRunOnce)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
ReleaseStartupMutex()513 VOID ReleaseStartupMutex()
514 {
515 if (s_hStartupMutex)
516 {
517 ReleaseMutex(s_hStartupMutex);
518 CloseHandle(s_hStartupMutex);
519 s_hStartupMutex = NULL;
520 }
521 }
522
InitializeStartupMutex()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
DoStartStartupItems(ITrayWindow * Tray)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
ProcessRunOnceItems()588 VOID ProcessRunOnceItems()
589 {
590 if (bExplorerIsShell && IsUserAnAdmin() && InitializeStartupMutex())
591 ProcessStartupItems(TRUE);
592 }
593