Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
16 KiB

  1. //
  2. // runonce.c (shared runonce code between explorer.exe and runonce.exe)
  3. //
  4. #include <runonce.h>
  5. #if (_WIN32_WINNT >= 0x0500)
  6. // stolen from <tsappcmp.h>
  7. #define TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS 0x00008000
  8. #define CompatibilityApp 1
  9. typedef LONG TERMSRV_COMPATIBILITY_CLASS;
  10. typedef BOOL (* PFNGSETTERMSRVAPPINSTALLMODE)(BOOL bState);
  11. typedef BOOL (* PFNGETTERMSRVCOMPATFLAGSEX)(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc);
  12. // even though this function is in kernel32.lib, we need to have a LoadLibrary/GetProcAddress
  13. // thunk for downlevel components who include this
  14. STDAPI_(BOOL) SHSetTermsrvAppInstallMode(BOOL bState)
  15. {
  16. static PFNGSETTERMSRVAPPINSTALLMODE pfn = NULL;
  17. if (pfn == NULL)
  18. {
  19. // kernel32 should already be loaded
  20. HMODULE hmod = GetModuleHandle(TEXT("kernel32.dll"));
  21. if (hmod)
  22. {
  23. pfn = (PFNGSETTERMSRVAPPINSTALLMODE)GetProcAddress(hmod, "SetTermsrvAppInstallMode");
  24. }
  25. else
  26. {
  27. pfn = (PFNGSETTERMSRVAPPINSTALLMODE)-1;
  28. }
  29. }
  30. if (pfn && (pfn != (PFNGSETTERMSRVAPPINSTALLMODE)-1))
  31. {
  32. return pfn(bState);
  33. }
  34. else
  35. {
  36. return FALSE;
  37. }
  38. }
  39. STDAPI_(ULONG) SHGetTermsrCompatFlagsEx(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc)
  40. {
  41. static PFNGETTERMSRVCOMPATFLAGSEX pfn = NULL;
  42. if (pfn == NULL)
  43. {
  44. HMODULE hmod = LoadLibrary(TEXT("TSAppCMP.DLL"));
  45. if (hmod)
  46. {
  47. pfn = (PFNGETTERMSRVCOMPATFLAGSEX)GetProcAddress(hmod, "GetTermsrCompatFlagsEx");
  48. }
  49. else
  50. {
  51. pfn = (PFNGETTERMSRVCOMPATFLAGSEX)-1;
  52. }
  53. }
  54. if (pfn && (pfn != (PFNGETTERMSRVCOMPATFLAGSEX)-1))
  55. {
  56. return pfn(pwszApp, pdwFlags, tscc);
  57. }
  58. else
  59. {
  60. *pdwFlags = 0;
  61. return 0;
  62. }
  63. }
  64. HANDLE SetJobCompletionPort(HANDLE hJob)
  65. {
  66. HANDLE hRet = NULL;
  67. HANDLE hIOPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
  68. if (hIOPort != NULL)
  69. {
  70. JOBOBJECT_ASSOCIATE_COMPLETION_PORT CompletionPort;
  71. CompletionPort.CompletionKey = hJob ;
  72. CompletionPort.CompletionPort = hIOPort;
  73. if (SetInformationJobObject(hJob,
  74. JobObjectAssociateCompletionPortInformation,
  75. &CompletionPort,
  76. sizeof(CompletionPort)))
  77. {
  78. hRet = hIOPort;
  79. }
  80. else
  81. {
  82. CloseHandle(hIOPort);
  83. }
  84. }
  85. return hRet;
  86. }
  87. STDAPI_(DWORD) WaitingThreadProc(void *pv)
  88. {
  89. HANDLE hIOPort = (HANDLE)pv;
  90. if (hIOPort)
  91. {
  92. while (TRUE)
  93. {
  94. DWORD dwCompletionCode;
  95. ULONG_PTR pCompletionKey;
  96. LPOVERLAPPED pOverlapped;
  97. if (!GetQueuedCompletionStatus(hIOPort, &dwCompletionCode, &pCompletionKey, &pOverlapped, INFINITE) ||
  98. (dwCompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO))
  99. {
  100. break;
  101. }
  102. }
  103. }
  104. return 0;
  105. }
  106. //
  107. // The following handles running an application and optionally waiting for it
  108. // to all the child procs to terminate. This is accomplished thru Kernel Job Objects
  109. // which is only available in NT5
  110. //
  111. BOOL _CreateRegJob(LPCTSTR pszCmd, BOOL bWait)
  112. {
  113. BOOL bRet = FALSE;
  114. HANDLE hJobObject = CreateJobObjectW(NULL, NULL);
  115. if (hJobObject)
  116. {
  117. HANDLE hIOPort = SetJobCompletionPort(hJobObject);
  118. if (hIOPort)
  119. {
  120. DWORD dwID;
  121. HANDLE hThread = CreateThread(NULL,
  122. 0,
  123. WaitingThreadProc,
  124. (void*)hIOPort,
  125. CREATE_SUSPENDED,
  126. &dwID);
  127. if (hThread)
  128. {
  129. PROCESS_INFORMATION pi = {0};
  130. STARTUPINFO si = {0};
  131. UINT fMask = SEE_MASK_FLAG_NO_UI;
  132. DWORD dwCreationFlags = CREATE_SUSPENDED;
  133. TCHAR sz[MAX_PATH * 2];
  134. wnsprintf(sz, ARRAYSIZE(sz), TEXT("RunDLL32.EXE Shell32.DLL,ShellExec_RunDLL ?0x%X?%s"), fMask, pszCmd);
  135. si.cb = sizeof(si);
  136. if (CreateProcess(NULL,
  137. sz,
  138. NULL,
  139. NULL,
  140. FALSE,
  141. dwCreationFlags,
  142. NULL,
  143. NULL,
  144. &si,
  145. &pi))
  146. {
  147. if (AssignProcessToJobObject(hJobObject, pi.hProcess))
  148. {
  149. // success!
  150. bRet = TRUE;
  151. ResumeThread(pi.hThread);
  152. ResumeThread(hThread);
  153. if (bWait)
  154. {
  155. SHProcessMessagesUntilEvent(NULL, hThread, INFINITE);
  156. }
  157. }
  158. else
  159. {
  160. TerminateProcess(pi.hProcess, ERROR_ACCESS_DENIED);
  161. }
  162. CloseHandle(pi.hProcess);
  163. CloseHandle(pi.hThread);
  164. }
  165. if (!bRet)
  166. {
  167. TerminateThread(hThread, ERROR_ACCESS_DENIED);
  168. }
  169. CloseHandle(hThread);
  170. }
  171. CloseHandle(hIOPort);
  172. }
  173. CloseHandle(hJobObject);
  174. }
  175. return bRet;
  176. }
  177. BOOL _TryHydra(LPCTSTR pszCmd, RRA_FLAGS *pflags)
  178. {
  179. // See if the terminal-services is enabled in "Application Server" mode
  180. if (IsOS(OS_TERMINALSERVER) && SHSetTermsrvAppInstallMode(TRUE))
  181. {
  182. WCHAR sz[MAX_PATH];
  183. *pflags |= RRA_WAIT;
  184. // Changing timing blows up IE 4.0, but IE5 is ok!
  185. // we are on a TS machine, NT version 4 or 5, with admin priv
  186. // see if the app-compatability flag is set for this executable
  187. // to use the special job-objects for executing module
  188. // get the module name, without the arguments
  189. if (0 < PathProcessCommand(pszCmd, sz, ARRAYSIZE(sz), PPCF_NODIRECTORIES))
  190. {
  191. ULONG ulCompat;
  192. SHGetTermsrCompatFlagsEx(sz, &ulCompat, CompatibilityApp);
  193. // if the special flag for this module-name is set...
  194. if (ulCompat & TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS)
  195. {
  196. *pflags |= RRA_USEJOBOBJECTS;
  197. }
  198. }
  199. return TRUE;
  200. }
  201. return FALSE;
  202. }
  203. #endif // (_WIN32_WINNT >= 0x0500)
  204. //
  205. // On success: returns process handle or INVALID_HANDLE_VALUE if no process
  206. // was launched (i.e., launched via DDE).
  207. // On failure: returns INVALID_HANDLE_VALUE.
  208. //
  209. BOOL _ShellExecRegApp(LPCTSTR pszCmd, BOOL fNoUI, BOOL fWait)
  210. {
  211. TCHAR szQuotedCmdLine[MAX_PATH+2];
  212. LPTSTR pszArgs;
  213. SHELLEXECUTEINFO ei = {0};
  214. // Gross, but if the process command fails, copy the command line to let
  215. // shell execute report the errors
  216. if (PathProcessCommand((LPWSTR)pszCmd,
  217. (LPWSTR)szQuotedCmdLine,
  218. ARRAYSIZE(szQuotedCmdLine),
  219. PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1)
  220. {
  221. lstrcpy(szQuotedCmdLine, pszCmd);
  222. }
  223. pszArgs= PathGetArgs(szQuotedCmdLine);
  224. if (*pszArgs)
  225. {
  226. // Strip args
  227. *(pszArgs - 1) = 0;
  228. }
  229. PathUnquoteSpaces(szQuotedCmdLine);
  230. ei.cbSize = sizeof(SHELLEXECUTEINFO);
  231. ei.lpFile = szQuotedCmdLine;
  232. ei.lpParameters = pszArgs;
  233. ei.nShow = SW_SHOWNORMAL;
  234. ei.fMask = SEE_MASK_NOCLOSEPROCESS;
  235. if (fNoUI)
  236. {
  237. ei.fMask |= SEE_MASK_FLAG_NO_UI;
  238. }
  239. if (ShellExecuteEx(&ei))
  240. {
  241. if (ei.hProcess)
  242. {
  243. if (fWait)
  244. {
  245. SHProcessMessagesUntilEvent(NULL, ei.hProcess, INFINITE);
  246. }
  247. CloseHandle(ei.hProcess);
  248. }
  249. return TRUE;
  250. }
  251. else
  252. {
  253. return FALSE;
  254. }
  255. }
  256. // The following handles running an application and optionally waiting for it
  257. // to terminate.
  258. STDAPI_(BOOL) ShellExecuteRegApp(LPCTSTR pszCmdLine, RRA_FLAGS fFlags)
  259. {
  260. BOOL bRet = FALSE;
  261. if (!pszCmdLine || !*pszCmdLine)
  262. {
  263. // Don't let empty strings through, they will endup doing something dumb
  264. // like opening a command prompt or the like
  265. return bRet;
  266. }
  267. #if (_WIN32_WINNT >= 0x0500)
  268. if (fFlags & RRA_USEJOBOBJECTS)
  269. {
  270. bRet = _CreateRegJob(pszCmdLine, fFlags & RRA_WAIT);
  271. }
  272. #endif
  273. if (!bRet)
  274. {
  275. // fallback if necessary.
  276. bRet = _ShellExecRegApp(pszCmdLine, fFlags & RRA_NOUI, fFlags & RRA_WAIT);
  277. }
  278. return bRet;
  279. }
  280. STDAPI_(BOOL) Cabinet_EnumRegApps(HKEY hkeyParent, LPCTSTR pszSubkey, RRA_FLAGS fFlags, PFNREGAPPSCALLBACK pfnCallback, LPARAM lParam)
  281. {
  282. HKEY hkey;
  283. BOOL bRet = TRUE;
  284. // With the addition of the ACL controlled "policy" run keys RegOpenKey
  285. // might fail on the pszSubkey. Use RegOpenKeyEx with MAXIMIM_ALLOWED
  286. // to ensure that we successfully open the subkey.
  287. if (RegOpenKeyEx(hkeyParent, pszSubkey, 0, MAXIMUM_ALLOWED, &hkey) == ERROR_SUCCESS)
  288. {
  289. DWORD cbValue;
  290. DWORD dwType;
  291. DWORD i;
  292. TCHAR szValueName[80];
  293. TCHAR szCmdLine[MAX_PATH];
  294. HDPA hdpaEntries = NULL;
  295. #ifdef DEBUG
  296. //
  297. // we only support named values so explicitly purge default values
  298. //
  299. LONG cbData = sizeof(szCmdLine);
  300. if (RegQueryValue(hkey, NULL, szCmdLine, &cbData) == ERROR_SUCCESS)
  301. {
  302. ASSERTMSG((cbData <= 2), "Cabinet_EnumRegApps: BOGUS default entry in <%s> '%s'", pszSubkey, szCmdLine);
  303. RegDeleteValue(hkey, NULL);
  304. }
  305. #endif
  306. // now enumerate all of the values.
  307. for (i = 0; !g_fEndSession ; i++)
  308. {
  309. LONG lEnum;
  310. DWORD cbData;
  311. cbValue = ARRAYSIZE(szValueName);
  312. cbData = sizeof(szCmdLine);
  313. lEnum = RegEnumValue(hkey, i, szValueName, &cbValue, NULL, &dwType, (LPBYTE)szCmdLine, &cbData);
  314. if (ERROR_MORE_DATA == lEnum)
  315. {
  316. // ERROR_MORE_DATA means the value name or data was too large
  317. // skip to the next item
  318. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot run oversize entry '%s' in <%s>", szValueName, pszSubkey);
  319. continue;
  320. }
  321. else if (lEnum != ERROR_SUCCESS)
  322. {
  323. if (lEnum != ERROR_NO_MORE_ITEMS)
  324. {
  325. // we hit some kind of registry failure
  326. bRet = FALSE;
  327. }
  328. break;
  329. }
  330. if ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))
  331. {
  332. REGAPP_INFO * prai;
  333. if (dwType == REG_EXPAND_SZ)
  334. {
  335. DWORD dwChars;
  336. TCHAR szCmdLineT[MAX_PATH];
  337. lstrcpy(szCmdLineT, szCmdLine);
  338. dwChars = SHExpandEnvironmentStrings(szCmdLineT,
  339. szCmdLine,
  340. ARRAYSIZE(szCmdLine));
  341. if ((dwChars == 0) || (dwChars > ARRAYSIZE(szCmdLine)))
  342. {
  343. // bail on this value if we failed the expansion, or if the string is > MAX_PATH
  344. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: expansion of '%s' in <%s> failed or is too long", szCmdLineT, pszSubkey);
  345. continue;
  346. }
  347. }
  348. TraceMsg(TF_GENERAL, "Cabinet_EnumRegApps: subkey = %s cmdline = %s", pszSubkey, szCmdLine);
  349. if (g_fCleanBoot && (szValueName[0] != TEXT('*')))
  350. {
  351. // only run things marked with a "*" in when in SafeMode
  352. continue;
  353. }
  354. // We used to execute each entry, wait for it to finish, and then make the next call to
  355. // RegEnumValue(). The problem with this is that some apps add themselves back to the runonce
  356. // after they are finished (test harnesses that reboot machines and want to be restarted) and
  357. // we dont want to delete them, so we snapshot the registry keys and execute them after we
  358. // have finished the enum.
  359. prai = (REGAPP_INFO *)LocalAlloc(LPTR, sizeof(REGAPP_INFO));
  360. if (prai)
  361. {
  362. lstrcpy(prai->szSubkey, pszSubkey);
  363. lstrcpy(prai->szValueName, szValueName);
  364. lstrcpy(prai->szCmdLine, szCmdLine);
  365. if (!hdpaEntries)
  366. {
  367. hdpaEntries = DPA_Create(5);
  368. }
  369. if (!hdpaEntries || (DPA_AppendPtr(hdpaEntries, prai) == -1))
  370. {
  371. LocalFree(prai);
  372. }
  373. }
  374. }
  375. }
  376. if (hdpaEntries)
  377. {
  378. int iIndex;
  379. int iTotal = DPA_GetPtrCount(hdpaEntries);
  380. for (iIndex = 0; iIndex < iTotal; iIndex++)
  381. {
  382. REGAPP_INFO* prai = (REGAPP_INFO*)DPA_GetPtr(hdpaEntries, iIndex);
  383. ASSERT(prai);
  384. // NB Things marked with a '!' mean delete after
  385. // the CreateProcess not before. This is to allow
  386. // certain apps (runonce.exe) to be allowed to rerun
  387. // to if the machine goes down in the middle of execing
  388. // them. Be very afraid of this switch.
  389. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] != TEXT('!')))
  390. {
  391. // This delete can fail if the user doesn't have the privilege
  392. if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS)
  393. {
  394. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: skipping entry %s (cannot delete the value)", prai->szValueName);
  395. LocalFree(prai);
  396. continue;
  397. }
  398. }
  399. pfnCallback(prai->szSubkey, prai->szCmdLine, fFlags, lParam);
  400. // Post delete '!' things.
  401. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] == TEXT('!')))
  402. {
  403. // This delete can fail if the user doesn't have the privilege
  404. if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS)
  405. {
  406. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot delete the value %s ", prai->szValueName);
  407. }
  408. }
  409. LocalFree(prai);
  410. }
  411. DPA_Destroy(hdpaEntries);
  412. hdpaEntries = NULL;
  413. }
  414. RegCloseKey(hkey);
  415. }
  416. else
  417. {
  418. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: failed to open subkey %s !", pszSubkey);
  419. bRet = FALSE;
  420. }
  421. if (g_fEndSession)
  422. {
  423. // NOTE: this is for explorer only, other consumers of runonce.c must declare g_fEndSession but leave
  424. // it set to FALSE always.
  425. // if we rx'd a WM_ENDSESSION whilst running any of these keys we must exit the process.
  426. ExitProcess(0);
  427. }
  428. return bRet;
  429. }
  430. STDAPI_(BOOL) ExecuteRegAppEnumProc(LPCTSTR szSubkey, LPCTSTR szCmdLine, RRA_FLAGS fFlags, LPARAM lParam)
  431. {
  432. BOOL bRet;
  433. RRA_FLAGS flagsTemp = fFlags;
  434. BOOL fInTSInstallMode = FALSE;
  435. #if (_WIN32_WINNT >= 0x0500)
  436. // In here, We only attempt TS specific in app-install-mode
  437. // if RunOnce entries are being processed
  438. if (0 == lstrcmpi(szSubkey, REGSTR_PATH_RUNONCE))
  439. {
  440. fInTSInstallMode = _TryHydra(szCmdLine, &flagsTemp);
  441. }
  442. #endif
  443. bRet = ShellExecuteRegApp(szCmdLine, flagsTemp);
  444. #if (_WIN32_WINNT >= 0x0500)
  445. if (fInTSInstallMode)
  446. {
  447. SHSetTermsrvAppInstallMode(FALSE);
  448. }
  449. #endif
  450. return bRet;
  451. }