xref: /reactos/base/shell/explorer/startup.cpp (revision c2c66aff)
1 /*
2  * Copyright (C) 2002 Andreas Mohr
3  * Copyright (C) 2002 Shachar Shemesh
4  * Copyright (C) 2013 Edijs Kolesnikovics
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 /* Based on the Wine "bootup" handler application
22  *
23  * This app handles the various "hooks" windows allows for applications to perform
24  * as part of the bootstrap process. Theses are roughly devided into three types.
25  * Knowledge base articles that explain this are 137367, 179365, 232487 and 232509.
26  * Also, 119941 has some info on grpconv.exe
27  * The operations performed are (by order of execution):
28  *
29  * After log in
30  * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx (synch, no imp)
31  * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce (synch)
32  * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run (asynch)
33  * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run (asynch)
34  * - All users Startup folder "%ALLUSERSPROFILE%\Start Menu\Programs\Startup" (asynch, no imp)
35  * - Current user Startup folder "%USERPROFILE%\Start Menu\Programs\Startup" (asynch, no imp)
36  * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce (asynch)
37  *
38  * None is processed in Safe Mode // FIXME: Check RunOnceEx in Safe Mode
39  */
40 
41 #include "precomp.h"
42 
43 #define INVALID_RUNCMD_RETURN -1
44 /**
45  * This function runs the specified command in the specified dir.
46  * [in,out] cmdline - the command line to run. The function may change the passed buffer.
47  * [in] dir - the dir to run the command in. If it is NULL, then the current dir is used.
48  * [in] wait - whether to wait for the run program to finish before returning.
49  * [in] minimized - Whether to ask the program to run minimized.
50  *
51  * Returns:
52  * If running the process failed, returns INVALID_RUNCMD_RETURN. Use GetLastError to get the error code.
53  * If wait is FALSE - returns 0 if successful.
54  * If wait is TRUE - returns the program's return value.
55  */
56 static int runCmd(LPWSTR cmdline, LPCWSTR dir, BOOL wait, BOOL minimized)
57 {
58     STARTUPINFOW si;
59     PROCESS_INFORMATION info;
60     DWORD exit_code = 0;
61     WCHAR szCmdLineExp[MAX_PATH+1] = L"\0";
62 
63     ExpandEnvironmentStringsW(cmdline, szCmdLineExp, _countof(szCmdLineExp));
64 
65     memset(&si, 0, sizeof(si));
66     si.cb = sizeof(si);
67     if (minimized)
68     {
69         si.dwFlags = STARTF_USESHOWWINDOW;
70         si.wShowWindow = SW_MINIMIZE;
71     }
72     memset(&info, 0, sizeof(info));
73 
74     if (!CreateProcessW(NULL, szCmdLineExp, NULL, NULL, FALSE, 0, NULL, dir, &si, &info))
75     {
76         TRACE("Failed to run command (%lu)\n", GetLastError());
77 
78         return INVALID_RUNCMD_RETURN;
79     }
80 
81     TRACE("Successfully ran command\n");
82 
83     if (wait)
84     {   /* wait for the process to exit */
85         WaitForSingleObject(info.hProcess, INFINITE);
86         GetExitCodeProcess(info.hProcess, &exit_code);
87     }
88 
89     CloseHandle(info.hThread);
90     CloseHandle(info.hProcess);
91 
92     return exit_code;
93 }
94 
95 
96 /**
97  * Process a "Run" type registry key.
98  * hkRoot is the HKEY from which "Software\Microsoft\Windows\CurrentVersion" is
99  *      opened.
100  * szKeyName is the key holding the actual entries.
101  * bDelete tells whether we should delete each value right before executing it.
102  * bSynchronous tells whether we should wait for the prog to complete before
103  *      going on to the next prog.
104  */
105 static BOOL ProcessRunKeys(HKEY hkRoot, LPCWSTR szKeyName, BOOL bDelete,
106         BOOL bSynchronous)
107 {
108     HKEY hkWin = NULL, hkRun = NULL;
109     LONG res = ERROR_SUCCESS;
110     DWORD i, cbMaxCmdLine = 0, cchMaxValue = 0;
111     WCHAR *szCmdLine = NULL;
112     WCHAR *szValue = NULL;
113 
114     if (hkRoot == HKEY_LOCAL_MACHINE)
115         TRACE("processing %ls entries under HKLM\n", szKeyName);
116     else
117         TRACE("processing %ls entries under HKCU\n", szKeyName);
118 
119     res = RegOpenKeyExW(hkRoot,
120                         L"Software\\Microsoft\\Windows\\CurrentVersion",
121                         0,
122                         KEY_READ,
123                         &hkWin);
124     if (res != ERROR_SUCCESS)
125     {
126         TRACE("RegOpenKeyW failed on Software\\Microsoft\\Windows\\CurrentVersion (%ld)\n", res);
127 
128         goto end;
129     }
130 
131     res = RegOpenKeyExW(hkWin,
132                         szKeyName,
133                         0,
134                         bDelete ? KEY_ALL_ACCESS : KEY_READ,
135                         &hkRun);
136     if (res != ERROR_SUCCESS)
137     {
138         if (res == ERROR_FILE_NOT_FOUND)
139         {
140             TRACE("Key doesn't exist - nothing to be done\n");
141 
142             res = ERROR_SUCCESS;
143         }
144         else
145             TRACE("RegOpenKeyExW failed on run key (%ld)\n", res);
146 
147         goto end;
148     }
149 
150     res = RegQueryInfoKeyW(hkRun,
151                            NULL,
152                            NULL,
153                            NULL,
154                            NULL,
155                            NULL,
156                            NULL,
157                            &i,
158                            &cchMaxValue,
159                            &cbMaxCmdLine,
160                            NULL,
161                            NULL);
162     if (res != ERROR_SUCCESS)
163     {
164         TRACE("Couldn't query key info (%ld)\n", res);
165 
166         goto end;
167     }
168 
169     if (i == 0)
170     {
171         TRACE("No commands to execute.\n");
172 
173         res = ERROR_SUCCESS;
174         goto end;
175     }
176 
177     szCmdLine = (WCHAR*)HeapAlloc(hProcessHeap,
178                           0,
179                           cbMaxCmdLine);
180     if (szCmdLine == NULL)
181     {
182         TRACE("Couldn't allocate memory for the commands to be executed\n");
183 
184         res = ERROR_NOT_ENOUGH_MEMORY;
185         goto end;
186     }
187 
188     ++cchMaxValue;
189     szValue = (WCHAR*)HeapAlloc(hProcessHeap,
190                         0,
191                         cchMaxValue * sizeof(*szValue));
192     if (szValue == NULL)
193     {
194         TRACE("Couldn't allocate memory for the value names\n");
195 
196         res = ERROR_NOT_ENOUGH_MEMORY;
197         goto end;
198     }
199 
200     while (i > 0)
201     {
202         DWORD cchValLength = cchMaxValue, cbDataLength = cbMaxCmdLine;
203         DWORD type;
204 
205         --i;
206 
207         res = RegEnumValueW(hkRun,
208                             i,
209                             szValue,
210                             &cchValLength,
211                             0,
212                             &type,
213                             (PBYTE)szCmdLine,
214                             &cbDataLength);
215         if (res != ERROR_SUCCESS)
216         {
217             TRACE("Couldn't read in value %lu - %ld\n", i, res);
218 
219             continue;
220         }
221 
222         /* safe mode - force to run if prefixed with asterisk */
223         if (GetSystemMetrics(SM_CLEANBOOT) && (szValue[0] != L'*')) continue;
224 
225         if (bDelete && (res = RegDeleteValueW(hkRun, szValue)) != ERROR_SUCCESS)
226         {
227             TRACE("Couldn't delete value - %lu, %ld. Running command anyways.\n", i, res);
228         }
229 
230         if (type != REG_SZ)
231         {
232             TRACE("Incorrect type of value #%lu (%lu)\n", i, type);
233 
234             continue;
235         }
236 
237         res = runCmd(szCmdLine, NULL, bSynchronous, FALSE);
238         if (res == INVALID_RUNCMD_RETURN)
239         {
240             TRACE("Error running cmd #%lu (%lu)\n", i, GetLastError());
241         }
242 
243         TRACE("Done processing cmd #%lu\n", i);
244     }
245 
246     res = ERROR_SUCCESS;
247 end:
248     if (szValue != NULL)
249         HeapFree(hProcessHeap, 0, szValue);
250     if (szCmdLine != NULL)
251         HeapFree(hProcessHeap, 0, szCmdLine);
252     if (hkRun != NULL)
253         RegCloseKey(hkRun);
254     if (hkWin != NULL)
255         RegCloseKey(hkWin);
256 
257     TRACE("done\n");
258 
259     return res == ERROR_SUCCESS ? TRUE : FALSE;
260 }
261 
262 
263 int
264 ProcessStartupItems(VOID)
265 {
266     /* TODO: ProcessRunKeys already checks SM_CLEANBOOT -- items prefixed with * should probably run even in safe mode */
267     BOOL bNormalBoot = GetSystemMetrics(SM_CLEANBOOT) == 0; /* Perform the operations that are performed every boot */
268     /* First, set the current directory to SystemRoot */
269     WCHAR gen_path[MAX_PATH];
270     DWORD res;
271     HKEY hSessionKey, hKey;
272     HRESULT hr;
273 
274     res = GetWindowsDirectoryW(gen_path, _countof(gen_path));
275     if (res == 0)
276     {
277         TRACE("Couldn't get the windows directory - error %lu\n", GetLastError());
278 
279         return 100;
280     }
281 
282     if (!SetCurrentDirectoryW(gen_path))
283     {
284         TRACE("Cannot set the dir to %ls (%lu)\n", gen_path, GetLastError());
285 
286         return 100;
287     }
288 
289     hr = SHCreateSessionKey(KEY_WRITE, &hSessionKey);
290     if (SUCCEEDED(hr))
291     {
292         LONG Error;
293         DWORD dwDisp;
294 
295         Error = RegCreateKeyExW(hSessionKey, L"StartupHasBeenRun", 0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisp);
296         RegCloseKey(hSessionKey);
297         if (Error == ERROR_SUCCESS)
298         {
299             RegCloseKey(hKey);
300             if (dwDisp == REG_OPENED_EXISTING_KEY)
301             {
302                 /* Startup programs has already been run */
303                 return 0;
304             }
305         }
306     }
307 
308     /* Perform the operations by order checking if policy allows it, checking if this is not Safe Mode,
309      * stopping if one fails, skipping if necessary.
310     */
311     res = TRUE;
312     /* TODO: RunOnceEx */
313 
314     if (res && (SHRestricted(REST_NOLOCALMACHINERUNONCE) == 0))
315         res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"RunOnce", TRUE, TRUE);
316 
317     if (res && bNormalBoot && (SHRestricted(REST_NOLOCALMACHINERUN) == 0))
318         res = ProcessRunKeys(HKEY_LOCAL_MACHINE, L"Run", FALSE, FALSE);
319 
320     if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0))
321         res = ProcessRunKeys(HKEY_CURRENT_USER, L"Run", FALSE, FALSE);
322 
323     /* TODO: All users Startup folder */
324 
325     /* TODO: Current user Startup folder */
326 
327     /* TODO: HKCU\RunOnce runs even if StartupHasBeenRun exists */
328     if (res && bNormalBoot && (SHRestricted(REST_NOCURRENTUSERRUNONCE) == 0))
329         res = ProcessRunKeys(HKEY_CURRENT_USER, L"RunOnce", TRUE, FALSE);
330 
331     TRACE("Operation done\n");
332 
333     return res ? 0 : 101;
334 }
335