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