// // runonce.c (shared runonce code between explorer.exe and runonce.exe) // #include #if (_WIN32_WINNT >= 0x0500) // stolen from #define TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS 0x00008000 #define CompatibilityApp 1 typedef LONG TERMSRV_COMPATIBILITY_CLASS; typedef BOOL (* PFNGSETTERMSRVAPPINSTALLMODE)(BOOL bState); typedef BOOL (* PFNGETTERMSRVCOMPATFLAGSEX)(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc); // even though this function is in kernel32.lib, we need to have a LoadLibrary/GetProcAddress // thunk for downlevel components who include this STDAPI_(BOOL) SHSetTermsrvAppInstallMode(BOOL bState) { static PFNGSETTERMSRVAPPINSTALLMODE pfn = NULL; if (pfn == NULL) { // kernel32 should already be loaded HMODULE hmod = GetModuleHandle(TEXT("kernel32.dll")); if (hmod) { pfn = (PFNGSETTERMSRVAPPINSTALLMODE)GetProcAddress(hmod, "SetTermsrvAppInstallMode"); } else { pfn = (PFNGSETTERMSRVAPPINSTALLMODE)-1; } } if (pfn && (pfn != (PFNGSETTERMSRVAPPINSTALLMODE)-1)) { return pfn(bState); } else { return FALSE; } } STDAPI_(ULONG) SHGetTermsrCompatFlagsEx(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc) { static PFNGETTERMSRVCOMPATFLAGSEX pfn = NULL; if (pfn == NULL) { HMODULE hmod = LoadLibrary(TEXT("TSAppCMP.DLL")); if (hmod) { pfn = (PFNGETTERMSRVCOMPATFLAGSEX)GetProcAddress(hmod, "GetTermsrCompatFlagsEx"); } else { pfn = (PFNGETTERMSRVCOMPATFLAGSEX)-1; } } if (pfn && (pfn != (PFNGETTERMSRVCOMPATFLAGSEX)-1)) { return pfn(pwszApp, pdwFlags, tscc); } else { *pdwFlags = 0; return 0; } } HANDLE SetJobCompletionPort(HANDLE hJob) { HANDLE hRet = NULL; HANDLE hIOPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); if (hIOPort != NULL) { JOBOBJECT_ASSOCIATE_COMPLETION_PORT CompletionPort; CompletionPort.CompletionKey = hJob ; CompletionPort.CompletionPort = hIOPort; if (SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &CompletionPort, sizeof(CompletionPort))) { hRet = hIOPort; } else { CloseHandle(hIOPort); } } return hRet; } STDAPI_(DWORD) WaitingThreadProc(void *pv) { HANDLE hIOPort = (HANDLE)pv; if (hIOPort) { while (TRUE) { DWORD dwCompletionCode; ULONG_PTR pCompletionKey; LPOVERLAPPED pOverlapped; if (!GetQueuedCompletionStatus(hIOPort, &dwCompletionCode, &pCompletionKey, &pOverlapped, INFINITE) || (dwCompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) { break; } } } return 0; } // // The following handles running an application and optionally waiting for it // to all the child procs to terminate. This is accomplished thru Kernel Job Objects // which is only available in NT5 // BOOL _CreateRegJob(LPCTSTR pszCmd, BOOL bWait) { BOOL bRet = FALSE; HANDLE hJobObject = CreateJobObjectW(NULL, NULL); if (hJobObject) { HANDLE hIOPort = SetJobCompletionPort(hJobObject); if (hIOPort) { DWORD dwID; HANDLE hThread = CreateThread(NULL, 0, WaitingThreadProc, (void*)hIOPort, CREATE_SUSPENDED, &dwID); if (hThread) { PROCESS_INFORMATION pi = {0}; STARTUPINFO si = {0}; UINT fMask = SEE_MASK_FLAG_NO_UI; DWORD dwCreationFlags = CREATE_SUSPENDED; TCHAR sz[MAX_PATH * 2]; wnsprintf(sz, ARRAYSIZE(sz), TEXT("RunDLL32.EXE Shell32.DLL,ShellExec_RunDLL ?0x%X?%s"), fMask, pszCmd); si.cb = sizeof(si); if (CreateProcess(NULL, sz, NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &si, &pi)) { if (AssignProcessToJobObject(hJobObject, pi.hProcess)) { // success! bRet = TRUE; ResumeThread(pi.hThread); ResumeThread(hThread); if (bWait) { SHProcessMessagesUntilEvent(NULL, hThread, INFINITE); } } else { TerminateProcess(pi.hProcess, ERROR_ACCESS_DENIED); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } if (!bRet) { TerminateThread(hThread, ERROR_ACCESS_DENIED); } CloseHandle(hThread); } CloseHandle(hIOPort); } CloseHandle(hJobObject); } return bRet; } BOOL _TryHydra(LPCTSTR pszCmd, RRA_FLAGS *pflags) { // See if the terminal-services is enabled in "Application Server" mode if (IsOS(OS_TERMINALSERVER) && SHSetTermsrvAppInstallMode(TRUE)) { WCHAR sz[MAX_PATH]; *pflags |= RRA_WAIT; // Changing timing blows up IE 4.0, but IE5 is ok! // we are on a TS machine, NT version 4 or 5, with admin priv // see if the app-compatability flag is set for this executable // to use the special job-objects for executing module // get the module name, without the arguments if (0 < PathProcessCommand(pszCmd, sz, ARRAYSIZE(sz), PPCF_NODIRECTORIES)) { ULONG ulCompat; SHGetTermsrCompatFlagsEx(sz, &ulCompat, CompatibilityApp); // if the special flag for this module-name is set... if (ulCompat & TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS) { *pflags |= RRA_USEJOBOBJECTS; } } return TRUE; } return FALSE; } #endif // (_WIN32_WINNT >= 0x0500) // // On success: returns process handle or INVALID_HANDLE_VALUE if no process // was launched (i.e., launched via DDE). // On failure: returns INVALID_HANDLE_VALUE. // BOOL _ShellExecRegApp(LPCTSTR pszCmd, BOOL fNoUI, BOOL fWait) { TCHAR szQuotedCmdLine[MAX_PATH+2]; LPTSTR pszArgs; SHELLEXECUTEINFO ei = {0}; // Gross, but if the process command fails, copy the command line to let // shell execute report the errors if (PathProcessCommand((LPWSTR)pszCmd, (LPWSTR)szQuotedCmdLine, ARRAYSIZE(szQuotedCmdLine), PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1) { lstrcpy(szQuotedCmdLine, pszCmd); } pszArgs= PathGetArgs(szQuotedCmdLine); if (*pszArgs) { // Strip args *(pszArgs - 1) = 0; } PathUnquoteSpaces(szQuotedCmdLine); ei.cbSize = sizeof(SHELLEXECUTEINFO); ei.lpFile = szQuotedCmdLine; ei.lpParameters = pszArgs; ei.nShow = SW_SHOWNORMAL; ei.fMask = SEE_MASK_NOCLOSEPROCESS; if (fNoUI) { ei.fMask |= SEE_MASK_FLAG_NO_UI; } if (ShellExecuteEx(&ei)) { if (ei.hProcess) { if (fWait) { SHProcessMessagesUntilEvent(NULL, ei.hProcess, INFINITE); } CloseHandle(ei.hProcess); } return TRUE; } else { return FALSE; } } // The following handles running an application and optionally waiting for it // to terminate. STDAPI_(BOOL) ShellExecuteRegApp(LPCTSTR pszCmdLine, RRA_FLAGS fFlags) { BOOL bRet = FALSE; if (!pszCmdLine || !*pszCmdLine) { // Don't let empty strings through, they will endup doing something dumb // like opening a command prompt or the like return bRet; } #if (_WIN32_WINNT >= 0x0500) if (fFlags & RRA_USEJOBOBJECTS) { bRet = _CreateRegJob(pszCmdLine, fFlags & RRA_WAIT); } #endif if (!bRet) { // fallback if necessary. bRet = _ShellExecRegApp(pszCmdLine, fFlags & RRA_NOUI, fFlags & RRA_WAIT); } return bRet; } STDAPI_(BOOL) Cabinet_EnumRegApps(HKEY hkeyParent, LPCTSTR pszSubkey, RRA_FLAGS fFlags, PFNREGAPPSCALLBACK pfnCallback, LPARAM lParam) { HKEY hkey; BOOL bRet = TRUE; // With the addition of the ACL controlled "policy" run keys RegOpenKey // might fail on the pszSubkey. Use RegOpenKeyEx with MAXIMIM_ALLOWED // to ensure that we successfully open the subkey. if (RegOpenKeyEx(hkeyParent, pszSubkey, 0, MAXIMUM_ALLOWED, &hkey) == ERROR_SUCCESS) { DWORD cbValue; DWORD dwType; DWORD i; TCHAR szValueName[80]; TCHAR szCmdLine[MAX_PATH]; HDPA hdpaEntries = NULL; #ifdef DEBUG // // we only support named values so explicitly purge default values // LONG cbData = sizeof(szCmdLine); if (RegQueryValue(hkey, NULL, szCmdLine, &cbData) == ERROR_SUCCESS) { ASSERTMSG((cbData <= 2), "Cabinet_EnumRegApps: BOGUS default entry in <%s> '%s'", pszSubkey, szCmdLine); RegDeleteValue(hkey, NULL); } #endif // now enumerate all of the values. for (i = 0; !g_fEndSession ; i++) { LONG lEnum; DWORD cbData; cbValue = ARRAYSIZE(szValueName); cbData = sizeof(szCmdLine); lEnum = RegEnumValue(hkey, i, szValueName, &cbValue, NULL, &dwType, (LPBYTE)szCmdLine, &cbData); if (ERROR_MORE_DATA == lEnum) { // ERROR_MORE_DATA means the value name or data was too large // skip to the next item TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot run oversize entry '%s' in <%s>", szValueName, pszSubkey); continue; } else if (lEnum != ERROR_SUCCESS) { if (lEnum != ERROR_NO_MORE_ITEMS) { // we hit some kind of registry failure bRet = FALSE; } break; } if ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) { REGAPP_INFO * prai; if (dwType == REG_EXPAND_SZ) { DWORD dwChars; TCHAR szCmdLineT[MAX_PATH]; lstrcpy(szCmdLineT, szCmdLine); dwChars = SHExpandEnvironmentStrings(szCmdLineT, szCmdLine, ARRAYSIZE(szCmdLine)); if ((dwChars == 0) || (dwChars > ARRAYSIZE(szCmdLine))) { // bail on this value if we failed the expansion, or if the string is > MAX_PATH TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: expansion of '%s' in <%s> failed or is too long", szCmdLineT, pszSubkey); continue; } } TraceMsg(TF_GENERAL, "Cabinet_EnumRegApps: subkey = %s cmdline = %s", pszSubkey, szCmdLine); if (g_fCleanBoot && (szValueName[0] != TEXT('*'))) { // only run things marked with a "*" in when in SafeMode continue; } // We used to execute each entry, wait for it to finish, and then make the next call to // RegEnumValue(). The problem with this is that some apps add themselves back to the runonce // after they are finished (test harnesses that reboot machines and want to be restarted) and // we dont want to delete them, so we snapshot the registry keys and execute them after we // have finished the enum. prai = (REGAPP_INFO *)LocalAlloc(LPTR, sizeof(REGAPP_INFO)); if (prai) { lstrcpy(prai->szSubkey, pszSubkey); lstrcpy(prai->szValueName, szValueName); lstrcpy(prai->szCmdLine, szCmdLine); if (!hdpaEntries) { hdpaEntries = DPA_Create(5); } if (!hdpaEntries || (DPA_AppendPtr(hdpaEntries, prai) == -1)) { LocalFree(prai); } } } } if (hdpaEntries) { int iIndex; int iTotal = DPA_GetPtrCount(hdpaEntries); for (iIndex = 0; iIndex < iTotal; iIndex++) { REGAPP_INFO* prai = (REGAPP_INFO*)DPA_GetPtr(hdpaEntries, iIndex); ASSERT(prai); // NB Things marked with a '!' mean delete after // the CreateProcess not before. This is to allow // certain apps (runonce.exe) to be allowed to rerun // to if the machine goes down in the middle of execing // them. Be very afraid of this switch. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] != TEXT('!'))) { // This delete can fail if the user doesn't have the privilege if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS) { TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: skipping entry %s (cannot delete the value)", prai->szValueName); LocalFree(prai); continue; } } pfnCallback(prai->szSubkey, prai->szCmdLine, fFlags, lParam); // Post delete '!' things. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] == TEXT('!'))) { // This delete can fail if the user doesn't have the privilege if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS) { TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot delete the value %s ", prai->szValueName); } } LocalFree(prai); } DPA_Destroy(hdpaEntries); hdpaEntries = NULL; } RegCloseKey(hkey); } else { TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: failed to open subkey %s !", pszSubkey); bRet = FALSE; } if (g_fEndSession) { // NOTE: this is for explorer only, other consumers of runonce.c must declare g_fEndSession but leave // it set to FALSE always. // if we rx'd a WM_ENDSESSION whilst running any of these keys we must exit the process. ExitProcess(0); } return bRet; } STDAPI_(BOOL) ExecuteRegAppEnumProc(LPCTSTR szSubkey, LPCTSTR szCmdLine, RRA_FLAGS fFlags, LPARAM lParam) { BOOL bRet; RRA_FLAGS flagsTemp = fFlags; BOOL fInTSInstallMode = FALSE; #if (_WIN32_WINNT >= 0x0500) // In here, We only attempt TS specific in app-install-mode // if RunOnce entries are being processed if (0 == lstrcmpi(szSubkey, REGSTR_PATH_RUNONCE)) { fInTSInstallMode = _TryHydra(szCmdLine, &flagsTemp); } #endif bRet = ShellExecuteRegApp(szCmdLine, flagsTemp); #if (_WIN32_WINNT >= 0x0500) if (fInTSInstallMode) { SHSetTermsrvAppInstallMode(FALSE); } #endif return bRet; }