|
|
//+----------------------------------------------------------------------------
//
// Scheduling Agent Service
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1996.
//
// File: runjob.cxx
//
// Contents: Functions to run the target file.
//
// History: 02-Jul-96 EricB created
//
//-----------------------------------------------------------------------------
#include "..\pch\headers.hxx"
#pragma hdrstop
#include <lmerr.h> // NERR_Success
#include <dsgetdc.h> // DsGetDcName
#include <lmaccess.h> // NetUserGetInfo
#include <lmapibuf.h> // NetApiBufferFree
#include <netevent.h> // for logging to event log
#include "globals.hxx"
#include "svc_core.hxx"
#include "..\inc\resource.h"
#include "path.hxx"
#include "..\inc\common.hxx"
#include "..\inc\runobj.hxx"
#include <wtsapi32.h>
HRESULT ComposeBatchParam( LPCTSTR pwszPrefix, LPCTSTR wszAppPathName, LPCTSTR pwszParameters, LPTSTR * ppwszCmdLine );
HRESULT ComposeParam(BOOL fTargetIsExe, LPTSTR ptszRunTarget, LPTSTR ptszParameters, LPTSTR * pptszCmdLine);
#include <userenv.h> // LoadUserProfile
BOOL AllowInteractiveServices(void); BOOL LogonAccount( LPCWSTR pwszJobFile, CJob * pJob, DWORD * pdwErrorMsg, HRESULT * pdwSpecificError, //CRun * pRun,
HANDLE * phToken, BOOL * pfTokenIsShellToken, LPWSTR * ppwsz, HANDLE * phUserProfile); HANDLE LoadAccountProfile( HANDLE hToken, LPCWSTR pwszUser, LPCWSTR pwszDomain);
BOOL GetUserTokenFromSession( LPTSTR lpszUsername, LPTSTR lpszDomain, PHANDLE phUserToken );
DWORD SchedUPNToAccountName( IN LPCWSTR lpUPN, OUT LPWSTR *ppAccountName);
void InitializeStartupInfo( CJob * pJob, LPTSTR ptszDesktop, STARTUPINFO * psi); HRESULT MapFindExecutableError(HINSTANCE hRet); BOOL WaitForStubExe(HANDLE hProcess);
#define WSZ_INTERACTIVE_DESKTOP L"WinSta0\\Default"
#define WSZ_SA_DESKTOP L"SAWinSta\\SADesktop"
#define CMD_PREFIX TEXT("cmd.exe /c ")
#define STUB_PREFIX L"RUNDLL32.EXE Shell32.DLL,ShellExec_RunDLL ?0x400?"
// 0x400 is SEE_MASK_FLAG_NO_UI
#define USERNAME L"USERNAME"
#define USERDOMAIN L"USERDOMAIN"
#define USERPROFILE L"USERPROFILE"
#define DQUOTE TEXT("\"")
#define SPACE TEXT(" ")
//+----------------------------------------------------------------------------
//
// Member: CSchedWorker::RunNTJob
//
// Synopsis: Run an NT Job.
//
// Arguments: [pJob] - the job object to be run.
// [pRun] - the run information object.
// [phrRet] - a place to return launch failure info.
// [pdwErrMsgID] - message ID for failure reporting.
//
// Returns: S_OK - if job launched.
// S_FALSE - if job not launched.
// HRESULT - other, fatal, error.
//
//-----------------------------------------------------------------------------
HRESULT CSchedWorker::RunNTJob(CJob * pJob, CRun * pRun, HRESULT * phrRet, DWORD * pdwErrMsgID) { LPWSTR pwszRunTarget, pwszWorkingDir = pJob->m_pwszWorkingDirectory, pwszParameters = pJob->m_pwszParameters; DWORD cch; BOOL fRanJob = FALSE; LPTSTR ptszDesktop = NULL; HANDLE hImpersonationHandle = NULL; HANDLE hToken = NULL; BOOL fTokenIsShellToken = FALSE; HANDLE hUserProfile = NULL; BOOL fTargetIsExe = FALSE; BOOL fTargetIsBinaryExe = FALSE; BOOL fUseStubExe = FALSE;
*pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
size_t cchBuff = lstrlen(pJob->m_pwszApplicationName) + 1; pwszRunTarget = new WCHAR[cchBuff];
if (pwszRunTarget == NULL) { LogServiceError(IDS_NON_FATAL_ERROR, ERROR_OUTOFMEMORY, IDS_HELP_HINT_CLOSE_APPS); ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY); return E_OUTOFMEMORY; }
StringCchCopy(pwszRunTarget, cchBuff, pJob->m_pwszApplicationName);
schDebugOut((DEB_TRACE, "*** Running job %S\n", pJob->m_ptszFileName)); schDebugOut((DEB_USER3, "*** with MaxRunTime of %u\n", pJob->m_dwMaxRunTime));
HRESULT hr = S_OK; WCHAR wszAppPathName[MAX_PATH + 1]; LPWSTR pwszCmdLine = NULL;
//
// Logon the account associated with the job and set the security token
// and desktop appropriately based on several factors: is this an AT job,
// is the account the same as the currently logged-on user, etc.
//
if (!LogonAccount(pJob->m_ptszFileName, pJob, pdwErrMsgID, phrRet, //pRun,
&hToken, &fTokenIsShellToken, &ptszDesktop, &hUserProfile)) { hr = S_FALSE; goto cleanup; }
if( NULL != ptszDesktop ) { hr = pRun->SetDesktop( _tcschr( ptszDesktop, '\\' ) + 1 ); if( FAILED( hr ) ) { LogServiceError(IDS_NON_FATAL_ERROR, ERROR_OUTOFMEMORY, IDS_HELP_HINT_CLOSE_APPS); ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY); goto cleanup; }
TCHAR ptszStation[MAX_PATH];
SecureZeroMemory( ptszStation, sizeof(ptszStation) );
wcsncpy( ptszStation, ptszDesktop, ( _tcschr( ptszDesktop, '\\' ) - ptszDesktop ) );
hr = pRun->SetStation( ptszStation );
if( FAILED( hr ) ) { LogServiceError(IDS_NON_FATAL_ERROR, ERROR_OUTOFMEMORY, IDS_HELP_HINT_CLOSE_APPS); ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY); goto cleanup; } }
//
// NOTE: After this point, if fTokenIsShellToken is TRUE, we must leave
// gUserLogonInfo.CritSection.
//
//
// For all but AT jobs, impersonate the user to ensure the user
// gets access checked correctly on the file executed.
//
hImpersonationHandle = NULL;
if (!pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE)) { if (fTokenIsShellToken) { hImpersonationHandle = ImpersonateLoggedInUser(); } else { hImpersonationHandle = ImpersonateUser(hToken, hImpersonationHandle); }
if (hImpersonationHandle == NULL) { *phrRet = 0; *pdwErrMsgID = IDS_ACCOUNT_LOGON_FAILED; hr = S_FALSE; goto cleanup2; } }
//
// Change to the job's working dir before searching for the
// executable.
//
if (pwszWorkingDir != NULL) { if (!SetCurrentDirectory(pwszWorkingDir)) { //
// An invalid working directory may not prevent the job from
// running, so this is not a fatal error. Log it though, to
// inform the user.
//
TCHAR tszExeName[MAX_PATH + 1];
GetExeNameFromCmdLine(pJob->GetCommand(), MAX_PATH + 1, tszExeName);
LogTaskError(pRun->GetName(), tszExeName, IDS_LOG_SEVERITY_WARNING, IDS_LOG_JOB_WARNING_BAD_DIR, NULL, GetLastError(), IDS_HELP_HINT_BADDIR);
//
// Set the pointer to NULL so that CreateProcess will ignore it.
//
pwszWorkingDir = NULL; } }
//
// Check if the run target has an extension and determine if the run
// target is a program, a batch or command file (.bat or .cmd), or a
// document. If there is no extension, then it is assumed that it is a
// program.
//
WCHAR* pExtension = PathFindExtension(pwszRunTarget);
if (*pExtension == '\0') { fTargetIsExe = TRUE; fTargetIsBinaryExe = TRUE; } else if (PathIsExe(pwszRunTarget)) { fTargetIsExe = TRUE;
if (PathIsBinaryExe(pwszRunTarget)) { fTargetIsBinaryExe = TRUE; } }
if (fTargetIsExe) { if (fTargetIsBinaryExe) { DBG_OUT("Job target is a binary executable"); } else { DBG_OUT("Job target is a batch file"); }
if (pwszRunTarget[1] == L':' || pwszRunTarget[1] == L'\\') { //
// If the second character is a colon or a backslash, then this
// must be a fully qualified path. If so, don't call SearchPath.
//
StringCchCopy(wszAppPathName, MAX_PATH + 1, pwszRunTarget); } else { //
// Build a full path name for the application.
//
DWORD cchFound;
cchFound = SearchPath(NULL, pwszRunTarget, DOTEXE, MAX_PATH + 1, wszAppPathName, NULL);
if (!cchFound || cchFound >= MAX_PATH) { //
// Error, cannot locate job target application. Note that this
// is not a fatal error (probably file-not-found) so
// processing will continue with other jobs in the list.
//
//
// phrRet and pdwErrMsgId are used by LogTaskError on return.
//
*phrRet = HRESULT_FROM_WIN32(GetLastError()); *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
hr = S_FALSE; goto cleanup3; } }
if (fTargetIsBinaryExe) { schDebugOut((DEB_ITRACE, "*** Running '%S'\n", wszAppPathName)); } } else { DBG_OUT("Job target is a document");
HINSTANCE hRet = FindExecutable(pwszRunTarget, pwszWorkingDir, wszAppPathName); if (hRet == (HINSTANCE)31) { //
// No association found. Try using rundll32.exe with ShellExecute
// to run the document.
//
fUseStubExe = TRUE;
fTargetIsExe = TRUE; fTargetIsBinaryExe = FALSE;
StringCchCopy(wszAppPathName, MAX_PATH + 1, pwszRunTarget); } else if (hRet < (HINSTANCE)32) { //
// This is not a fatal error, so RunJobs will just log the failure
// and continue with any other pending jobs.
//
//
// phrRet and pdwErrMsgId are used by LogTaskError on return.
//
schDebugOut((DEB_ERROR, "FindExecutable FAILED with %d for '%ws'\n", hRet, pwszRunTarget)); *phrRet = MapFindExecutableError(hRet); *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
hr = S_FALSE; goto cleanup3; } else { //
// If running a document by association, the parameter property is
// ignored and the run target property is passed as the parameter.
//
pwszParameters = pwszRunTarget; pwszRunTarget = wszAppPathName;
schDebugOut((DEB_ITRACE, "*** Running '%S'\n", pwszParameters)); } }
if (fTargetIsExe && !fTargetIsBinaryExe) { hr = ComposeBatchParam(fUseStubExe ? STUB_PREFIX : CMD_PREFIX, wszAppPathName, pwszParameters, &pwszCmdLine);
if (FAILED(hr)) { goto cleanup3; }
schDebugOut((DEB_ITRACE, "*** Running batch file '%S'\n", pwszCmdLine)); } else { //
// Add the app name as the first token of the command line param.
//
if (pwszParameters != NULL) { hr = ComposeParam(fTargetIsExe, pwszRunTarget, pwszParameters, &pwszCmdLine);
if (FAILED(hr)) { goto cleanup3; }
schDebugOut((DEB_ITRACE, "*** With cmd line '%S'\n", pwszCmdLine)); } }
STARTUPINFO startupinfo;
InitializeStartupInfo(pJob, ptszDesktop, &startupinfo);
if (pJob->IsFlagSet(TASK_FLAG_HIDDEN)) { startupinfo.wShowWindow = SW_HIDE; }
//
// Modify the path if the application has an app path registry entry
//
BOOL fChangedPath; LPWSTR pwszSavedPath;
fChangedPath = SetAppPath(wszAppPathName, &pwszSavedPath);
//
// Launch job.
//
// NB : Must call CreateProcess when the token handle is
// NULL (in the case of AT jobs running as local system),
// since CreateProcessAsUser rejects NULL handles.
// Alternatively, OpenProcessToken could be used,
// but then we have to deal with the failure cases,
// logging appropriate errors, closing the token
// handle, etc.
//
HANDLE hProcess = NULL; HANDLE hThread = NULL; DWORD dwProcessId = 0;
if (hToken == NULL) { PROCESS_INFORMATION processinfo; ZeroMemory(&processinfo, sizeof(PROCESS_INFORMATION));
fRanJob = CreateProcess((fTargetIsExe && !fTargetIsBinaryExe) ? NULL : wszAppPathName, pwszCmdLine, NULL, NULL, FALSE, pJob->m_dwPriority | CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM, NULL, pwszWorkingDir, &startupinfo, &processinfo);
hProcess = processinfo.hProcess; hThread = processinfo.hThread; dwProcessId = processinfo.dwProcessId; } else { LPVOID lpEnvironment;
//
// Launch the job with the appropriate environment
//
schDebugOut((DEB_ITRACE, "Calling CreateEnvironmentBlock...\n")); if (!CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE)) { ERR_OUT("CreateEnvironmentBlock", GetLastError()); lpEnvironment = NULL; } else { schDebugOut((DEB_ITRACE, "... CreateEnvironmentBlock succeded\n")); }
PROCESS_INFORMATION processinfo; ZeroMemory(&processinfo, sizeof(PROCESS_INFORMATION));
fRanJob = CreateProcessAsUser(hToken, (fTargetIsExe && !fTargetIsBinaryExe) ? NULL : wszAppPathName, pwszCmdLine, NULL, NULL, FALSE, pJob->m_dwPriority | CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, pwszWorkingDir, &startupinfo, &processinfo);
hProcess = processinfo.hProcess; hThread = processinfo.hThread; dwProcessId = processinfo.dwProcessId;
//
// DestroyEnvironmentBlock handles NULL
//
DestroyEnvironmentBlock(lpEnvironment); }
if (fRanJob && fUseStubExe) { //
// If we launched the stub exe, we must wait for it to exit, and check
// its exit code.
//
fRanJob = WaitForStubExe(hProcess);
//
// It's not interesting to copy info about the stub exe into pRun
//
CloseHandle(hProcess); CloseHandle(hThread); hProcess = NULL; dwProcessId = NULL; }
if (fRanJob) { //
// Successfully launched job.
//
hr = S_OK;
pRun->SetHandle(hProcess); pRun->SetProcessId(dwProcessId);
if (fUseStubExe) { pRun->ClearFlag(RUN_STATUS_RUNNING); // was set by SetHandle
fRanJob = FALSE;
// HMH: okay, I don't like this logic, but that's the way it worked
// when I got here. It seems like the process launched by the stub
// would want the user profile, etc, available...
// ... but then we wouldn't know when to close it!
if (hUserProfile) UnloadUserProfile(hToken, hUserProfile);
if (hToken && !fTokenIsShellToken) CloseHandle(hToken); } else { CloseHandle(hThread); pRun->SetProfileHandles(hToken, hUserProfile, !fTokenIsShellToken); } } else { //
// Job launch failed.
//
hr = S_FALSE;
// so, let's clean up - shall we?
if (hUserProfile) UnloadUserProfile(hToken, hUserProfile);
if (hToken && !fTokenIsShellToken) CloseHandle(hToken);
//
// phrRet and pdwErrMsgId are used by LogTaskError on return.
//
*phrRet = HRESULT_FROM_WIN32(GetLastError()); *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START; schDebugOut((DEB_ERROR, "*** CreateProcess for job %S failed, %lu\n", pJob->m_ptszFileName, GetLastError())); }
if (fChangedPath) { SetEnvironmentVariable(L"PATH", pwszSavedPath); delete [] pwszSavedPath; pwszSavedPath = NULL; }
//
// If impersonating, stop.
//
cleanup3:
if (!pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE)) { StopImpersonating(hImpersonationHandle, !fTokenIsShellToken); }
cleanup2:
if (fTokenIsShellToken) { LeaveCriticalSection(gUserLogonInfo.CritSection); }
cleanup:
//
// If the job ran successfully, then the CRun object pointed to by pRun
// has the profile and user tokens and will release them when the job
// quits.
//
// Change back to the service's working directory.
//
if (pwszWorkingDir != NULL) { if (!SetCurrentDirectory(m_ptszSvcDir)) { LogServiceError(IDS_NON_FATAL_ERROR, GetLastError(), 0); ERR_OUT("RunJobs: changing back to the service's directory", HRESULT_FROM_WIN32(GetLastError())); } }
if (pwszRunTarget != wszAppPathName) { delete [] pwszRunTarget; } else { delete [] pwszParameters; }
if (pwszCmdLine != NULL) { delete [] pwszCmdLine; }
return hr; }
//+----------------------------------------------------------------------------
//
// Function: LogonAccount
//
// Synopsis: Retrieve the account information associated with the job
// and logon.
//
// Non-AT jobs:
// Account == Current logged on user:
// If the logon succeeds and the account matches that of the
// currently logged on user, return the shell security token
// to be used with CreateProcessAsUser. This enables jobs to
// show up on the user's desktop.
//
// Account != Current logged on user or no user logged on:
// If the logon succeeds, but the currently logged on user is
// different than the account, or there is no-one logged on,
// return the account token. Also return the scheduling agent's
// desktop name in the desktop return argument so this job can
// run on it.
//
// AT jobs:
// Ensure the AT job owner is an administrator and return an
// account token. AT jobs never get the shell token, since
// that's how the original schedule service worked. Also
// return the desktop name, "WinSta0\Default".
//
// Arguments: [pwszJobFile] -- Path to the job object file.
// [pJob] -- Job object to execute under the
// associated account.
// [pdwErrorMsg] -- SCHED_E error message on error.
// [pdwSpecificError] -- HRESULT on error.
// [phToken] -- Returned token.
// [pfTokenIsShellToken] -- If TRUE, the token returned is the
// shell's.
// [ppwszDesktop] -- Desktop to launch the job on.
// [phUserProfile] -- user profile token
//
// Returns: TRUE -- Logon successful.
// FALSE -- Logon failure.
//
// Notes: DO NOT delete:
// *pptszDestkop. If non-NULL, it refers to a static string.
// *phToken if *pfTokenIsShellToken == TRUE. This token
// cannot be duplicated. You delete it and you've got
// problems.
// If *pfTokenIsShellToken, the logon session critical section
// has been entered! Right after the call to CreateProcessAsUser
// leave this critical section if this flag value is TRUE.
//
//-----------------------------------------------------------------------------
BOOL LogonAccount(LPCWSTR pwszJobFile, CJob * pJob, DWORD * pdwErrorMsg, HRESULT * phrSpecificError, // CRun * pRun,
HANDLE * phToken, BOOL * pfTokenIsShellToken, LPWSTR * ppwszDesktop, HANDLE * phUserProfile) { JOB_CREDENTIALS jc; HANDLE hToken = NULL; HRESULT hr; WCHAR wszProfilePath[MAX_PATH+1] = L""; ULONG cchPath = ARRAY_LEN(wszProfilePath);
*pdwErrorMsg = 0; *phrSpecificError = S_OK; *phToken = NULL; *pfTokenIsShellToken = FALSE; *ppwszDesktop = NULL; *phUserProfile = NULL;
if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE)) { //
// Verify the job's signature.
//
if (! pJob->VerifySignature()) { *phrSpecificError = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); *pdwErrorMsg = IDS_FILE_ACCESS_DENIED; return(FALSE); }
*ppwszDesktop = WSZ_INTERACTIVE_DESKTOP;
hr = GetNSAccountInformation(&jc);
if (SUCCEEDED(hr)) { if (hr == S_OK) { if (!LogonUser(jc.wszAccount, jc.wszDomain, jc.wszPassword, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &hToken)) { *pdwErrorMsg = IDS_NS_ACCOUNT_LOGON_FAILED; *phrSpecificError = HRESULT_FROM_WIN32(GetLastError()); }
//
// Don't leave the plain-text password on the stack.
//
ZERO_PASSWORD(jc.wszPassword);
if (*phrSpecificError) { return(FALSE); }
// If the job was scheduled to run in any account other than LocalSystem account
if ((!jc.fIsPasswordNull) ||(jc.wszAccount[0] != L'\0')) { // If Fast User Switching is enabled and the task user
// is logged on in any of the sessions then use the session's
// user token in place of that obtained above so any UI associated
// with the job can show up on the user's desktop.
// If terminal serveice is running but FUS is disabled, WTSEnumerateSessions
// will return the only user logged on. If the username and domain name of that
// user matches with the jc.wszDomain and jc.wszAccount respectively, then
// GetUserTokenFromSession will return TRUE with the token of that user
HANDLE hSessionUserToken = INVALID_HANDLE_VALUE; BOOL bUserLoggedOn = GetUserTokenFromSession(jc.wszAccount,jc.wszDomain,&hSessionUserToken);
if(bUserLoggedOn) { schDebugOut((DEB_TRACE, "*** user session found\n"));
if (!jc.fIsPasswordNull) { // pRun->SetProfileHandles(hSessionUserToken, *phUserProfile);
*ppwszDesktop = WSZ_INTERACTIVE_DESKTOP; }
hToken = hSessionUserToken; } }
*phToken = hToken; *phUserProfile = LoadAccountProfile(hToken, jc.wszAccount, jc.wszDomain); } } else { CHECK_HRESULT(hr); *pdwErrorMsg = IDS_FAILED_NS_ACCOUNT_RETRIEVAL; *phrSpecificError = hr; return(FALSE); } } else { hr = GetAccountInformation(pJob->GetFileName(), &jc);
if (FAILED(hr)) { CHECK_HRESULT(hr); *pdwErrorMsg = IDS_FAILED_ACCOUNT_RETRIEVAL; *phrSpecificError = hr; return(FALSE); }
//
// If the job was set with a NULL password, we don't need to log it on.
//
if (jc.fIsPasswordNull) { //
// If the job was scheduled to run in the LocalSystem account
// (Accountname is the empty string), the NULL password is valid.
//
if (jc.wszAccount[0] == L'\0') { //
// It's LocalSystem, so we don't need to log on the account.
// Since the token is zeroed out above, this works
//
schDebugOut((DEB_TRACE, "Running %ws as LocalSystem\n", pJob->GetFileName())); *ppwszDesktop = WSZ_SA_DESKTOP; return(TRUE); } else { //
// It's not LocalSystem, so make sure this job has
// the appropriate flags for a NULL password set
//
if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON)) { schDebugOut((DEB_ERROR, "%ws is scheduled to run in " "a user account with a NULL password, but" " lacks TASK_FLAG_RUN_ONLY_IF_LOGGED_ON\n", pJob->GetFileName())); //
// Not a completely accurate error message, but since there
// is no UI for this task option, it's good enough.
//
*pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED; *phrSpecificError = SCHED_E_UNSUPPORTED_ACCOUNT_OPTION; } } } else { //
// If the name was stored as a UPN, convert it to a SAM name first.
//
if (jc.wszDomain[0] == L'\0') { LPWSTR pwszSamName; DWORD dwErr = SchedUPNToAccountName(jc.wszAccount, &pwszSamName); if (dwErr != NO_ERROR) { *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED; *phrSpecificError = HRESULT_FROM_WIN32(dwErr); CHECK_HRESULT(*phrSpecificError); } else { WCHAR * pSlash = wcschr(pwszSamName, L'\\'); schAssert(pSlash); *pSlash = L'\0'; StringCchCopy(jc.wszDomain, MAX_DOMAINNAME + 1, pwszSamName); StringCchCopy(jc.wszAccount, MAX_USERNAME + 1, pSlash + 1); delete pwszSamName; } }
if (SUCCEEDED(*phrSpecificError)) { if (!LogonUser(jc.wszAccount, jc.wszDomain, jc.wszPassword, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &hToken)) { *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED; *phrSpecificError = HRESULT_FROM_WIN32(GetLastError()); } } }
//
// Don't leave the plain-text password on the stack.
//
ZERO_PASSWORD(jc.wszPassword);
if (*phrSpecificError) { return(FALSE); }
//
// Load the user profile associated with the account just logged on.
// (If the user is already logged on, this will just increment the
// profile ref count, to be decremented when the job stops.)
// Don't bother doing this if the job is "run-only-if-logged-on", in
// which case it's OK to unload the profile when the user logs off.
//
if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON)) { *phToken = hToken; *phUserProfile = LoadAccountProfile(*phToken, jc.wszAccount, jc.wszDomain); }
// If Fast User Switching is enabled and the task user
// is logged on in any of the sessions then use the session's
// user token in place of that obtained above so any UI associated
// with the job can show up on the user's desktop.
// If terminal serveice is running but FUS is disabled, WTSEnumerateSessions
// will return the only user logged on. If the username and domain name of that
// user matches with the jc.wszDomain and jc.wszAccount respectively, then
// GetUserTokenFromSession will return TRUE with the token of that user
HANDLE hSessionUserToken = INVALID_HANDLE_VALUE; BOOL bUserLoggedOn = GetUserTokenFromSession(jc.wszAccount,jc.wszDomain,&hSessionUserToken); if(bUserLoggedOn) { schDebugOut((DEB_TRACE, "*** Terminal services running and user session found\n")); if (!jc.fIsPasswordNull) { *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP; }
// we're not passing this one out, close it
if (hToken) CloseHandle(hToken);
*phToken = hSessionUserToken; *pfTokenIsShellToken = FALSE; } else { schDebugOut((DEB_TRACE, "*** user session not found executing old code\n")); //
// Providing a user is logged on locally, is the account logged
// on above the same as that of the currently logged on user?
// If so, use the shell's security token in place of that
// obtained above so any UI associated with the job can
// show up on the user's desktop.
//
// ** Important **
//
// Only perform this check if the account logon succeeded
// above. Otherwise, it would be possible to specify an
// account name with an invalid password and have the job
// run anyway.
//
EnterCriticalSection(gUserLogonInfo.CritSection);
GetLoggedOnUser();
if (gUserLogonInfo.DomainUserName != NULL && _wcsicmp(jc.wszAccount, gUserLogonInfo.UserName) == 0 && _wcsicmp(jc.wszDomain, gUserLogonInfo.DomainName) == 0)
{ if (!jc.fIsPasswordNull) { *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP; }
// we're not passing this one out, so close it
if (hToken) CloseHandle(hToken);
*phToken = gUserLogonInfo.ShellToken; *pfTokenIsShellToken = TRUE; } else { LeaveCriticalSection(gUserLogonInfo.CritSection);
//
// Is this "run-only-if-logged-on"?
//
if (pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON)) { //
// The job is "run-only-if-logged-on" and the user is
// not currently logged on, so fail silently
//
schDebugOut((DEB_TRACE, "Not running %ws because user is not logged on\n", pJob->GetFileName())); *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED; // not really used
*phrSpecificError = S_FALSE; // suppress error logging
if (!jc.fIsPasswordNull) { CloseHandle(hToken); } return(FALSE); }
*phToken = hToken; *ppwszDesktop = WSZ_SA_DESKTOP; //
// Load the user profile associated with the account just logged on.
// (If the user is already logged on, this will just increment the
// profile ref count, to be decremented when the job stops.)
// Don't bother doing this if the job is "run-only-if-logged-on", in
// which case it's OK to unload the profile when the user logs off.
//
if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON) && (NULL == *phUserProfile)) { *phUserProfile = LoadAccountProfile(*phToken, jc.wszAccount, jc.wszDomain); } } } }
return(TRUE); }
//+---------------------------------------------------------------------------
//
// Function: LoadAccountProfile
//
// Synopsis: Attempt to load the profile for the specified user.
//
// Arguments: [hToken] - handle representing user
// [pwszUser] - user name
// [pwszDomain] - domain name
//
// Returns: Profile handle or NULL.
//
// History: 10-04-96 DavidMun Created
// 07-07-99 AnirudhS Rewrote to use NetUserGetInfo
//
// Notes: Returned profile handle must be closed with
// UnloadUserProfile(hToken, hUserProfile);
// CODEWORK Delay-load netapi32.dll.
//
//----------------------------------------------------------------------------
HANDLE LoadAccountProfile( HANDLE hToken, LPCWSTR pwszUser, LPCWSTR pwszDomain ) { schDebugOut((DEB_TRACE, "Loading profile for '%ws%'\\'%ws'\n", pwszDomain, pwszUser));
//
// Determine the server on which to look up the account info
// Skip this for for local accounts
// CODEWORK lstrcmpi won't work if pwszDomain is a DNS name.
//
PDOMAIN_CONTROLLER_INFO pDcInfo = NULL; LPWSTR pwszDC = NULL;
if (lstrcmpi(pwszDomain, gpwszComputerName) != 0) { DWORD err = DsGetDcName(NULL, pwszDomain, NULL, NULL, 0, &pDcInfo); if (err == NO_ERROR) { pwszDC = pDcInfo->DomainControllerName; } else { schDebugOut((DEB_ERROR, "DsGetDcName for '%ws' FAILED, %u\n", pwszDomain, err));
// continue anyway, using NULL as the server
} }
//
// Impersonate the user before calling NetUserGetInfo
//
if (!ImpersonateLoggedOnUser(hToken)) { ERR_OUT("ImpersonateLoggedOnUser", GetLastError()); }
//
// Look up the path to the profile for the account
//
LPUSER_INFO_3 pUserInfo = NULL;
NET_API_STATUS nerr = NetUserGetInfo(pwszDC, pwszUser, 3, (LPBYTE *) &pUserInfo); //
// Stop impersonating
//
if (!RevertToSelf()) { ERR_OUT("RevertToSelf", GetLastError()); }
if (nerr != NERR_Success) { schDebugOut((DEB_ERROR, "NetUserGetInfo on '%ws' for '%ws' FAILED, %u\n", pwszDC, pwszUser, nerr)); NetApiBufferFree(pDcInfo); SetLastError(nerr); return NULL; }
NetApiBufferFree(pDcInfo);
schDebugOut((DEB_USER3, "Profile path is '%ws'\n", pUserInfo->usri3_profile));
//
// LoadUserProfile changes our USERPROFILE environment variable, so save
// its value before calling LoadUserProfile
//
WCHAR wszOrigUserProfile[MAX_PATH + 1] = L"";
GetEnvironmentVariable(USERPROFILE, wszOrigUserProfile, ARRAY_LEN(wszOrigUserProfile));
//
// Load the profile
//
PROFILEINFO ProfileInfo;
SecureZeroMemory(&ProfileInfo, sizeof(ProfileInfo)); ProfileInfo.dwSize = sizeof(ProfileInfo); ProfileInfo.dwFlags = PI_NOUI; ProfileInfo.lpUserName = (LPWSTR) pwszUser; if (pUserInfo != NULL) { ProfileInfo.lpProfilePath = pUserInfo->usri3_profile; }
if (!LoadUserProfile(hToken, &ProfileInfo)) { schDebugOut((DEB_ERROR, "LoadUserProfile from '%ws' FAILED, %lu\n", ProfileInfo.lpProfilePath, GetLastError())); ProfileInfo.hProfile = NULL; }
NetApiBufferFree(pUserInfo);
//
// Restore environment variables changed by LoadUserProfile
//
SetEnvironmentVariable(USERPROFILE, wszOrigUserProfile);
return ProfileInfo.hProfile; }
//+----------------------------------------------------------------------------
//
// Function: AllowInteractiveServices
//
// Synopsis: Tests the NoInteractiveServices value in the Microsoft\Windows
// key. If the value is present and its value is non-zero return
// FALSE; return TRUE otherwise.
//
// Arguments: None.
//
// Returns: TRUE -- Allow interactive services.
// FALSE -- Disallow interactive services.
//
// Notes: None.
//
//-----------------------------------------------------------------------------
BOOL AllowInteractiveServices(void) { #define WINDOWS_REGISTRY_PATH L"System\\CurrentControlSet\\Control\\Windows"
#define NOINTERACTIVESERVICES L"NoInteractiveServices"
HKEY hKey; DWORD dwNoInteractiveServices, dwSize, dwType;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WINDOWS_REGISTRY_PATH, 0L, KEY_READ, &hKey) == ERROR_SUCCESS) { dwSize = sizeof(dwNoInteractiveServices);
if (RegQueryValueEx(hKey, NOINTERACTIVESERVICES, NULL, &dwType, (LPBYTE)&dwNoInteractiveServices, &dwSize) == ERROR_SUCCESS) { if (dwType == REG_DWORD) { return(dwNoInteractiveServices == 0); } }
RegCloseKey(hKey); }
//
// I really hate to have this be the default, but AT does this currently.
//
return(TRUE); }
//+----------------------------------------------------------------------------
//
// Function: InitializeStartupInfo
//
// Synopsis: Initialize the STARTUPINFO structure passed. If the job is
// an AT interactive job, set structure fields accordingly.
//
// Arguments: [pJob] -- Job object.
// [ptszDesktop] -- Desktop name.
// [psi] -- Structure to initialized.
//
// Returns: None.
//
// Notes: None.
//
//-----------------------------------------------------------------------------
void InitializeStartupInfo( CJob * pJob, LPTSTR ptszDesktop, STARTUPINFO * psi) { //
// NT only.
//
// Check if the job is to run interactively. Applicable only for AT jobs.
//
// If the job is an AT job AND
// if the interactive flag is set AND
// if the NoInteractiveServices is not set in the registry THEN
//
// initialize the STARTUPINFO struct such that the AT job will run
// interactively.
//
BOOL fInteractive = pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE) && pJob->IsFlagSet(TASK_FLAG_INTERACTIVE) && AllowInteractiveServices(); //
// Emulate the NT4 AT_SVC and log an error to the event log, if the
// task is supposed to be interactive, but we can't be, due to
// system settings.
//
// Note this query is NOT fInteractive.
//
if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE) && pJob->IsFlagSet(TASK_FLAG_INTERACTIVE) && !AllowInteractiveServices()) { LPWSTR StringArray[1]; HRESULT hr;
//
// EVENT_COMMAND_NOT_INTERACTIVE
// The %1 command is marked as an interactive command. However, the system is
// configured to not allow interactive command execution. This command may not
// function properly.
//
hr = pJob->GetCurFile(&StringArray[0]); if (FAILED(hr)) { ERR_OUT("Failed to obtain file name for non-interactive AT job", hr); } else { if (! ReportEvent(g_hAtEventSource, // source handle
EVENTLOG_WARNING_TYPE, // event type
0, // event category
EVENT_COMMAND_NOT_INTERACTIVE, // event id
NULL, // user sid
1, // number of strings
0, // data block length
(LPCWSTR *)StringArray, // string array
NULL)) // data block
{ // Not fatal, but why did we fail?
ERR_OUT("Failed to report the non-interactive event to the eventlog", GetLastError()); } //
// Clean up -- Theoretically, we should use IMalloc::Free here, but we are in
// the same process, and the memory was allocated from CoTaskMemAlloc,
// so CoTaskMemFree is the appropriate call
//
CoTaskMemFree(StringArray[0]); } }
GetStartupInfo(psi);
psi->dwFlags |= STARTF_USESHOWWINDOW; psi->wShowWindow = SW_SHOWNOACTIVATE;
if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE)) { if (fInteractive) { psi->lpDesktop = ptszDesktop; psi->dwFlags |= STARTF_DESKTOPINHERIT; } else { psi->lpDesktop = WSZ_SA_DESKTOP; psi->dwFlags &= ~STARTF_DESKTOPINHERIT; } } else { psi->lpDesktop = ptszDesktop; } }
//+----------------------------------------------------------------------------
//
// Function: ComposeBatchParam
//
// Synopsis: Builds the CreateProcess command line parameter
//
// Arguments: [pwszPrefix] -
// [pwszAppPathName] - The run target task property.
// [pwszParameters] - The parameters task propery.
// [ppwszCmdLine] - The command line to return.
//
// Returns: S_OK or E_OUTOFMEMORY.
//
//-----------------------------------------------------------------------------
HRESULT ComposeBatchParam( LPCTSTR pwszPrefix, LPCTSTR pwszAppPathName, LPCTSTR pwszParameters, LPTSTR * ppwszCmdLine) { ULONG cchCmdLine; BOOL fBatchNameHasSpaces = HasSpaces(pwszAppPathName);
//
// Space for the command line is length of prefix "cmd /c " plus batch
// file name, plus two if the batch file name will be surrounded with
// spaces, plus length of parameters, if any, plus one for the space
// preceding the parameters, plus one for the terminating nul.
//
cchCmdLine = lstrlen(pwszPrefix) + 1 + lstrlen(pwszAppPathName) + (fBatchNameHasSpaces ? 2 : 0) + (pwszParameters ? 1 + lstrlen(pwszParameters) : 0);
*ppwszCmdLine = new TCHAR[cchCmdLine];
if (!*ppwszCmdLine) { schDebugOut((DEB_ERROR, "RunNTJob: Can't allocate %u for cmdline\n", cchCmdLine)); return E_OUTOFMEMORY; }
StringCchCopy(*ppwszCmdLine, cchCmdLine, pwszPrefix);
if (fBatchNameHasSpaces) { StringCchCat(*ppwszCmdLine, cchCmdLine, DQUOTE); }
StringCchCat(*ppwszCmdLine, cchCmdLine, pwszAppPathName);
if (fBatchNameHasSpaces) { StringCchCat(*ppwszCmdLine, cchCmdLine, DQUOTE); }
if (pwszParameters) { StringCchCat(*ppwszCmdLine, cchCmdLine, SPACE); StringCchCat(*ppwszCmdLine, cchCmdLine, pwszParameters); }
return S_OK; }
//+----------------------------------------------------------------------------
//
// Function: ComposeParam
//
// Synopsis: Builds the CreateProcess command line parameter
//
// Arguments: [fTargetIsExe] - Is pwszRunTarget an exe or a document.
// [ptszRunTarget] - The run target task property.
// [ptszParameters] - The parameters task propery.
// [ptszCmdLine] - The command line to return.
//
// Returns: S_OK or E_OUTOFMEMORY.
//
//-----------------------------------------------------------------------------
HRESULT ComposeParam(BOOL fTargetIsExe, LPTSTR ptszRunTarget, LPTSTR ptszParameters, LPTSTR * pptszCmdLine) { LPTSTR ptszCmdLine;
//
// Check for whitespace in the app name.
//
BOOL fAppNameHasSpaces = HasSpaces(ptszRunTarget);
//
// If running a document, check for spaces in the doc path name.
//
BOOL fParamHasSpaces = FALSE;
if (!fTargetIsExe && HasSpaces(ptszParameters)) { fParamHasSpaces = TRUE; }
//
// Figure the length, adding 1 for the space and 1 for the null,
// plus 2 for the quotes, if needed.
//
DWORD cch = lstrlen(ptszRunTarget) + lstrlen(ptszParameters) + 2 + (fAppNameHasSpaces ? 2 : 0) + (fParamHasSpaces ? 2 : 0);
ptszCmdLine = new TCHAR[cch]; if (!ptszCmdLine) { LogServiceError(IDS_NON_FATAL_ERROR, ERROR_OUTOFMEMORY, IDS_HELP_HINT_CLOSE_APPS); ERR_OUT("CSchedWorker::RunWin95Job", E_OUTOFMEMORY); *pptszCmdLine = NULL; return E_OUTOFMEMORY; }
if (fAppNameHasSpaces) { //
// Enclose the app name in quotes if it contains whitespace.
//
StringCchCopy(ptszCmdLine, cch, DQUOTE); StringCchCat(ptszCmdLine, cch, ptszRunTarget); StringCchCat(ptszCmdLine, cch, DQUOTE); } else { StringCchCopy(ptszCmdLine, cch, ptszRunTarget); }
StringCchCat(ptszCmdLine, cch, SPACE);
if (fParamHasSpaces) { StringCchCat(ptszCmdLine, cch, DQUOTE); }
StringCchCat(ptszCmdLine, cch, ptszParameters);
if (fParamHasSpaces) { StringCchCat(ptszCmdLine, cch, DQUOTE); }
*pptszCmdLine = ptszCmdLine;
return S_OK; }
//+----------------------------------------------------------------------------
//
// Function: MapFindExecutableError
//
// Synopsis: Converts the poorly designed error codes returned by the
// FindExecutable API to HRESULTs
//
// Arguments: [hRet] - Error return code from FindExecutable
//
// Returns: HRESULT (with FACILITY_WIN32) for the same error
//
//-----------------------------------------------------------------------------
HRESULT MapFindExecutableError(HINSTANCE hRet) { schAssert((DWORD_PTR)hRet <= 32);
HRESULT hr; if (hRet == 0) { hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); } else if ((DWORD_PTR)hRet == 31) { hr = HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION); } else { hr = HRESULT_FROM_WIN32((DWORD_PTR)hRet); }
return hr; }
//+----------------------------------------------------------------------------
//
// Function: WaitForStubExe
//
// Synopsis: Waits for the stub exe to launch the job
//
// Arguments: [hProcess] - Handle to the stub exe process
//
// Returns: TRUE if stub exe launched job
// FALSE if stub exe didn't launch job. Last error is set to the
// exit code from the stub exe.
//
//-----------------------------------------------------------------------------
BOOL WaitForStubExe(HANDLE hProcess) { BOOL fRanJob = FALSE;
DWORD dwWait = WaitForSingleObject(hProcess, 90000); if (dwWait == WAIT_OBJECT_0) { DWORD dwExitCode; if (!GetExitCodeProcess(hProcess, &dwExitCode)) { ERR_OUT("GetExitCodeProcess", GetLastError()); } else if (dwExitCode == 0) { fRanJob = TRUE; } else { ERR_OUT("Stub exe run", dwExitCode); SetLastError(dwExitCode); } } else if (dwWait == WAIT_TIMEOUT) { schAssert(!"Stub exe didn't exit in 90 sec!"); SetLastError(ERROR_TIMEOUT); } // else WAIT_FAILED - last error will be set to failure code
return fRanJob; }
//+----------------------------------------------------------------------------
//
// Function: GetUserTokenFromSession
//
// Synopsis: Enumerates the sessions and returns token of the session in
// which the given user has logged on
//
// Arguments: [IN lpszUsername] - user account name
// [IN lpszDomain] - domain name
// [OUT phUserToken] - token to be returned
//
// Returns: FALSE if the given user is not found in the enumerated sessions
// TRUE if the user is found in the the enumerated sessions array
//
// This function enumerates the sessions and compared the user name
// and domainname of the session with the given username and domain
// name. If such session is found, it checks to see if it is a console
// session.
// If it is a console session, the search is terminated and the token
// given by the session is returned in phUserToken
// Else the token from first session saved and search is continued
//
// At the end of the search if no console session is found then
// the saved first session token is returned.
//
// Else if no session found whatsoever then the function returns FALSE
//-----------------------------------------------------------------------------
BOOL GetUserTokenFromSession( LPTSTR lpszUsername, // user name
LPTSTR lpszDomain, // domain or server
PHANDLE phUserToken // receive tokens handle
) {
PWTS_SESSION_INFO pWTSSessionInfo = NULL; DWORD WTSSessionInfoCount = 0;
//WTS_CURRENT_SERVER_HANDLE indicates the terminal server
//on which your application is running
BOOL result = WTSEnumerateSessions( WTS_CURRENT_SERVER_HANDLE, 0, //Reserved; must be zero
1, //version of the enumeration request. Must be 1
&pWTSSessionInfo, &WTSSessionInfoCount);
if(!result) { schDebugOut((DEB_TRACE, "*** WTSEnumerateSessions failed\n")); return (FALSE); }
schDebugOut((DEB_TRACE, "*** WTSEnumerateSessions returned %d sessions\n",WTSSessionInfoCount));
LPTSTR pWTSDomainNameBuffer = NULL; LPTSTR pWTSUserNameBuffer = NULL;
HANDLE hNewToken = INVALID_HANDLE_VALUE; HANDLE hFirstToken = INVALID_HANDLE_VALUE; HANDLE hConsoleToken = INVALID_HANDLE_VALUE;
// Get the session id of the session attached to the console. If there is
// no session attached to console then this return 0xFFFFFFFF
DWORD ConsoleSessionID = WTSGetActiveConsoleSessionId ();
BOOL bSuccess = FALSE; //Check each session to see if the user name and the domain name matches with the
//ones that are passed to this function
for (DWORD i = 0; i < WTSSessionInfoCount; i++) { WTS_INFO_CLASS WTSInfoClass; pWTSDomainNameBuffer = NULL; pWTSUserNameBuffer = NULL; DWORD BytesReturned = 0;
BOOL bDomainNameResult = WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, pWTSSessionInfo[i].SessionId, WTSDomainName, //the type of information to retrieve
&pWTSDomainNameBuffer, &BytesReturned );
BOOL bUserNameResult = WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, pWTSSessionInfo[i].SessionId, WTSUserName, //the type of information to retrieve
&pWTSUserNameBuffer, &BytesReturned ); if (bDomainNameResult && bUserNameResult) { schDebugOut((DEB_TRACE, "*** \n Comparing %s with %s and %s with %s", lpszUsername,pWTSUserNameBuffer, lpszDomain, pWTSDomainNameBuffer));
if (_wcsicmp(lpszUsername, pWTSUserNameBuffer) == 0 && _wcsicmp(lpszDomain, pWTSDomainNameBuffer) == 0) { // Call WTSQueryUserToken to retrieve a handle to the user access
// token for this session. Token returned by WTSQueryUserToken is
// a primary token and can be passed to CreateProcessAsUser
BOOL fRet = WTSQueryUserToken(pWTSSessionInfo[i].SessionId, &hNewToken);
if(fRet) { // Check to see if it is a console session
if(pWTSSessionInfo[i].SessionId == ConsoleSessionID) { schDebugOut((DEB_TRACE, "*** Console session found\n")); // We have have found the user session that is attached to console
// No need to search the remaining So we can break from here
hConsoleToken = hNewToken;
WTSFreeMemory(pWTSUserNameBuffer); WTSFreeMemory(pWTSDomainNameBuffer);
bSuccess = TRUE; break; }
// Else if this is the first token that matches then save it in hFirstToken
// and proceed to search for console session that matches with the user
// If such session is not found then we will use this token
else if (!bSuccess) { schDebugOut((DEB_TRACE, "*** First session found\n"));
hFirstToken = hNewToken; bSuccess = TRUE; } else { if (hNewToken != INVALID_HANDLE_VALUE) { CloseHandle(hNewToken); hNewToken = INVALID_HANDLE_VALUE; } }
// else keep seaching as we may get console session id in the remaining
// list
} } }
// pWTSUserNameBuffer may be non-null if bUserNameResult is false
if (pWTSUserNameBuffer) { WTSFreeMemory(pWTSUserNameBuffer); }
// pWTSDomainNameBuffer may be non-null if bDomainNameResult is false
if (pWTSDomainNameBuffer) { WTSFreeMemory(pWTSDomainNameBuffer); } }
WTSFreeMemory(pWTSSessionInfo);
if(bSuccess) { // We may have either one or both open tokens.
// If we get hConsoleToken then we return hConsoleToken
// In that case if hFirstToken is open then we close hFirstToken
// If we dont get hConsoleToken then we return hFirstToken
if(hConsoleToken != INVALID_HANDLE_VALUE) { if(hFirstToken != INVALID_HANDLE_VALUE) { CloseHandle(hFirstToken); }
*phUserToken = hConsoleToken;
schDebugOut((DEB_TRACE, "*** Returning TRUE with Console Session Token\n")); } else { *phUserToken = hFirstToken;
schDebugOut((DEB_TRACE, "*** Returning TRUE with First Session Token\n")); } }
else { schDebugOut((DEB_TRACE, "*** Returning FALSE\n")); } return (bSuccess); }
|