windows-nt/Source/XPSP1/NT/shell/inc/runonce.c
2020-09-26 16:20:57 +08:00

546 lines
16 KiB
C

//
// runonce.c (shared runonce code between explorer.exe and runonce.exe)
//
#include <runonce.h>
#if (_WIN32_WINNT >= 0x0500)
// stolen from <tsappcmp.h>
#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;
}