ab7a478a327f3ee12006ae78ccc0d0bde9445589
[reactos.git] / base / shell / explorer / startup.cpp
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 }