xref: /reactos/base/shell/explorer/startup.cpp (revision c948ea85)
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