546 lines
16 KiB
C
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;
|
||
|
}
|