1284 lines
36 KiB
C++
1284 lines
36 KiB
C++
//+----------------------------------------------------------------------------
|
|
//
|
|
// Job Scheduler Service
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996.
|
|
//
|
|
// File: sch_main.cxx
|
|
//
|
|
// Contents: job scheduler NT service entry point and thread launcher
|
|
//
|
|
// History: 08-Sep-95 EricB created
|
|
// 03-Mar-01 JBenton - BUG 207402 RESUMESUSPEND and RESUMEAUTOMATIC
|
|
// events arrive out of the expected sequence on some hardware.
|
|
// This caused the internal POWER_RESUME event to be sent twice
|
|
// which sometimes (20%) prevented the scheduled job that
|
|
// triggered the wake-up from running.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "..\..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include <globals.hxx>
|
|
#include <svc_core.hxx>
|
|
#include "..\..\inc\sadat.hxx"
|
|
#include "..\..\inc\resource.h"
|
|
#include "..\..\idletask\inc\idlesrv.h"
|
|
|
|
DECLARE_INFOLEVEL(Sched);
|
|
|
|
// globals
|
|
SERVICE_STATUS_HANDLE g_hStatus = NULL;
|
|
SERVICE_STATUS g_SvcStatus; // BUGBUG guard with critsec, put in class
|
|
ATOM g_aClass = 0;
|
|
HANDLE g_hWindowThread = NULL;
|
|
HWND g_hwndSchedSvc = NULL;
|
|
LONG g_fUserIsLoggedOn = FALSE; // Whether a user is currently logged on
|
|
HANDLE g_WndEvent = NULL;
|
|
UINT g_uTaskbarMessage = 0;
|
|
BOOL g_fShuttingDown;
|
|
|
|
// local prototypes
|
|
void SchedStart(DWORD, LPWSTR *);
|
|
BOOL RunningAsLocalSystem(VOID);
|
|
HRESULT WindowMsgFcn(LPVOID pVoid);
|
|
LRESULT CALLBACK SchedWndProc(HWND, UINT, WPARAM, LPARAM);
|
|
|
|
// routine to run on the prefetcher service thread.
|
|
|
|
extern "C" DWORD WINAPI PfSvcMainThread(VOID *Param);
|
|
|
|
#define IDM_EXIT 100
|
|
|
|
extern "C" void CALLBACK CloseProcEx(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseProc
|
|
//
|
|
// Synopsis: Entry point used with RunDll32
|
|
// closes down window via WM_CLOSE
|
|
//
|
|
// Arguments: [hwnd] -- ignored
|
|
// [hinst] -- uninteresting
|
|
// [nCmdShow] -- boring
|
|
// [lpszCmdLine] -- command line from invocation
|
|
//
|
|
// Notes: command line should be proc id.
|
|
// This is a straight passthrough to the implementation in procssr.cxx
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
extern "C" void CALLBACK CloseProc(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
CloseProcEx(hwnd, hinst, lpszCmdLine, nCmdShow);
|
|
}
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
|
|
{
|
|
if (DLL_PROCESS_ATTACH==ulReason)
|
|
{
|
|
DisableThreadLibraryCalls (hInstance) ;
|
|
if (CStaticCritSec::anyFailure())
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SchedServiceMain
|
|
//
|
|
// Synopsis: Entry point when running in an SvcHost.exe instance.
|
|
//
|
|
// Arguments: [CArgs] - count of arg strings
|
|
// [ppwszArgs] - array of arg strings
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
VOID
|
|
WINAPI
|
|
SchedServiceMain(DWORD cArgs, LPWSTR * ppwszArgs)
|
|
{
|
|
//
|
|
// We need to initialize g_hInstance here for OpenLogFile
|
|
// to work.
|
|
//
|
|
g_hInstance = GetModuleHandle(SCH_SERVICE_DLL_NAME);
|
|
|
|
//
|
|
// Open the schedule service log file.
|
|
//
|
|
if (FAILED(OpenLogFile()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogServiceEvent(IDS_LOG_SERVICE_STARTED);
|
|
|
|
SchedStart(cArgs, ppwszArgs);
|
|
|
|
LogServiceEvent(IDS_LOG_SERVICE_EXITED);
|
|
|
|
//
|
|
// Close the schedule service log.
|
|
//
|
|
CloseLogFile();
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SchedStart
|
|
//
|
|
// Synopsis: Primary thread of the NT service
|
|
//
|
|
// Arguments: [CArgs] - count of arg strings
|
|
// [ppwszArgs] - array of arg strings
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
SchedStart(DWORD cArgs, LPWSTR * ppwszArgs)
|
|
{
|
|
HANDLE hPfSvcThread;
|
|
HANDLE hPfSvcStopEvent;
|
|
DWORD ErrorCode;
|
|
BOOLEAN StartedIdleDetectionServer;
|
|
|
|
HRESULT hr;
|
|
|
|
//
|
|
// initialize locals so we know what to cleanup.
|
|
//
|
|
|
|
hPfSvcStopEvent = NULL;
|
|
hPfSvcThread = NULL;
|
|
StartedIdleDetectionServer = FALSE;
|
|
|
|
//
|
|
// Initialize some globals.
|
|
//
|
|
|
|
if (!g_hInstance) {
|
|
g_hInstance = GetModuleHandle(NULL);
|
|
}
|
|
|
|
g_fShuttingDown = FALSE;
|
|
g_hStatus = NULL;
|
|
g_hWindowThread = NULL;
|
|
g_hwndSchedSvc = NULL;
|
|
g_fUserIsLoggedOn = FALSE;
|
|
g_WndEvent = NULL;
|
|
|
|
g_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
g_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE |
|
|
SERVICE_ACCEPT_SHUTDOWN |
|
|
SERVICE_ACCEPT_POWEREVENT;
|
|
|
|
g_SvcStatus.dwWaitHint = SCH_WAIT_HINT;
|
|
g_SvcStatus.dwCheckPoint = 1;
|
|
g_SvcStatus.dwWin32ExitCode = NO_ERROR;
|
|
g_SvcStatus.dwServiceSpecificExitCode = 0;
|
|
g_SvcStatus.dwCurrentState = SERVICE_START_PENDING;
|
|
|
|
//
|
|
// Register the control handler.
|
|
//
|
|
g_hStatus = RegisterServiceCtrlHandlerEx(g_tszSrvcName,
|
|
SchSvcHandler,
|
|
NULL);
|
|
|
|
if (g_hStatus == NULL)
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, GetLastError(), 0);
|
|
ERR_OUT("RegisterServiceCtrlHandler", GetLastError());
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Let the service controller know we're making progress.
|
|
//
|
|
UpdateStatus();
|
|
|
|
//
|
|
// Make sure the service is running as LocalSystem
|
|
//
|
|
if (!RunningAsLocalSystem())
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE,
|
|
HRESULT_FROM_WIN32(SCHED_E_SERVICE_NOT_LOCALSYSTEM),
|
|
0);
|
|
|
|
SchStop(HRESULT_FROM_WIN32(SCHED_E_SERVICE_NOT_LOCALSYSTEM), FALSE);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize the service.
|
|
//
|
|
hr = SchInit();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr, FALSE);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Let the service controller know we're making progress.
|
|
//
|
|
StartupProgressing();
|
|
|
|
//
|
|
// Get the ID of the logged on user, if there is one.
|
|
//
|
|
GetLoggedOnUser();
|
|
|
|
//
|
|
// Let the service controller know we're making progress.
|
|
//
|
|
StartupProgressing();
|
|
|
|
//
|
|
// Initialize NetSchedule API support code.
|
|
//
|
|
hr = InitializeNetScheduleApi();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Let the service controller know we're making progress.
|
|
//
|
|
StartupProgressing();
|
|
|
|
//
|
|
// Start the RPC server.
|
|
//
|
|
hr = StartRpcServer();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Let the service controller know we're making progress.
|
|
//
|
|
StartupProgressing();
|
|
|
|
//
|
|
// Create the window thread event.
|
|
//
|
|
g_WndEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (g_WndEvent == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("CreateEvent", hr);
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Create the window thread.
|
|
//
|
|
DWORD dwThreadID;
|
|
g_hWindowThread = CreateThread(NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE)WindowMsgFcn,
|
|
NULL,
|
|
0,
|
|
&dwThreadID);
|
|
if (!g_hWindowThread)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Creation of Window Thread", hr);
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr);
|
|
|
|
CloseHandle( g_WndEvent );
|
|
g_WndEvent = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize idle detection server. This must be done after we
|
|
// start the other RPC servers that register a dynamic ncalrpc
|
|
// endpoint.
|
|
//
|
|
|
|
ErrorCode = ItSrvInitialize();
|
|
|
|
hr = HRESULT_FROM_WIN32(ErrorCode);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
SchStop(hr);
|
|
return;
|
|
}
|
|
|
|
StartedIdleDetectionServer = TRUE;
|
|
|
|
|
|
//
|
|
// Initialize the event it is going to wait on and kick off the
|
|
// prefetcher maintenance thread.
|
|
//
|
|
|
|
hPfSvcStopEvent = CreateEvent(NULL, // no security attributes
|
|
TRUE, // manual reset event
|
|
FALSE, // not-signalled
|
|
NULL); // no name
|
|
|
|
if (hPfSvcStopEvent) {
|
|
hPfSvcThread = CreateThread(0,0,PfSvcMainThread,&hPfSvcStopEvent,0,0);
|
|
}
|
|
|
|
//
|
|
// We're off and running.
|
|
//
|
|
g_SvcStatus.dwCurrentState = SERVICE_RUNNING;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
UpdateStatus();
|
|
|
|
hr = g_pSched->InitialDirScan();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Update the service flag in SA.DAT to running.
|
|
// Also redetermine whether the machine supports wakeup timers. We do
|
|
// this every time the service starts in order to work around problems
|
|
// with IBM Thinkpads and VPOWERD on Windows 98. (BUGBUG Do we need
|
|
// to do this on NT as well?)
|
|
//
|
|
// Note, this function should not fail. If it does, something is
|
|
// seriously wrong with the system.
|
|
//
|
|
|
|
hr = SADatCreate(g_TasksFolderInfo.ptszPath);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Call the main function.
|
|
//
|
|
|
|
hr = SchedMain(NULL);
|
|
|
|
//
|
|
// Exit.
|
|
//
|
|
|
|
Exit:
|
|
|
|
//
|
|
// If one exists, signal prefetcher thread's stop event and wait
|
|
// for it to terminate.
|
|
//
|
|
|
|
if (hPfSvcStopEvent && hPfSvcThread) {
|
|
SetEvent(hPfSvcStopEvent);
|
|
WaitForSingleObject(hPfSvcThread, INFINITE);
|
|
}
|
|
|
|
if (hPfSvcStopEvent) {
|
|
CloseHandle(hPfSvcStopEvent);
|
|
}
|
|
|
|
if (hPfSvcThread) {
|
|
CloseHandle(hPfSvcThread);
|
|
}
|
|
|
|
HANDLE rghWaitArray[2] = { g_WndEvent, g_hWindowThread };
|
|
|
|
DBG_OUT("Service exit -- waiting for window thread to finish starting.");
|
|
|
|
if (WaitForMultipleObjects(2, rghWaitArray, FALSE, INFINITE) == WAIT_OBJECT_0)
|
|
{
|
|
//
|
|
// g_WndEvent was signalled -- we can now send a message
|
|
// to the window thread to tell it to shut down
|
|
//
|
|
g_fShuttingDown = TRUE;
|
|
SendMessage(g_hwndSchedSvc, WM_COMMAND, IDM_EXIT, 0);
|
|
|
|
//
|
|
// Wait for window thread to exit before exitting the main thread.
|
|
// This is to ensure that the task bar icon is removed. The wait is
|
|
// probably not needed since the SendMessage exit processing looks
|
|
// like it is completely syncronous.
|
|
//
|
|
|
|
DBG_OUT("Waiting for window thread to exit.");
|
|
WaitForSingleObject(g_hWindowThread, INFINITE);
|
|
DBG_OUT("Window thread exited.");
|
|
}
|
|
else
|
|
{
|
|
DBG_OUT("Window thread exited prematurely -- shutting down.");
|
|
}
|
|
|
|
//
|
|
// Stop idle detection server.
|
|
//
|
|
|
|
if (StartedIdleDetectionServer) {
|
|
ItSrvUninitialize();
|
|
}
|
|
|
|
//
|
|
// Cleanup some globals.
|
|
//
|
|
|
|
if (g_fUserIsLoggedOn) {
|
|
LogonSessionDataCleanup();
|
|
g_fUserIsLoggedOn = FALSE;
|
|
}
|
|
|
|
if (g_hWindowThread) {
|
|
CloseHandle(g_hWindowThread);
|
|
g_hWindowThread = NULL;
|
|
}
|
|
|
|
if (g_WndEvent) {
|
|
CloseHandle(g_WndEvent);
|
|
g_WndEvent = NULL;
|
|
}
|
|
|
|
SchStop(hr);
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: RunningAsLocalSystem
|
|
//
|
|
// Synopsis: Detects whether the service was started in the System account.
|
|
//
|
|
// Arguments: None
|
|
//
|
|
// Returns: TRUE if the service is running as LocalSystem
|
|
// FALSE if it is not or if any errors were encountered
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL
|
|
RunningAsLocalSystem(VOID)
|
|
{
|
|
SID LocalSystemSid = { SID_REVISION,
|
|
1,
|
|
SECURITY_NT_AUTHORITY,
|
|
SECURITY_LOCAL_SYSTEM_RID };
|
|
|
|
BOOL fCheckSucceeded;
|
|
BOOL fIsLocalSystem = FALSE;
|
|
|
|
fCheckSucceeded = CheckTokenMembership(NULL,
|
|
&LocalSystemSid,
|
|
&fIsLocalSystem);
|
|
|
|
if (!fCheckSucceeded)
|
|
{
|
|
ERR_OUT("CheckTokenMembership", GetLastError());
|
|
}
|
|
|
|
return (fCheckSucceeded && fIsLocalSystem);
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: WindowMsgFcn
|
|
//
|
|
// Synopsis: Window message loop thread of the service.
|
|
//
|
|
// Arguments: [pVoid] - currently not used
|
|
//
|
|
// Returns: HRESULTS - the service is not currently detecting if this
|
|
// thread exits prematurely. Thus, the exit code is ignored.
|
|
// Monitoring the thread handle in SchedMain would give more
|
|
// thorough error detection.
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
WindowMsgFcn(LPVOID pVoid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Find out the ID of the message that the tray will send us when it
|
|
// starts.
|
|
//
|
|
g_uTaskbarMessage = RegisterWindowMessage(TEXT("TaskbarCreated"));
|
|
|
|
//
|
|
// Register the window class
|
|
//
|
|
WNDCLASS wc;
|
|
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = SchedWndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = g_hInstance;
|
|
wc.hIcon = NULL;
|
|
wc.hCursor = NULL;
|
|
wc.hbrBackground = NULL;
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = SCHED_CLASS;
|
|
|
|
g_aClass = RegisterClass(&wc);
|
|
if (!g_aClass)
|
|
{
|
|
ULONG ulLastError = GetLastError();
|
|
|
|
LogServiceError(IDS_NON_FATAL_ERROR, ulLastError, 0);
|
|
ERR_OUT("RegisterClass", ulLastError);
|
|
return HRESULT_FROM_WIN32(ulLastError);
|
|
}
|
|
|
|
//
|
|
// Now create the hidden window on the interactive desktop.
|
|
//
|
|
g_hwndSchedSvc = CreateWindow(SCHED_CLASS, SCHED_TITLE, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
|
CW_USEDEFAULT, (HWND)NULL, (HMENU)NULL,
|
|
g_hInstance, (LPVOID)NULL);
|
|
if (!g_hwndSchedSvc)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
ERR_OUT("CreateWindow", hr);
|
|
return hr;
|
|
}
|
|
|
|
ShowWindow(g_hwndSchedSvc, SW_HIDE);
|
|
UpdateWindow(g_hwndSchedSvc);
|
|
|
|
//
|
|
// Initialize this thread's keep-awake count.
|
|
//
|
|
|
|
InitThreadWakeCount();
|
|
|
|
//
|
|
// Initialize idle detection. This must be done by the window thread
|
|
// (not the state machine thread).
|
|
//
|
|
InitIdleDetection();
|
|
|
|
//
|
|
// Notify the main thread that the window creation is complete.
|
|
//
|
|
if (!SetEvent(g_WndEvent))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
LogServiceError(IDS_INITIALIZATION_FAILURE, (DWORD)hr, 0);
|
|
ERR_OUT("SetEvent(g_WndEvent)", hr);
|
|
return hr;
|
|
}
|
|
|
|
MSG msg;
|
|
while (GetMessage(&msg, (HWND) NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
if (g_aClass != 0)
|
|
{
|
|
DeleteAtom(g_aClass);
|
|
g_aClass = 0;
|
|
|
|
UnregisterClass(SCHED_CLASS, g_hInstance);
|
|
}
|
|
|
|
DBG_OUT("Window exited.");
|
|
return S_OK;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SchedWndProc
|
|
//
|
|
// Synopsis: handle messages
|
|
//
|
|
// Returns: occasionally
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
LRESULT CALLBACK
|
|
SchedWndProc(HWND hwndSched, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT lResult = 0;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
case WM_SETTEXT:
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDM_EXIT:
|
|
|
|
//
|
|
// Only exit if this message was sent by the
|
|
// Task Scheduler's shutdown code (vs. a user)
|
|
//
|
|
if (g_fShuttingDown)
|
|
{
|
|
DestroyWindow(hwndSched);
|
|
}
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_TIMECHANGE:
|
|
DBG_OUT("WM_TIMECHANGE received");
|
|
if (g_pSched)
|
|
{
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_TIME_CHANGED);
|
|
}
|
|
break;
|
|
|
|
case WM_SCHED_SetNextIdleNotification:
|
|
return SetNextIdleNotificationFn((WORD)wParam);
|
|
|
|
case WM_SCHED_SetIdleLossNotification:
|
|
return SetIdleLossNotificationFn();
|
|
|
|
case WM_DESTROY:
|
|
DBG_OUT("Got WM_DESTROY.");
|
|
|
|
//
|
|
// Only exit if this message was sent by the
|
|
// Task Scheduler's shutdown code (vs. a user)
|
|
//
|
|
if (g_fShuttingDown)
|
|
{
|
|
EndIdleDetection();
|
|
PostQuitMessage(0);
|
|
}
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
case WM_QUIT:
|
|
|
|
//
|
|
// If the Task Scheduler isn't shutting down, these
|
|
// are malicious messages, so ignore them. If the
|
|
// Task Scheduler is shutting down, fall through and
|
|
// let DefWindowProc handle these
|
|
//
|
|
if (!g_fShuttingDown)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Fall through
|
|
|
|
default:
|
|
if (uMsg == g_uTaskbarMessage)
|
|
{
|
|
schDebugOut((DEB_ITRACE,
|
|
"Got Taskbar message, calling SchSvcHandler.\n"));
|
|
SchSvcHandler(SERVICE_CONTROL_USER_LOGON, 0, NULL, NULL);
|
|
// CODEWORK: Is a non-zero return code expected from the wndproc?
|
|
}
|
|
else
|
|
{
|
|
return DefWindowProc(hwndSched, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
return lResult;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SchSvcHandler
|
|
//
|
|
// Synopsis: handles service controller callback notifications
|
|
//
|
|
// Arguments: [dwControl] - the control code
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD WINAPI
|
|
SchSvcHandler(
|
|
DWORD dwControl,
|
|
DWORD dwEventType,
|
|
LPVOID lpEventData,
|
|
LPVOID lpContext
|
|
)
|
|
{
|
|
static BOOL fResumeHandled; // initially FALSE
|
|
|
|
switch (dwControl)
|
|
{
|
|
case SERVICE_CONTROL_PAUSE:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_PAUSE");
|
|
if (g_SvcStatus.dwCurrentState == SERVICE_RUNNING ||
|
|
g_SvcStatus.dwCurrentState == SERVICE_CONTINUE_PENDING)
|
|
{
|
|
g_SvcStatus.dwCurrentState = SERVICE_PAUSE_PENDING;
|
|
g_SvcStatus.dwWaitHint = 0;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
|
|
g_pSched->SubmitControl(0);
|
|
LogServiceEvent(IDS_LOG_SERVICE_PAUSED);
|
|
}
|
|
else
|
|
{
|
|
ERR_OUT("Trying to pause when service not running!", 0);
|
|
}
|
|
break;
|
|
|
|
case SERVICE_CONTROL_CONTINUE:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_CONTINUE");
|
|
if (g_SvcStatus.dwCurrentState == SERVICE_PAUSED ||
|
|
g_SvcStatus.dwCurrentState == SERVICE_PAUSE_PENDING)
|
|
{
|
|
g_SvcStatus.dwCurrentState = SERVICE_CONTINUE_PENDING;
|
|
g_SvcStatus.dwWaitHint = 0;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
|
|
g_pSched->SubmitControl(0);
|
|
LogServiceEvent(IDS_LOG_SERVICE_CONTINUED);
|
|
}
|
|
else
|
|
{
|
|
ERR_OUT("Trying to continue when service not paused!", 0);
|
|
}
|
|
break;
|
|
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_SHUTDOWN");
|
|
case SERVICE_CONTROL_STOP:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_STOP");
|
|
g_SvcStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
|
g_SvcStatus.dwWaitHint = SCH_WAIT_HINT;
|
|
g_SvcStatus.dwCheckPoint = 1;
|
|
|
|
UpdateStatus();
|
|
|
|
g_pSched->SubmitControl(0);
|
|
break;
|
|
|
|
case SERVICE_CONTROL_USER_LOGON:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_USER_LOGON");
|
|
//
|
|
// (This could be called by the window-handling thread.)
|
|
// The tray (or mstinit /logon) has notified us that it's started.
|
|
// This usually happens because a user has logged on, but it also
|
|
// happens when the tray crashes and restarts. We must ignore the
|
|
// notification in the latter case.
|
|
//
|
|
if (InterlockedExchange(&g_fUserIsLoggedOn, TRUE))
|
|
{
|
|
//
|
|
// It wasn't really a user logon. Ignore the control.
|
|
//
|
|
DBG_OUT("SchSvcHandler: User is already logged on");
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Signal the main thread loop to run logon jobs.
|
|
//
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_USER_LOGON);
|
|
break;
|
|
|
|
case SERVICE_CONTROL_USER_LOGOFF:
|
|
|
|
schDebugOut((DEB_ITRACE, "User logging off *******************\n"));
|
|
|
|
//
|
|
// Dealloc & set to NULL the global data associated with the
|
|
// user's logon session.
|
|
//
|
|
|
|
LogonSessionDataCleanup();
|
|
|
|
g_fUserIsLoggedOn = FALSE;
|
|
|
|
break;
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
DBG_OUT("SchSvcHandler: SERVICE_CONTROL_INTERROGATE");
|
|
//
|
|
// The interrogate is satisfied by the UpdateStatus call below.
|
|
//
|
|
break;
|
|
|
|
case SERVICE_CONTROL_POWEREVENT:
|
|
switch (dwEventType)
|
|
{
|
|
case PBT_APMPOWERSTATUSCHANGE:
|
|
SYSTEM_POWER_STATUS PwrStatus;
|
|
if (!GetSystemPowerStatus(&PwrStatus))
|
|
{
|
|
LogServiceError(IDS_NON_FATAL_ERROR, GetLastError(), 0);
|
|
ERR_OUT("GetSystemPowerStatus", GetLastError());
|
|
break;
|
|
}
|
|
if (PwrStatus.ACLineStatus == 0)
|
|
{
|
|
//
|
|
// On battery.
|
|
//
|
|
OnPowerChange(TRUE);
|
|
}
|
|
else
|
|
{
|
|
if (PwrStatus.ACLineStatus == 1)
|
|
{
|
|
//
|
|
// On AC power.
|
|
//
|
|
OnPowerChange(FALSE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PBT_APMQUERYSUSPEND:
|
|
//
|
|
// The computer is preparing for suspended mode.
|
|
// Signal the other thread to stop running jobs.
|
|
//
|
|
DBG_OUT("PBT_APMQUERYSUSPEND received");
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_POWER_SUSPEND);
|
|
break;
|
|
|
|
case PBT_APMQUERYSUSPENDFAILED:
|
|
//
|
|
// Aborted going into suspended mode.
|
|
// Signal the other thread to run the elapsed jobs.
|
|
//
|
|
DBG_OUT("PBT_APMQUERYSUSPENDFAILED received");
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_POWER_SUSPEND_FAILED);
|
|
break;
|
|
|
|
case PBT_APMSUSPEND:
|
|
//
|
|
// The computer is going into the suspended mode.
|
|
// We already prepared for it when we got QUERYSUSPEND.
|
|
// BUGBUG We should wait here for the other thread to finish.
|
|
//
|
|
DBG_OUT("PBT_APMSUSPEND received, ignoring (already handled)");
|
|
fResumeHandled = FALSE;
|
|
break;
|
|
|
|
|
|
//
|
|
// PBT_APMRESUMExxx messages:
|
|
// The computer is coming back from suspended mode.
|
|
// Signal the other thread to run the appropriate jobs.
|
|
//
|
|
|
|
case PBT_APMRESUMESUSPEND:
|
|
DBG_OUT("PBT_APMRESUMESUSPEND received");
|
|
if (fResumeHandled)
|
|
{
|
|
DBG_OUT("IGNORING resumesuspend (sent after resumeautomatic)");
|
|
}
|
|
else
|
|
{
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_POWER_RESUME);
|
|
//
|
|
// On some systems we see the RESUMESUSPEND message before
|
|
// the RESUMEAUTOMATIC message. We don't want to signal the
|
|
// main loop twice. So set a flag telling us to ignore
|
|
// RESUMEAUTOMATIC messages until a SUSPEND message is sent.
|
|
//
|
|
fResumeHandled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case PBT_APMRESUMECRITICAL:
|
|
DBG_OUT("PBT_APMRESUMECRITICAL received");
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_POWER_RESUME);
|
|
break;
|
|
|
|
case PBT_APMRESUMEAUTOMATIC:
|
|
DBG_OUT("PBT_APMRESUMEAUTOMATIC received");
|
|
|
|
if (fResumeHandled)
|
|
{
|
|
DBG_OUT("IGNORING resumeautomatic (sent after resumeresume)");
|
|
}
|
|
else
|
|
{
|
|
g_pSched->SubmitControl(SERVICE_CONTROL_POWER_RESUME);
|
|
//
|
|
// After this RESUMEAUTOMATIC message, the system may also
|
|
// send a RESUMESUSPEND message (if it detects user activity).
|
|
// We don't want to signal the main loop twice. So set a flag
|
|
// telling us to ignore RESUMESUSPEND messages until a SUSPEND
|
|
// message is sent.
|
|
//
|
|
fResumeHandled = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ERR_OUT("Unrecognized service control code", dwControl);
|
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
UpdateStatus();
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: CSchedWorker::HandleControl
|
|
//
|
|
// Synopsis: Handle NT service controller state changes.
|
|
//
|
|
// Returns: the control or the current/new state
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
CSchedWorker::HandleControl()
|
|
{
|
|
TRACE(CSchedWorker,HandleControl);
|
|
|
|
DWORD dwControl = m_ControlQueue.GetEntry();
|
|
|
|
switch (dwControl)
|
|
{
|
|
case SERVICE_CONTROL_USER_LOGON:
|
|
schDebugOut((DEB_ITRACE, " Event is USER_LOGON\n"));
|
|
break;
|
|
|
|
case SERVICE_CONTROL_POWER_SUSPEND:
|
|
schDebugOut((DEB_ITRACE, " Event is POWER_SUSPEND\n"));
|
|
break;
|
|
|
|
case SERVICE_CONTROL_POWER_SUSPEND_FAILED:
|
|
schDebugOut((DEB_ITRACE, " Event is POWER_SUSPEND_FAILED\n"));
|
|
break;
|
|
|
|
case SERVICE_CONTROL_POWER_RESUME:
|
|
schDebugOut((DEB_ITRACE, " Event is POWER_RESUME\n"));
|
|
break;
|
|
|
|
case SERVICE_CONTROL_TIME_CHANGED:
|
|
schDebugOut((DEB_ITRACE, " Event is TIME_CHANGED\n"));
|
|
break;
|
|
|
|
case 0:
|
|
schDebugOut((DEB_ITRACE, " Service Control is state %#lx\n",
|
|
g_SvcStatus.dwCurrentState));
|
|
|
|
switch (g_SvcStatus.dwCurrentState)
|
|
{
|
|
case SERVICE_STOP_PENDING:
|
|
schDebugOut((DEB_ITRACE,
|
|
" WaitForMultipleObjects signaled for exit\n"));
|
|
break;
|
|
|
|
case SERVICE_PAUSE_PENDING:
|
|
schDebugOut((DEB_ITRACE,
|
|
" WaitForMultipleObjects signaled for pausing\n"));
|
|
g_SvcStatus.dwCurrentState = SERVICE_PAUSED;
|
|
g_SvcStatus.dwWaitHint = 0;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
UpdateStatus();
|
|
break;
|
|
|
|
case SERVICE_CONTINUE_PENDING:
|
|
schDebugOut((DEB_ITRACE,
|
|
" WaitForMultipleObjects signaled for continuing\n"));
|
|
g_SvcStatus.dwCurrentState = SERVICE_RUNNING;
|
|
g_SvcStatus.dwWaitHint = 0;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
UpdateStatus();
|
|
break;
|
|
}
|
|
return g_SvcStatus.dwCurrentState;
|
|
|
|
default:
|
|
schDebugOut((DEB_ITRACE, " ??? UNKNOWN CONTROL %#lx\n", dwControl));
|
|
break;
|
|
}
|
|
|
|
return dwControl;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetCurrentServiceState
|
|
//
|
|
// Returns: The schedule service's current state.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
GetCurrentServiceState(void)
|
|
{
|
|
return g_SvcStatus.dwCurrentState;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: UpdateStatus
|
|
//
|
|
// Synopsis: Tell the service controller what the current status is.
|
|
//
|
|
// Returns: Win32 error values
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
UpdateStatus(void)
|
|
{
|
|
TRACE_FUNCTION(UpdateStatus);
|
|
|
|
DWORD dwRet = NO_ERROR;
|
|
|
|
if (g_hStatus == NULL)
|
|
{
|
|
ERR_OUT("UpdateStatus called with a null status handle!", 0);
|
|
return ERROR_INVALID_HANDLE;
|
|
}
|
|
|
|
if (!SetServiceStatus(g_hStatus, &g_SvcStatus))
|
|
{
|
|
dwRet = GetLastError();
|
|
LogServiceError(IDS_NON_FATAL_ERROR, dwRet, 0);
|
|
ERR_OUT("SetServiceStatus", dwRet);
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SchStop
|
|
//
|
|
// Synopsis: Shuts down the schedule service
|
|
//
|
|
// Arguments: [hr] - an error code if terminating abnormally
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
SchStop(HRESULT hr, BOOL fCoreCleanup)
|
|
{
|
|
//
|
|
// Update the service flag in SA.DAT to not running.
|
|
// Check the folder path for NULL in case initialization failed.
|
|
//
|
|
|
|
if (g_TasksFolderInfo.ptszPath != NULL)
|
|
{
|
|
UpdateSADatServiceFlags(g_TasksFolderInfo.ptszPath,
|
|
SA_DAT_SVCFLAG_SVC_RUNNING,
|
|
TRUE);
|
|
}
|
|
|
|
g_SvcStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
|
g_SvcStatus.dwCheckPoint++;
|
|
|
|
UpdateStatus();
|
|
|
|
//
|
|
// do core cleanup
|
|
//
|
|
|
|
if (fCoreCleanup)
|
|
{
|
|
SchCleanup();
|
|
}
|
|
|
|
//
|
|
// tell service controller that we are done cleaning up
|
|
//
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
|
|
{
|
|
g_SvcStatus.dwWin32ExitCode = HRESULT_CODE(hr);
|
|
}
|
|
else
|
|
{
|
|
g_SvcStatus.dwWin32ExitCode = hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_SvcStatus.dwWin32ExitCode = NO_ERROR;
|
|
}
|
|
|
|
g_SvcStatus.dwServiceSpecificExitCode = 0;
|
|
g_SvcStatus.dwCurrentState = SERVICE_STOPPED;
|
|
g_SvcStatus.dwControlsAccepted = 0;
|
|
g_SvcStatus.dwWaitHint = 0;
|
|
g_SvcStatus.dwCheckPoint = 0;
|
|
|
|
UpdateStatus();
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: StartupProgressing
|
|
//
|
|
// Synopsis: Notifies the service controller that startup is still
|
|
// progressing normally.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
StartupProgressing(void)
|
|
{
|
|
g_SvcStatus.dwCheckPoint++;
|
|
UpdateStatus();
|
|
}
|
|
|
|
|
|
|
|
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
//
|
|
// Functions below are used for the sysprep extensions
|
|
//
|
|
//*****************************************************************************
|
|
//*****************************************************************************
|
|
|
|
HRESULT PrepSysPrepTask(ITask** ppITaskToRun, WCHAR* pwszTaskName);
|
|
HRESULT WINAPI SaveSysprepInfo(void);
|
|
HRESULT WINAPI ConvertSysprepInfo(void);
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SysPrepBackup
|
|
//
|
|
// Synopsis: Entry point called prior to sysprep to store information needed for use after sysprep
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void WINAPI SysPrepBackup(void)
|
|
{
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ITask* pITaskToRun = NULL;
|
|
WCHAR wszTaskName[20];
|
|
|
|
HRESULT hr = PrepSysPrepTask(&pITaskToRun, wszTaskName);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pITaskToRun->Run();
|
|
|
|
//
|
|
// wait until task completes execution and deletes itself, or until we've waited too long
|
|
// the simplest way to do this is to check for the job file to go away
|
|
// checking job status would require us to repeatedly load the job file via Activate() anyway
|
|
//
|
|
WCHAR* pwszTasksFolder = NULL;
|
|
hr = GetTasksFolder(&pwszTasksFolder);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WCHAR wszJobPath[MAX_PATH + 1];
|
|
StringCchCopy(wszJobPath, MAX_PATH + 1, pwszTasksFolder);
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, L"\\");
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, wszTaskName);
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, TSZ_DOTJOB);
|
|
|
|
DWORD dwNumWaits = 0;
|
|
while (0xFFFFFFFF != GetFileAttributes(wszJobPath) && dwNumWaits < 60)
|
|
{
|
|
Sleep(1000);
|
|
dwNumWaits++;
|
|
}
|
|
|
|
delete [] pwszTasksFolder;
|
|
}
|
|
}
|
|
|
|
if (pITaskToRun)
|
|
{
|
|
pITaskToRun->Release();
|
|
}
|
|
|
|
CoUninitialize();
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// useful for debugging, but ignore errors in normal case as there's nothing we can do
|
|
ERR_OUT("SysPrepBackup", hr);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SysPrepCallback
|
|
//
|
|
// Synopsis: Entry point used with RunDll32
|
|
// runs sysprep first stage
|
|
//
|
|
// Arguments: [hwnd] -- ignored
|
|
// [hinst] -- uninteresting
|
|
// [nCmdShow] -- boring
|
|
// [lpszCmdLine] -- command line from invocation
|
|
//
|
|
// Notes: This is a straight passthrough to the implementation in ...?
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
extern "C" void SysPrepCallback(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
//
|
|
// Store pre-sysprep credential key in a safe, secure place for use after sysprep
|
|
//
|
|
HRESULT hr = SaveSysprepInfo();
|
|
if (FAILED(hr))
|
|
{
|
|
// useful for debugging, but ignore errors in normal case as there's nothing we can do
|
|
ERR_OUT("SysPrepCallback", hr);
|
|
}
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SysPrepRestore
|
|
//
|
|
// Synopsis: Entry point called after sysprep to process information stored prior to sysprep
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void WINAPI SysPrepRestore(void)
|
|
{
|
|
//
|
|
// Retrieve pre-sysprep credential key from safe, secure place and use it to decrypt all
|
|
// existing stored task scheduler credentials, then encrypt them using the post-sysprep key
|
|
//
|
|
HRESULT hr = ConvertSysprepInfo();
|
|
if (FAILED(hr))
|
|
{
|
|
// useful for debugging, but ignore errors in normal case
|
|
ERR_OUT("SysPrepRestore", hr);
|
|
}
|
|
}
|