[EXPLORER_NEW]
[reactos.git] / reactos / base / shell / explorer / services / startup.c
1 /*
2 * Copyright (C) 2002 Andreas Mohr
3 * Copyright (C) 2002 Shachar Shemesh
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 /* Based on the Wine "bootup" handler application
21 *
22 * This app handles the various "hooks" windows allows for applications to perform
23 * as part of the bootstrap process. Theses are roughly devided into three types.
24 * Knowledge base articles that explain this are 137367, 179365, 232487 and 232509.
25 * Also, 119941 has some info on grpconv.exe
26 * The operations performed are (by order of execution):
27 *
28 * Preboot (prior to fully loading the Windows kernel):
29 * - wininit.exe (rename operations left in wininit.ini - Win 9x only)
30 * - PendingRenameOperations (rename operations left in the registry - Win NT+ only)
31 *
32 * Startup (before the user logs in)
33 * - Services (NT, ?semi-synchronous?, not implemented yet)
34 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce (9x, asynch)
35 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices (9x, asynch)
36 *
37 * After log in
38 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce (all, synch)
39 * - HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run (all, asynch)
40 * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run (all, asynch)
41 * - Startup folders (all, ?asynch?, no imp)
42 * - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce (all, asynch)
43 *
44 * Somewhere in there is processing the RunOnceEx entries (also no imp)
45 *
46 * Bugs:
47 * - If a pending rename registry does not start with \??\ the entry is
48 * processed anyways. I'm not sure that is the Windows behaviour.
49 * - Need to check what is the windows behaviour when trying to delete files
50 * and directories that are read-only
51 * - In the pending rename registry processing - there are no traces of the files
52 * processed (requires translations from Unicode to Ansi).
53 */
54
55 #include <stdio.h>
56 #include <windows.h>
57 #include <ctype.h>
58
59 EXTERN_C HRESULT WINAPI SHCreateSessionKey(REGSAM samDesired, PHKEY phKey);
60
61 /**
62 * Performs the rename operations dictated in %SystemRoot%\Wininit.ini.
63 * Returns FALSE if there was an error, or otherwise if all is ok.
64 */
65 static BOOL wininit()
66 {
67 return TRUE;
68 }
69
70 static BOOL pendingRename()
71 {
72 static const WCHAR ValueName[] = {'P','e','n','d','i','n','g',
73 'F','i','l','e','R','e','n','a','m','e',
74 'O','p','e','r','a','t','i','o','n','s',0};
75 static const WCHAR SessionW[] = { 'S','y','s','t','e','m','\\',
76 'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
77 'C','o','n','t','r','o','l','\\',
78 'S','e','s','s','i','o','n',' ','M','a','n','a','g','e','r',0};
79 WCHAR *buffer=NULL;
80 const WCHAR *src=NULL, *dst=NULL;
81 DWORD dataLength=0;
82 HKEY hSession=NULL;
83 DWORD res;
84
85 printf("Entered\n");
86
87 if ((res=RegOpenKeyExW(HKEY_LOCAL_MACHINE, SessionW, 0, KEY_ALL_ACCESS, &hSession))
88 !=ERROR_SUCCESS)
89 {
90 if (res==ERROR_FILE_NOT_FOUND)
91 {
92 printf("The key was not found - skipping\n");
93 res=TRUE;
94 }
95 else
96 {
97 printf("Couldn't open key, error %ld\n", res);
98 res=FALSE;
99 }
100
101 goto end;
102 }
103
104 res=RegQueryValueExW(hSession, ValueName, NULL, NULL /* The value type does not really interest us, as it is not
105 truely a REG_MULTI_SZ anyways */,
106 NULL, &dataLength);
107 if (res==ERROR_FILE_NOT_FOUND)
108 {
109 /* No value - nothing to do. Great! */
110 printf("Value not present - nothing to rename\n");
111 res=TRUE;
112 goto end;
113 }
114
115 if (res!=ERROR_SUCCESS)
116 {
117 printf("Couldn't query value's length (%ld)\n", res);
118 res=FALSE;
119 goto end;
120 }
121
122 buffer=malloc(dataLength);
123 if (buffer==NULL)
124 {
125 printf("Couldn't allocate %lu bytes for the value\n", dataLength);
126 res=FALSE;
127 goto end;
128 }
129
130 res=RegQueryValueExW(hSession, ValueName, NULL, NULL, (LPBYTE)buffer, &dataLength);
131 if (res!=ERROR_SUCCESS)
132 {
133 printf("Couldn't query value after successfully querying before (%lu),\n"
134 "please report to wine-devel@winehq.org\n", res);
135 res=FALSE;
136 goto end;
137 }
138
139 /* Make sure that the data is long enough and ends with two NULLs. This
140 * simplifies the code later on.
141 */
142 if (dataLength<2*sizeof(buffer[0]) ||
143 buffer[dataLength/sizeof(buffer[0])-1]!='\0' ||
144 buffer[dataLength/sizeof(buffer[0])-2]!='\0')
145 {
146 printf("Improper value format - doesn't end with NULL\n");
147 res=FALSE;
148 goto end;
149 }
150
151 for(src=buffer; (src-buffer)*sizeof(src[0])<dataLength && *src!='\0';
152 src=dst+lstrlenW(dst)+1)
153 {
154 DWORD dwFlags=0;
155
156 printf("processing next command\n");
157
158 dst=src+lstrlenW(src)+1;
159
160 /* We need to skip the \??\ header */
161 if (src[0]=='\\' && src[1]=='?' && src[2]=='?' && src[3]=='\\')
162 src+=4;
163
164 if (dst[0]=='!')
165 {
166 dwFlags|=MOVEFILE_REPLACE_EXISTING;
167 dst++;
168 }
169
170 if (dst[0]=='\\' && dst[1]=='?' && dst[2]=='?' && dst[3]=='\\')
171 dst+=4;
172
173 if (*dst!='\0')
174 {
175 /* Rename the file */
176 MoveFileExW(src, dst, dwFlags);
177 } else
178 {
179 /* Delete the file or directory */
180 res = GetFileAttributesW (src);
181 if (res != (DWORD)-1)
182 {
183 if ((res&FILE_ATTRIBUTE_DIRECTORY)==0)
184 {
185 /* It's a file */
186 DeleteFileW(src);
187 } else
188 {
189 /* It's a directory */
190 RemoveDirectoryW(src);
191 }
192 } else
193 {
194 printf("couldn't get file attributes (%ld)\n", GetLastError());
195 }
196 }
197 }
198
199 if ((res=RegDeleteValueW(hSession, ValueName))!=ERROR_SUCCESS)
200 {
201 printf("Error deleting the value (%lu)\n", GetLastError());
202 res=FALSE;
203 } else
204 res=TRUE;
205
206 end:
207 if (buffer!=NULL)
208 free(buffer);
209
210 if (hSession!=NULL)
211 RegCloseKey(hSession);
212
213 return res;
214 }
215
216 enum runkeys {
217 RUNKEY_RUN, RUNKEY_RUNONCE, RUNKEY_RUNSERVICES, RUNKEY_RUNSERVICESONCE
218 };
219
220 const WCHAR runkeys_names[][30]=
221 {
222 {'R','u','n',0},
223 {'R','u','n','O','n','c','e',0},
224 {'R','u','n','S','e','r','v','i','c','e','s',0},
225 {'R','u','n','S','e','r','v','i','c','e','s','O','n','c','e',0}
226 };
227
228 #define INVALID_RUNCMD_RETURN -1
229 /**
230 * This function runs the specified command in the specified dir.
231 * [in,out] cmdline - the command line to run. The function may change the passed buffer.
232 * [in] dir - the dir to run the command in. If it is NULL, then the current dir is used.
233 * [in] wait - whether to wait for the run program to finish before returning.
234 * [in] minimized - Whether to ask the program to run minimized.
235 *
236 * Returns:
237 * If running the process failed, returns INVALID_RUNCMD_RETURN. Use GetLastError to get the error code.
238 * If wait is FALSE - returns 0 if successful.
239 * If wait is TRUE - returns the program's return value.
240 */
241 static int runCmd(LPWSTR cmdline, LPCWSTR dir, BOOL wait, BOOL minimized)
242 {
243 STARTUPINFOW si;
244 PROCESS_INFORMATION info;
245 DWORD exit_code=0;
246 WCHAR szCmdLineExp[MAX_PATH+1]= L"\0";
247
248 ExpandEnvironmentStrings(cmdline, szCmdLineExp, sizeof(szCmdLineExp));
249
250 memset(&si, 0, sizeof(si));
251 si.cb=sizeof(si);
252 if (minimized)
253 {
254 si.dwFlags=STARTF_USESHOWWINDOW;
255 si.wShowWindow=SW_MINIMIZE;
256 }
257 memset(&info, 0, sizeof(info));
258
259 if (!CreateProcessW(NULL, szCmdLineExp, NULL, NULL, FALSE, 0, NULL, dir, &si, &info))
260 {
261 printf("Failed to run command (%ld)\n", GetLastError());
262
263 return INVALID_RUNCMD_RETURN;
264 }
265
266 printf("Successfully ran command\n"); //%s - Created process handle %p\n",
267 //wine_dbgstr_w(szCmdLineExp), info.hProcess);
268
269 if (wait)
270 { /* wait for the process to exit */
271 WaitForSingleObject(info.hProcess, INFINITE);
272 GetExitCodeProcess(info.hProcess, &exit_code);
273 }
274
275 CloseHandle(info.hThread);
276 CloseHandle(info.hProcess);
277
278 return exit_code;
279 }
280
281 /**
282 * Process a "Run" type registry key.
283 * hkRoot is the HKEY from which "Software\Microsoft\Windows\CurrentVersion" is
284 * opened.
285 * szKeyName is the key holding the actual entries.
286 * bDelete tells whether we should delete each value right before executing it.
287 * bSynchronous tells whether we should wait for the prog to complete before
288 * going on to the next prog.
289 */
290 static BOOL ProcessRunKeys(HKEY hkRoot, LPCWSTR szKeyName, BOOL bDelete,
291 BOOL bSynchronous)
292 {
293 static const WCHAR WINKEY_NAME[]={'S','o','f','t','w','a','r','e','\\',
294 'M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\',
295 'C','u','r','r','e','n','t','V','e','r','s','i','o','n',0};
296 HKEY hkWin=NULL, hkRun=NULL;
297 LONG res=ERROR_SUCCESS;
298 DWORD i, nMaxCmdLine=0, nMaxValue=0;
299 WCHAR *szCmdLine=NULL;
300 WCHAR *szValue=NULL;
301
302 if (hkRoot==HKEY_LOCAL_MACHINE)
303 wprintf(L"processing %s entries under HKLM\n", szKeyName);
304 else
305 wprintf(L"processing %s entries under HKCU\n", szKeyName);
306
307 if ((res=RegOpenKeyExW(hkRoot, WINKEY_NAME, 0, KEY_READ, &hkWin))!=ERROR_SUCCESS)
308 {
309 printf("RegOpenKey failed on Software\\Microsoft\\Windows\\CurrentVersion (%ld)\n",
310 res);
311
312 goto end;
313 }
314
315 if ((res=RegOpenKeyExW(hkWin, szKeyName, 0, bDelete?KEY_ALL_ACCESS:KEY_READ, &hkRun))!=
316 ERROR_SUCCESS)
317 {
318 if (res==ERROR_FILE_NOT_FOUND)
319 {
320 printf("Key doesn't exist - nothing to be done\n");
321
322 res=ERROR_SUCCESS;
323 }
324 else
325 printf("RegOpenKey failed on run key (%ld)\n", res);
326
327 goto end;
328 }
329
330 if ((res=RegQueryInfoKeyW(hkRun, NULL, NULL, NULL, NULL, NULL, NULL, &i, &nMaxValue,
331 &nMaxCmdLine, NULL, NULL))!=ERROR_SUCCESS)
332 {
333 printf("Couldn't query key info (%ld)\n", res);
334
335 goto end;
336 }
337
338 if (i==0)
339 {
340 printf("No commands to execute.\n");
341
342 res=ERROR_SUCCESS;
343 goto end;
344 }
345
346 if ((szCmdLine=malloc(nMaxCmdLine))==NULL)
347 {
348 printf("Couldn't allocate memory for the commands to be executed\n");
349
350 res=ERROR_NOT_ENOUGH_MEMORY;
351 goto end;
352 }
353
354 if ((szValue=malloc((++nMaxValue)*sizeof(*szValue)))==NULL)
355 {
356 printf("Couldn't allocate memory for the value names\n");
357
358 free(szCmdLine);
359 res=ERROR_NOT_ENOUGH_MEMORY;
360 goto end;
361 }
362
363 while(i>0)
364 {
365 DWORD nValLength=nMaxValue, nDataLength=nMaxCmdLine;
366 DWORD type;
367
368 --i;
369
370 if ((res=RegEnumValueW(hkRun, i, szValue, &nValLength, 0, &type,
371 (LPBYTE)szCmdLine, &nDataLength))!=ERROR_SUCCESS)
372 {
373 printf("Couldn't read in value %ld - %ld\n", i, res);
374
375 continue;
376 }
377
378 /* safe mode - force to run if prefixed with asterisk */
379 if (GetSystemMetrics(SM_CLEANBOOT) && (szValue[0] != L'*')) continue;
380
381 if (bDelete && (res=RegDeleteValueW(hkRun, szValue))!=ERROR_SUCCESS)
382 {
383 printf("Couldn't delete value - %ld, %ld. Running command anyways.\n", i, res);
384 }
385
386 if (type!=REG_SZ)
387 {
388 printf("Incorrect type of value #%ld (%ld)\n", i, type);
389
390 continue;
391 }
392
393 if ((res=runCmd(szCmdLine, NULL, bSynchronous, FALSE))==INVALID_RUNCMD_RETURN)
394 {
395 printf("Error running cmd #%ld (%ld)\n", i, GetLastError());
396 }
397
398 printf("Done processing cmd #%ld\n", i);
399 }
400
401 free(szValue);
402 free(szCmdLine);
403 res=ERROR_SUCCESS;
404
405 end:
406 if (hkRun!=NULL)
407 RegCloseKey(hkRun);
408 if (hkWin!=NULL)
409 RegCloseKey(hkWin);
410
411 printf("done\n");
412
413 return res==ERROR_SUCCESS?TRUE:FALSE;
414 }
415
416 /// structure holding startup flags
417 struct op_mask {
418 BOOL w9xonly; /* Perform only operations done on Windows 9x */
419 BOOL ntonly; /* Perform only operations done on Windows NT */
420 BOOL startup; /* Perform the operations that are performed every boot */
421 BOOL preboot; /* Perform file renames typically done before the system starts */
422 BOOL prelogin; /* Perform the operations typically done before the user logs in */
423 BOOL postlogin; /* Operations done after login */
424 };
425
426 static const struct op_mask
427 SESSION_START = {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE},
428 SETUP = {FALSE, FALSE, FALSE, TRUE, TRUE, TRUE};
429 #define DEFAULT SESSION_START
430
431 int startup(int argc, const char *argv[])
432 {
433 struct op_mask ops; /* Which of the ops do we want to perform? */
434 /* First, set the current directory to SystemRoot */
435 TCHAR gen_path[MAX_PATH];
436 DWORD res;
437 HKEY hSessionKey, hKey;
438 HRESULT hr;
439
440 res = GetWindowsDirectory(gen_path, sizeof(gen_path));
441
442 if (res==0)
443 {
444 printf("Couldn't get the windows directory - error %ld\n",
445 GetLastError());
446
447 return 100;
448 }
449
450 if (!SetCurrentDirectory(gen_path))
451 {
452 wprintf(L"Cannot set the dir to %s (%ld)\n", gen_path, GetLastError());
453
454 return 100;
455 }
456
457 hr = SHCreateSessionKey(KEY_WRITE, &hSessionKey);
458 if (SUCCEEDED(hr))
459 {
460 LONG Error;
461 DWORD dwDisp;
462
463 Error = RegCreateKeyEx(hSessionKey, L"StartupHasBeenRun", 0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisp);
464 RegCloseKey(hSessionKey);
465 if (Error == ERROR_SUCCESS)
466 {
467 RegCloseKey(hKey);
468 if (dwDisp == REG_OPENED_EXISTING_KEY)
469 {
470 /* Startup programs has already been run */
471 return 0;
472 }
473 }
474 }
475
476 if (argc > 1)
477 {
478 switch(argv[1][0])
479 {
480 case 'r': /* Restart */
481 ops = SETUP;
482 break;
483 case 's': /* Full start */
484 ops = SESSION_START;
485 break;
486 default:
487 ops = DEFAULT;
488 break;
489 }
490 } else
491 ops = DEFAULT;
492
493 /* do not run certain items in Safe Mode */
494 if(GetSystemMetrics(SM_CLEANBOOT)) ops.startup = FALSE;
495
496 /* Perform the ops by order, stopping if one fails, skipping if necessary */
497 /* Shachar: Sorry for the perl syntax */
498 res = TRUE;
499 if (res && !ops.ntonly && ops.preboot)
500 res = wininit();
501 if (res && !ops.w9xonly && ops.preboot)
502 res = pendingRename();
503 if (res && !ops.ntonly && ops.prelogin)
504 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, runkeys_names[RUNKEY_RUNSERVICESONCE], TRUE, FALSE);
505 if (res && !ops.ntonly && ops.prelogin && ops.startup)
506 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, runkeys_names[RUNKEY_RUNSERVICES], FALSE, FALSE);
507 if (res && ops.postlogin)
508 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, runkeys_names[RUNKEY_RUNONCE], TRUE, TRUE);
509 if (res && ops.postlogin && ops.startup)
510 res = ProcessRunKeys(HKEY_LOCAL_MACHINE, runkeys_names[RUNKEY_RUN], FALSE, FALSE);
511 if (res && ops.postlogin && ops.startup)
512 res = ProcessRunKeys(HKEY_CURRENT_USER, runkeys_names[RUNKEY_RUN], FALSE, FALSE);
513 if (res && ops.postlogin && ops.startup)
514 res = ProcessRunKeys(HKEY_CURRENT_USER, runkeys_names[RUNKEY_RUNONCE], TRUE, FALSE);
515
516 printf("Operation done\n");
517
518 return res ? 0 : 101;
519 }