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