You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1580 lines
49 KiB
1580 lines
49 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996.
|
|
//
|
|
// File: log.cxx
|
|
//
|
|
// Contents: Logging routines for the job scheduler service.
|
|
//
|
|
// Classes: None.
|
|
//
|
|
// Functions: OpenLogFile
|
|
// CloseLogFile
|
|
// LogTaskStatus
|
|
// LogTaskError
|
|
// LogServiceStatus
|
|
// LogServiceError
|
|
// ConstructStatusField
|
|
// ConstructResultField
|
|
// GetSchedulerResultCodeString
|
|
// OverwriteRecordFragment
|
|
// IntegerToString
|
|
//
|
|
// History: 1-Feb-96 MarkBl Created.
|
|
// 24-Oct-96 AnirudhS Modified to handle DBCS.
|
|
// 2-Feb-98 jschwart Modified to handle Unicode.
|
|
// 26-Feb-01 JBenton Prefix bug 294880
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include <sddl.h>
|
|
#include "globals.hxx"
|
|
#include "svc_core.hxx"
|
|
#include "..\inc\resource.h"
|
|
#include "..\inc\common.hxx"
|
|
|
|
#define CCH_INT 11
|
|
#define ARRAY_LEN(a) (sizeof(a)/sizeof(a[0]))
|
|
|
|
TCHAR * ConstructStatusField(DWORD, SYSTEMTIME *, ULONG *);
|
|
TCHAR * ConstructResultField(DWORD, LPTSTR);
|
|
TCHAR * GetSchedulerResultCodeString(DWORD, DWORD);
|
|
TCHAR * IntegerToString(ULONG, TCHAR *);
|
|
VOID OverwriteRecordFragment(VOID);
|
|
VOID WriteLog(LPTSTR);
|
|
BOOL GetDateTime(const SYSTEMTIME *, LPTSTR, LPTSTR);
|
|
VOID LogServiceMessage(LPCTSTR, DWORD);
|
|
|
|
|
|
//
|
|
// Note, this buffer must be at least double the size of the ASCII string:
|
|
// "[ ***** Most recent entry is above this line ***** ]\r\n\r\n"
|
|
// to leave sufficient space for localization changes.
|
|
//
|
|
#define MOST_RECENT_ENTRY_MARKER_SIZE 128
|
|
static TCHAR gtszMostRecentEntryMarker[MOST_RECENT_ENTRY_MARKER_SIZE + 1] = TEXT("");
|
|
|
|
extern CStaticCritSec gcsLogCritSection;
|
|
|
|
HANDLE ghLog = NULL;
|
|
DWORD gdwMaxLogSizeKB = NULL;
|
|
DWORD gcbMostRecentEntryMarkerSize;
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: OpenLogFile
|
|
//
|
|
// Synopsis: Open the log file and position the global file pointer.
|
|
//
|
|
// Log file path/name can be specified in in the registry as:
|
|
// HKEY_LOCAL_MACHINE\Software\Microsoft\JobScheduler\LogPath.
|
|
// If this value is not specified, or we fail somehow fetching
|
|
// it, default to the log file name "SCHEDLGU.TXT" in the tasks folder.
|
|
//
|
|
// The log file handle is cached as a global.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULT status code.
|
|
//
|
|
// Notes: ** Important Note **
|
|
//
|
|
// This function *must* be called *once* prior to log usage.
|
|
// This function should be called after g_hInstance has been
|
|
// initialized.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
OpenLogFile(VOID)
|
|
{
|
|
TCHAR tszBuffer[MAX_PATH + 1] = TEXT("\0");
|
|
DWORD cbBufferSize = sizeof(tszBuffer);
|
|
DWORD dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
|
|
DWORD dwType;
|
|
HKEY hKey;
|
|
HRESULT hr = S_OK;
|
|
|
|
#define tszLogPath TEXT("LogPath")
|
|
#define tszMaxLogSizeKB TEXT("MaxLogSizeKB")
|
|
#define tszMarkerSentinel TEXT("[ *****")
|
|
#define MARKER_SENTINEL_LENGTH (ARRAY_LEN(tszMarkerSentinel) - 1)
|
|
#define READ_BUFFER_SIZE 512
|
|
|
|
//
|
|
// Even though this function should be called only once at service start up,
|
|
// protect it with a critsec just in case someone tries to start two or more
|
|
// instances of the scheduler service simultaneously
|
|
//
|
|
EnterCriticalSection(&gcsLogCritSection);
|
|
|
|
if (ghLog != NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
schAssert(!"ghLog not NULL on entry to OpenLogFile -- attempted to run more than one instance of service?");
|
|
goto ErrorExit;
|
|
}
|
|
|
|
// Load the most recent entry marker string from the resource table.
|
|
// Set the size of the marker to the end of the string. Otherwise,
|
|
// the IsTextUnicode API (called by notepad) thinks this is Ansi.
|
|
//
|
|
gcbMostRecentEntryMarkerSize =
|
|
|
|
LoadString(g_hInstance,
|
|
IDS_MOSTRECENTLOGENTRYMARKER,
|
|
gtszMostRecentEntryMarker,
|
|
MOST_RECENT_ENTRY_MARKER_SIZE + 1);
|
|
|
|
if (!gcbMostRecentEntryMarkerSize)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
// Convert to size in bytes
|
|
//
|
|
gcbMostRecentEntryMarkerSize *= sizeof(TCHAR);
|
|
|
|
// Read the log path and maximum size from the registry. Note that these
|
|
// are stored in the service's key.
|
|
//
|
|
if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
SCH_AGENT_KEY,
|
|
0,
|
|
KEY_READ | KEY_SET_VALUE,
|
|
&hKey))
|
|
{
|
|
if (RegQueryValueEx(hKey,
|
|
tszLogPath,
|
|
NULL,
|
|
&dwType,
|
|
(UCHAR *)tszBuffer,
|
|
&cbBufferSize) ||
|
|
(dwType != REG_SZ && dwType != REG_EXPAND_SZ))
|
|
{
|
|
//
|
|
// Value is missing; create it with default
|
|
//
|
|
RegSetValueEx(hKey,
|
|
tszLogPath,
|
|
NULL,
|
|
REG_EXPAND_SZ,
|
|
(const BYTE *)TSZ_LOG_NAME_DEFAULT,
|
|
sizeof(TSZ_LOG_NAME_DEFAULT));
|
|
|
|
// if this call fails, there isn't much we can do
|
|
// but at least we've defaulted to a reasonable value
|
|
// and can continue with logging
|
|
}
|
|
|
|
cbBufferSize = sizeof(dwMaxLogSizeKB);
|
|
|
|
if (RegQueryValueEx(hKey,
|
|
tszMaxLogSizeKB,
|
|
NULL,
|
|
&dwType,
|
|
(UCHAR *)&dwMaxLogSizeKB,
|
|
&cbBufferSize) || dwType != REG_DWORD)
|
|
{
|
|
//
|
|
// Value is missing; create it with default
|
|
//
|
|
dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
|
|
|
|
RegSetValueEx(hKey,
|
|
tszMaxLogSizeKB,
|
|
NULL,
|
|
REG_DWORD,
|
|
(CONST BYTE *)&dwMaxLogSizeKB,
|
|
cbBufferSize);
|
|
|
|
// if this call fails, there isn't much we can do
|
|
// but at least we've defaulted to a reasonable value
|
|
// and can continue with logging
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
// Default log path on error.
|
|
//
|
|
if (!tszBuffer[0])
|
|
{
|
|
StringCchCopy(tszBuffer, MAX_PATH + 1, TSZ_LOG_NAME_DEFAULT);
|
|
}
|
|
|
|
// Expand environment strings in the log path.
|
|
//
|
|
TCHAR tszFileName[MAX_PATH+1];
|
|
DWORD cch = ExpandEnvironmentStrings(tszBuffer,
|
|
tszFileName,
|
|
ARRAY_LEN(tszFileName));
|
|
if (cch == 0 || cch > ARRAY_LEN(tszFileName))
|
|
{
|
|
ERR_OUT("ExpandEnvironmentStrings", cch);
|
|
hr = E_OUTOFMEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Obtain just the folder path from the full log file path
|
|
//
|
|
TCHAR tszLogFolderPath[MAX_PATH + 1];
|
|
StringCchCopy(tszLogFolderPath, MAX_PATH + 1, tszFileName);
|
|
TCHAR* ptszLastSlash = _tcsrchr(tszLogFolderPath, TEXT('\\'));
|
|
if (ptszLastSlash)
|
|
*ptszLastSlash = TEXT('\0');
|
|
|
|
DWORD cchLogFolderPath = _tcslen(tszLogFolderPath);
|
|
|
|
//
|
|
// Compare the log file path with the tasks folder path;
|
|
// The memory allocated by GetTasksFolder() must be deleted below.
|
|
//
|
|
TCHAR* ptszTasksFolder = NULL;
|
|
hr = GetTasksFolder(&ptszTasksFolder);
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
DWORD cchTasksFolder = _tcslen(ptszTasksFolder);
|
|
|
|
if (cchLogFolderPath == cchTasksFolder && !_tcsnicmp(tszFileName, ptszTasksFolder, cchTasksFolder))
|
|
{
|
|
//
|
|
// The log file is to be stored under the tasks folder;
|
|
// if the tasks folder doesn't exist, create it and set security
|
|
//
|
|
hr = EnsureTasksFolderExists(ptszTasksFolder);
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The log file is to be stored under a different folder;
|
|
// if the folder doesn't exist, create it and set security,
|
|
// but do not enable our shell extension on this folder
|
|
//
|
|
hr = EnsureTasksFolderExists(tszLogFolderPath, FALSE);
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
|
|
// create an appropriate ACL
|
|
// note that it only takes effect if we're *creating* the file
|
|
// we're basically adding read access for authenticated users on PRO
|
|
//
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
WCHAR* pwszSDDL = NULL;
|
|
|
|
OSVERSIONINFOEX verInfo;
|
|
verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
if (!GetVersionEx((LPOSVERSIONINFOW)&verInfo))
|
|
return E_FAIL;
|
|
|
|
if (verInfo.wProductType == VER_NT_WORKSTATION)
|
|
{
|
|
pwszSDDL =
|
|
L"D:(A;;FA;;;CO)(A;;FR;;;AU)(A;;FA;;;BA)(A;;FA;;;SY)";
|
|
//
|
|
// generate SD to be used for tasks folder
|
|
//
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(pwszSDDL, SDDL_REVISION_1, &pSD, NULL))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
SECURITY_ATTRIBUTES sa;
|
|
sa.lpSecurityDescriptor = pSD;
|
|
sa.bInheritHandle = FALSE;
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
|
|
|
|
// Create the file if it doesn't exist, open it if it does.
|
|
//
|
|
HANDLE hLog = CreateFile(tszFileName,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
(verInfo.wProductType == VER_NT_WORKSTATION) ? &sa : NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
|
|
NULL);
|
|
|
|
if (hLog == INVALID_HANDLE_VALUE)
|
|
{
|
|
// We're in a fine mess, bail.
|
|
//
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
schDebugOut((DEB_ERROR, "Attempted to create file \"" FMT_TSTR "\"\n", tszFileName));
|
|
goto ErrorExit;
|
|
}
|
|
|
|
TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
|
|
DWORD dwRead;
|
|
DWORD iMarker = 0; // Scope the marker index such that the search may span multiple reads.
|
|
DWORD dwLoops = 0; // Keep track of how many successful reads we've made.
|
|
// Used to figure out whether to write the UNICODE
|
|
// byte order mark (BOM) at the top of the file
|
|
|
|
// Seek to the most recent entry marker. Do so by searching for the first
|
|
// several distinguishable characters of the marker - a sentinel.
|
|
//
|
|
for (;;)
|
|
{
|
|
// Save away current file position for later file pointer adjustment.
|
|
//
|
|
LARGE_INTEGER liLogPos;
|
|
|
|
liLogPos.QuadPart = 0;
|
|
|
|
if ((liLogPos.LowPart = SetFilePointer(hLog,
|
|
0,
|
|
&liLogPos.HighPart,
|
|
FILE_CURRENT)) == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!ReadFile(hLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
|
|
!dwRead)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Convert to the number of characters (and chop off a stray byte
|
|
// if it exists in the Unicode case)
|
|
//
|
|
dwRead /= sizeof(TCHAR);
|
|
|
|
for (DWORD iBuffer = 0; iBuffer < dwRead; iBuffer++)
|
|
{
|
|
// If the first marker character is found, or the marker
|
|
// comparison is continued from the previous read, evaluate
|
|
// remaining marker string.
|
|
//
|
|
if (rgtBuffer[iBuffer] == TEXT('[') || iMarker)
|
|
{
|
|
for (; iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer;
|
|
iMarker++, iBuffer++)
|
|
{
|
|
if (rgtBuffer[iBuffer] != tszMarkerSentinel[iMarker])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the marker is found, stop & re-position the file
|
|
// pointer for future writes.
|
|
//
|
|
if (iMarker == MARKER_SENTINEL_LENGTH)
|
|
{
|
|
// Adjust file pointer accordingly.
|
|
//
|
|
liLogPos.QuadPart += iBuffer * sizeof(TCHAR);
|
|
liLogPos.QuadPart -= MARKER_SENTINEL_LENGTH * sizeof(TCHAR);
|
|
|
|
if (SetFilePointer(hLog,
|
|
liLogPos.LowPart,
|
|
&liLogPos.HighPart,
|
|
FILE_BEGIN) != -1)
|
|
{
|
|
goto MarkerFound;
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else if (iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer)
|
|
{
|
|
// Almost a match, but not quite - reset for continued
|
|
// search.
|
|
//
|
|
iMarker = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
dwLoops++;
|
|
}
|
|
|
|
if (!dwLoops && !dwRead)
|
|
{
|
|
// We just created the file and it's empty, so write the Unicode BOM
|
|
//
|
|
|
|
DWORD cbWritten;
|
|
WCHAR wcBOM = 0xFEFF;
|
|
|
|
if (!WriteFile(hLog, &wcBOM, sizeof(WCHAR), &cbWritten, NULL) ||
|
|
!cbWritten)
|
|
{
|
|
// If we can't write to the log, we've got problems
|
|
//
|
|
CloseHandle(hLog);
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
// Marker not found. Seek to file end.
|
|
//
|
|
if (SetFilePointer(hLog, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
|
|
{
|
|
// Another fine mess, bail.
|
|
//
|
|
CloseHandle(hLog);
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
schAssert(!"Couldn't seek to log file end");
|
|
goto ErrorExit;
|
|
}
|
|
|
|
MarkerFound:
|
|
gdwMaxLogSizeKB = dwMaxLogSizeKB;
|
|
ghLog = hLog;
|
|
|
|
ErrorExit:
|
|
if (pSD)
|
|
LocalFree(pSD);
|
|
|
|
if (ptszTasksFolder)
|
|
{
|
|
delete [] ptszTasksFolder;
|
|
}
|
|
LeaveCriticalSection(&gcsLogCritSection);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseLogFile
|
|
//
|
|
// Synopsis: Close log file and invalidate global handle.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: ** Important Note **
|
|
//
|
|
// Presumably, this function is called on process closure.
|
|
// Therefore, let the OS delete the critical section, *not* this
|
|
// thread. Otherwise, the critical section can be deleted out
|
|
// from under other threads currently accessing the log.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
CloseLogFile(VOID)
|
|
{
|
|
//
|
|
// If OpenLogFile has not completed successfully, the critical section
|
|
// won't have been initialized nor the global file handle set.
|
|
//
|
|
if (ghLog != NULL)
|
|
{
|
|
// Handle close gracefully in case another thread is accessing the log.
|
|
// Do so by entering the log critical section, closing the log and
|
|
// invalidating the global log handle (setting it to NULL).
|
|
//
|
|
EnterCriticalSection(&gcsLogCritSection);
|
|
|
|
if (ghLog)
|
|
{
|
|
CloseHandle(ghLog);
|
|
ghLog = NULL;
|
|
}
|
|
|
|
LeaveCriticalSection(&gcsLogCritSection);
|
|
}
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogTaskStatus
|
|
//
|
|
// Purpose: Log successful task operations.
|
|
//
|
|
// Arguments: [ptszTaskName] - the task name.
|
|
// [ptszTaskTarget] - the application/document name.
|
|
// [uMsgID] - this would typically be either:
|
|
// IDS_LOG_JOB_STATUS_STARTED or
|
|
// IDS_LOG_JOB_STATUS_FINISHED
|
|
// [dwExitCode] - if uMsgID is IDS_LOG_JOB_STATUS_FINISHED,
|
|
// it is the task exit code; ignored otherwise.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogTaskStatus(
|
|
LPCTSTR ptszTaskName,
|
|
LPTSTR ptszTaskTarget,
|
|
UINT uMsgID,
|
|
DWORD dwExitCode)
|
|
{
|
|
TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
|
|
TCHAR * ptszStatusMsg = NULL;
|
|
ULONG ccSize;
|
|
|
|
//
|
|
// Add the date & time as inserts to the format string.
|
|
//
|
|
|
|
TCHAR tszDate[SCH_MEDBUF_LEN];
|
|
TCHAR tszTime[SCH_MEDBUF_LEN];
|
|
|
|
if (!GetDateTime(NULL, tszDate, tszTime))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TCHAR * ptszResultField = NULL;
|
|
|
|
// Load the format string resource.
|
|
//
|
|
if (!LoadString(g_hInstance,
|
|
uMsgID,
|
|
tszMsgFormat,
|
|
ARRAY_LEN(tszMsgFormat)))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
if (uMsgID == IDS_LOG_JOB_STATUS_FINISHED)
|
|
{
|
|
ptszResultField = ConstructResultField(dwExitCode, ptszTaskTarget);
|
|
|
|
if (ptszResultField == NULL)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
|
|
(TCHAR *)ptszTaskTarget,
|
|
tszDate,
|
|
tszTime,
|
|
ptszResultField };
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszMsgFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszStatusMsg,
|
|
1,
|
|
(va_list *) rgptszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
if (ptszResultField != NULL)
|
|
{
|
|
LocalFree(ptszResultField);
|
|
}
|
|
return;
|
|
}
|
|
|
|
WriteLog(ptszStatusMsg);
|
|
|
|
LocalFree(ptszStatusMsg);
|
|
if (ptszResultField != NULL)
|
|
{
|
|
LocalFree(ptszResultField);
|
|
}
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogTaskError
|
|
//
|
|
// Purpose: Log task warnings and errors.
|
|
//
|
|
// Arguments: [ptszTaskName] - the task name.
|
|
// [ptszTaskTarget] - the application/document name.
|
|
// [uSeverityMsgID] - this would typically be either:
|
|
// IDS_LOG_SEVERITY_WARNING or
|
|
// IDS_LOG_SEVERITY_ERROR
|
|
// [uErrorClassMsgID] - this indicates the class of error, such
|
|
// as "Unable to start task" or "Forced to
|
|
// close"
|
|
// [pst] - the time when the error occured; if NULL,
|
|
// enters the current time.
|
|
// [dwErrorCode] - if non-zero, then an error from the OS
|
|
// that would be expanded by FormatMessage.
|
|
// [uHelpHintMsgID] - if an error, then a suggestion as to a
|
|
// possible remedy.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogTaskError(
|
|
LPCTSTR ptszTaskName,
|
|
LPCTSTR ptszTaskTarget,
|
|
UINT uSeverityMsgID,
|
|
UINT uErrorClassMsgID,
|
|
LPSYSTEMTIME pst,
|
|
DWORD dwErrCode,
|
|
UINT uHelpHintMsgID)
|
|
{
|
|
TCHAR tszEmpty[] = TEXT("");
|
|
|
|
//
|
|
// Verify params:
|
|
//
|
|
|
|
if (ptszTaskName == NULL)
|
|
{
|
|
ptszTaskName = tszEmpty;
|
|
}
|
|
if (ptszTaskTarget == NULL)
|
|
{
|
|
ptszTaskTarget = tszEmpty;
|
|
}
|
|
|
|
TCHAR tszFormat[SCH_BUF_LEN];
|
|
|
|
//
|
|
// Compose the first part of the error log entry:
|
|
// "<task name>" (<task target>) <time> ** [WARNING | ERROR] **
|
|
//
|
|
|
|
if (!LoadString(g_hInstance,
|
|
uSeverityMsgID,
|
|
tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Add the date & time as inserts to the format string.
|
|
//
|
|
|
|
TCHAR tszDate[SCH_MEDBUF_LEN];
|
|
TCHAR tszTime[SCH_MEDBUF_LEN];
|
|
|
|
if (!GetDateTime(pst, tszDate, tszTime))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Obtain the error message string.
|
|
//
|
|
|
|
LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
|
|
dwErrCode,
|
|
uHelpHintMsgID);
|
|
if (ptszErrMsg == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Glue the whole mess together.
|
|
//
|
|
|
|
TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
|
|
(TCHAR *)ptszTaskTarget,
|
|
tszDate,
|
|
tszTime,
|
|
ptszErrMsg };
|
|
|
|
TCHAR * ptszLogStr;
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszLogStr,
|
|
1,
|
|
(va_list *) rgptszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
LocalFree(ptszErrMsg);
|
|
return;
|
|
}
|
|
|
|
WriteLog(ptszLogStr);
|
|
|
|
LocalFree(ptszErrMsg);
|
|
LocalFree(ptszLogStr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogServiceError
|
|
//
|
|
// Purpose: Log service failures.
|
|
//
|
|
// Arguments: [uErrorClassMsgID] - as above.
|
|
// [dwErrCode] - as above.
|
|
// [uHelpHintMsgID] - as above.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogServiceError(
|
|
UINT uErrorClassMsgID,
|
|
DWORD dwErrCode,
|
|
UINT uHelpHintMsgID)
|
|
{
|
|
TCHAR tszSvcErrMsgFormat[SCH_MEDBUF_LEN];
|
|
|
|
if (LoadString(g_hInstance,
|
|
IDS_LOG_SERVICE_ERROR,
|
|
tszSvcErrMsgFormat,
|
|
SCH_MEDBUF_LEN) == 0)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
//
|
|
// Generic error message if things are really foobared.
|
|
//
|
|
StringCchCopy(tszSvcErrMsgFormat, SCH_MEDBUF_LEN,
|
|
TEXT("\042Task Scheduler Service\042 ** FATAL ERROR **\n"));
|
|
WriteLog(tszSvcErrMsgFormat);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Add the date & time as inserts to the format string.
|
|
//
|
|
|
|
TCHAR tszDate[SCH_MEDBUF_LEN];
|
|
TCHAR tszTime[SCH_MEDBUF_LEN];
|
|
|
|
if (!GetDateTime(NULL, tszDate, tszTime))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
|
|
dwErrCode,
|
|
uHelpHintMsgID);
|
|
if (ptszErrMsg == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TCHAR * rgptszInserts[] = {tszDate, tszTime, ptszErrMsg};
|
|
TCHAR * ptszLogStr;
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszSvcErrMsgFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszLogStr,
|
|
1,
|
|
(va_list *) rgptszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
LocalFree(ptszErrMsg);
|
|
return;
|
|
}
|
|
|
|
WriteLog(ptszLogStr);
|
|
|
|
LocalFree(ptszErrMsg);
|
|
LocalFree(ptszLogStr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogServiceEvent
|
|
//
|
|
// Synopsis: Write the service event to the log file.
|
|
//
|
|
// Purpose: Note the starting, stopping, pausing, and continuing of the
|
|
// service.
|
|
//
|
|
// Arguments: [uStrId] - a string identifying the event.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogServiceEvent(UINT uStrId)
|
|
{
|
|
TCHAR * ptszSvcMsg;
|
|
ULONG cbMsgSize;
|
|
SYSTEMTIME st;
|
|
|
|
GetLocalTime(&st);
|
|
|
|
ptszSvcMsg = ConstructStatusField(uStrId, &st, &cbMsgSize);
|
|
|
|
if( NULL == ptszSvcMsg )
|
|
{
|
|
schDebugOut((DEB_ITRACE, "LogServiceEvent - ConstructStatusField(uStrId, &st, &cbMsgSize) failed!\n"));
|
|
return;
|
|
}
|
|
|
|
LogServiceMessage(ptszSvcMsg, cbMsgSize);
|
|
|
|
LocalFree(ptszSvcMsg);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogMissedRuns
|
|
//
|
|
// Synopsis: Write details about missed runs to the log file.
|
|
//
|
|
// Arguments: [pstLastRun], [pstNow] - times between which runs were missed.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogMissedRuns(const SYSTEMTIME * pstLastRun, const SYSTEMTIME * pstNow)
|
|
{
|
|
TCHAR tszLastRunDate[SCH_MEDBUF_LEN];
|
|
TCHAR tszLastRunTime[SCH_MEDBUF_LEN];
|
|
TCHAR tszNowDate [SCH_MEDBUF_LEN];
|
|
TCHAR tszNowTime [SCH_MEDBUF_LEN];
|
|
|
|
if (!GetDateTime(pstLastRun, tszLastRunDate, tszLastRunTime) ||
|
|
!GetDateTime(pstNow, tszNowDate, tszNowTime))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
|
|
if (!LoadString(g_hInstance,
|
|
IDS_LOG_RUNS_MISSED,
|
|
tszMsgFormat,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
TCHAR * rgptszInserts[] = { tszLastRunDate, tszLastRunTime,
|
|
tszNowDate, tszNowTime };
|
|
|
|
TCHAR * ptszLogStr;
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszMsgFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszLogStr,
|
|
1,
|
|
(va_list *) rgptszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
LogServiceMessage(ptszLogStr, (lstrlen(ptszLogStr) + 1) * sizeof(TCHAR));
|
|
|
|
LocalFree(ptszLogStr);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LogServiceMessage
|
|
//
|
|
// Synopsis: Write a generic service message to the log file.
|
|
//
|
|
// Purpose: Used by LogServiceEvent and LogMissedRuns.
|
|
//
|
|
// Arguments: [ptszStrMsg] - a string message.
|
|
// [cbStrMsg] - size of pszStrMsg in bytes (may be overestimated,
|
|
// used only to calculate size of intermediate buffer.)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
LogServiceMessage(LPCTSTR ptszStrMsg, DWORD cbStrMsg)
|
|
{
|
|
size_t cchMsg = SCH_MEDBUF_LEN + (cbStrMsg / sizeof(TCHAR)) + 1;
|
|
TCHAR * ptszMsg = (TCHAR *)LocalAlloc(LPTR, cchMsg * sizeof(TCHAR));
|
|
if (ptszMsg == NULL)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
if (LoadString(g_hInstance,
|
|
IDS_LOG_SERVICE_TITLE,
|
|
ptszMsg,
|
|
SCH_MEDBUF_LEN) == 0)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
//
|
|
// Generic error message if things are really foobared.
|
|
//
|
|
StringCchCopy(ptszMsg, cchMsg, TEXT("\042Task Scheduler Service\042 ** ERROR **\n"));
|
|
}
|
|
|
|
if (ptszStrMsg != NULL)
|
|
{
|
|
StringCchCat(ptszMsg, cchMsg, ptszStrMsg);
|
|
}
|
|
|
|
WriteLog(ptszMsg);
|
|
|
|
LocalFree(ptszMsg);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: WriteLog
|
|
//
|
|
// Synopsis: Write the string to the log file.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
WriteLog(LPTSTR ptsz)
|
|
{
|
|
LARGE_INTEGER liCurLogSize, liMaxLogSize;
|
|
DWORD cbWritten;
|
|
ULONG cbStringSize = lstrlen(ptsz) * sizeof(TCHAR);
|
|
ULONG cbDataSize = cbStringSize;
|
|
|
|
EnterCriticalSection(&gcsLogCritSection);
|
|
|
|
schDebugOut((DEB_TRACE, "LOG:\n " FMT_TSTR "", ptsz));
|
|
|
|
// Lose some time here by not caching this value, but not much.
|
|
//
|
|
cbDataSize += lstrlen(gtszMostRecentEntryMarker) * sizeof(TCHAR);
|
|
|
|
// Get the current log size to see if there is room to write this.
|
|
//
|
|
liCurLogSize.QuadPart = 0;
|
|
|
|
if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
|
|
0,
|
|
&liCurLogSize.HighPart,
|
|
FILE_CURRENT)) == -1)
|
|
{
|
|
goto ErrorExit_A;
|
|
}
|
|
|
|
// Add current data size. Convert maximum size to bytes for comparison.
|
|
//
|
|
liCurLogSize.QuadPart += cbDataSize;
|
|
|
|
liMaxLogSize.QuadPart = gdwMaxLogSizeKB * 1024;
|
|
|
|
// Is there sufficient space to write the entry?
|
|
//
|
|
if (liCurLogSize.QuadPart > liMaxLogSize.QuadPart)
|
|
{
|
|
// No, adjust the end of file to eliminate the most recent entry
|
|
// marker & wrap to beginning.
|
|
//
|
|
SetEndOfFile(ghLog); // Ignore return code.
|
|
|
|
// skip the BOM
|
|
//
|
|
if (SetFilePointer(ghLog, sizeof(WCHAR), NULL, FILE_BEGIN) == -1)
|
|
{
|
|
// Seek failure
|
|
//
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
schAssert(!"Couldn't seek log file");
|
|
goto ErrorExit_A;
|
|
}
|
|
}
|
|
|
|
// Write the string.
|
|
//
|
|
if (!WriteFile(ghLog, ptsz, cbStringSize, &cbWritten, NULL))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto ErrorExit_A;
|
|
}
|
|
|
|
// Write most recent entry marker.
|
|
//
|
|
// First, save the current file pointer position. This will be the
|
|
// starting location of the next log write. Note: double-timing current
|
|
// log size local since it is no longer used.
|
|
//
|
|
liCurLogSize.QuadPart = 0;
|
|
|
|
if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
|
|
0,
|
|
&liCurLogSize.HighPart,
|
|
FILE_CURRENT)) == -1)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto ErrorExit_A;
|
|
}
|
|
|
|
if (!WriteFile(ghLog,
|
|
gtszMostRecentEntryMarker,
|
|
gcbMostRecentEntryMarkerSize,
|
|
&cbWritten,
|
|
NULL))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto ErrorExit_A;
|
|
}
|
|
|
|
// If the log has wrapped, it's likely the write pointer is positioned
|
|
// somewhere in the middle of the next record. If this is the case,
|
|
// the remaining partial record must be overwritten with spaces.
|
|
//
|
|
OverwriteRecordFragment();
|
|
|
|
// Restore log position for next write.
|
|
//
|
|
if (SetFilePointer(ghLog,
|
|
liCurLogSize.LowPart,
|
|
&liCurLogSize.HighPart,
|
|
FILE_BEGIN) == -1)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
|
|
ErrorExit_A:
|
|
LeaveCriticalSection(&gcsLogCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: OverwriteRecordFragment
|
|
//
|
|
// Synopsis: If the log has wrapped, the last write most likely has
|
|
// partially overwritten a record. This routine overwrites such
|
|
// record fragments with spaces up to the next record.
|
|
//
|
|
// Fundamental assumption - how a record is designated: The start
|
|
// of a new record is designated by the two characters \n". This
|
|
// routine simply fills text with spaces up to but not including
|
|
// this character sequence.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: N/A
|
|
//
|
|
// Notes: Upon exit, the log file pointer is restored to its original
|
|
// position on entry.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
OverwriteRecordFragment(VOID)
|
|
{
|
|
TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
|
|
LARGE_INTEGER liSavedLogPos, liLogPos;
|
|
DWORD dwRead;
|
|
|
|
// Save file pointer position during read for subsequent write.
|
|
//
|
|
liSavedLogPos.QuadPart = 0;
|
|
|
|
if ((liSavedLogPos.LowPart = SetFilePointer(ghLog,
|
|
0,
|
|
&liSavedLogPos.HighPart,
|
|
FILE_CURRENT)) == -1)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return;
|
|
}
|
|
|
|
// From the previous write, the last character is a line feed.
|
|
//
|
|
TCHAR tchPrev = TEXT('\n');
|
|
TCHAR tchCur;
|
|
int cbOverwrite = 0;
|
|
DWORD i = 0; // Index of chCur in rgbBuffer
|
|
|
|
for (;;)
|
|
{
|
|
if (!ReadFile(ghLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
|
|
!dwRead)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Convert to the number of characters (and chop off a stray byte
|
|
// if it exists in the Unicode case)
|
|
//
|
|
dwRead /= sizeof(TCHAR);
|
|
|
|
for ( ; i < dwRead; i++, cbOverwrite += sizeof(TCHAR))
|
|
{
|
|
tchCur = rgtBuffer[i];
|
|
|
|
if (tchPrev == TEXT('\n') && tchCur == TEXT('"') && cbOverwrite > 2 * sizeof(TCHAR))
|
|
{
|
|
break;
|
|
}
|
|
tchPrev = tchCur;
|
|
}
|
|
|
|
if (i < dwRead)
|
|
{
|
|
// We found the \n" character sequence. Don't
|
|
// overwrite the \r\n" sequence of the next record.
|
|
//
|
|
cbOverwrite -= 2 * sizeof(TCHAR);
|
|
break;
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
|
|
DWORD cbWritten;
|
|
|
|
// Overwrite record fragment with spaces.
|
|
//
|
|
if (cbOverwrite > 0)
|
|
{
|
|
// Adjust file pointer from read above.
|
|
//
|
|
if (SetFilePointer(ghLog,
|
|
liSavedLogPos.LowPart,
|
|
&liSavedLogPos.HighPart,
|
|
FILE_BEGIN) != -1)
|
|
{
|
|
for (UINT uCount = 0;
|
|
uCount < READ_BUFFER_SIZE / sizeof(TCHAR);
|
|
uCount++)
|
|
{
|
|
rgtBuffer[uCount] = TEXT(' ');
|
|
}
|
|
|
|
while (cbOverwrite > 0)
|
|
{
|
|
if (!WriteFile(ghLog,
|
|
rgtBuffer,
|
|
min(cbOverwrite, READ_BUFFER_SIZE),
|
|
&cbWritten,
|
|
NULL))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
break;
|
|
}
|
|
|
|
cbOverwrite -= cbWritten;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ConstructStatusField
|
|
//
|
|
// Synopsis: Retrieve the status field with an optional status timestamp
|
|
// insert.
|
|
//
|
|
// Arguments: [dwStatusFieldMsgID] -- Status field format string msg id.
|
|
// [pstStatusTime] -- Optional status timestamp. If NULL,
|
|
// no timestamp is written.
|
|
// [pcbSize] -- Returned status field size (bytes).
|
|
//
|
|
// Returns: TCHAR * status field
|
|
// NULL on error
|
|
//
|
|
// Notes: FormatMessage allocates the return string. Use LocalFree() to
|
|
// deallocate.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
TCHAR *
|
|
ConstructStatusField(
|
|
DWORD dwStatusFieldMsgID,
|
|
SYSTEMTIME * pstStatusTime,
|
|
ULONG * pcbSize)
|
|
{
|
|
// Note: Insure string buffer sizes are at least double the size of the
|
|
// largest string they'll contain, for localization reasons.
|
|
//
|
|
TCHAR tszStatusFieldFormat[SCH_BIGBUF_LEN];
|
|
TCHAR tszDate[SCH_MEDBUF_LEN];
|
|
TCHAR tszTime[SCH_MEDBUF_LEN];
|
|
TCHAR * rgptszInserts[] = { tszDate, tszTime };
|
|
TCHAR * ptszStatusField = NULL;
|
|
|
|
// The status field may/may not contain a date & time. The first
|
|
// branch is taken for status fields containing them.
|
|
//
|
|
if (pstStatusTime != NULL)
|
|
{
|
|
// Add the date & time as inserts to the status field format string.
|
|
//
|
|
if (!GetDateTime(pstStatusTime, tszDate, tszTime))
|
|
{
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
ULONG ccSize = 0;
|
|
|
|
// Load the status field format string resource.
|
|
//
|
|
if (LoadString(g_hInstance,
|
|
dwStatusFieldMsgID,
|
|
tszStatusFieldFormat,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
if (!(ccSize = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
(pstStatusTime != NULL ? FORMAT_MESSAGE_ARGUMENT_ARRAY : 0),
|
|
tszStatusFieldFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszStatusField,
|
|
1,
|
|
(pstStatusTime != NULL ? (va_list *) rgptszInserts : NULL))))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
|
|
*pcbSize = ccSize * sizeof(TCHAR);
|
|
|
|
return(ptszStatusField);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ConstructResultField
|
|
//
|
|
// Synopsis: Retrieve the result field. Algorithm:
|
|
//
|
|
// The result code is the job's exit code. Utilize the following
|
|
// algorithm to obtain the exit code string:
|
|
//
|
|
// Attempt to fetch the the exit code string as a
|
|
// message binary specified in the Job Scheduler portion of
|
|
// the registry.
|
|
//
|
|
// If this fails, produce a default
|
|
// "message not found for exit code (n)" message.
|
|
//
|
|
// Insert the result string obtained above as an insert string
|
|
// to the result field format string.
|
|
//
|
|
// Arguments: [dwResultCode] -- Result field result code.
|
|
// [ptszJobExecutable] -- Binary name executed by the job.
|
|
//
|
|
// Returns: TCHAR * result field
|
|
// NULL on error
|
|
//
|
|
// Notes: FormatMessage allocates the return string. Use LocalFree() to
|
|
// deallocate.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
TCHAR *
|
|
ConstructResultField(
|
|
DWORD dwResultCode,
|
|
LPTSTR ptszJobExecutable)
|
|
{
|
|
// Note: Insure format string buffer size is at least double the size of
|
|
// the largest string it will contain, for localization reasons.
|
|
//
|
|
TCHAR tszResultFieldFormat[SCH_MEDBUF_LEN];
|
|
TCHAR tszResultCodeValue[CCH_INT + 1];
|
|
TCHAR * ptszResultField = NULL;
|
|
TCHAR * ptszResult;
|
|
|
|
IntegerToString(dwResultCode, tszResultCodeValue);
|
|
|
|
// Job exit code. Fetch the exit code string from the
|
|
// ExitCodeMessageFile associated with the job program.
|
|
//
|
|
if ((ptszResult = GetExitCodeString(dwResultCode,
|
|
tszResultCodeValue,
|
|
(TCHAR *)ptszJobExecutable)) == NULL)
|
|
{
|
|
// Produce a default "message not found" result string.
|
|
//
|
|
ptszResult = GetSchedulerResultCodeString(
|
|
IDS_LOG_EXIT_CODE_MSG_NOT_FOUND,
|
|
dwResultCode);
|
|
}
|
|
|
|
ULONG ccSize = 0;
|
|
|
|
// Load the result field format string resource.
|
|
//
|
|
if (ptszResult != NULL)
|
|
{
|
|
if (LoadString(g_hInstance,
|
|
IDS_LOG_JOB_RESULT_FINISHED,
|
|
tszResultFieldFormat,
|
|
SCH_MEDBUF_LEN))
|
|
{
|
|
TCHAR * rgtszInserts[] = { ptszResult, tszResultCodeValue };
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszResultFieldFormat,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszResultField,
|
|
1,
|
|
(va_list *) rgtszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
|
|
LocalFree(ptszResult); // pszResultField now encapsulates
|
|
// this string.
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
}
|
|
|
|
return(ptszResultField);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetSchedulerResultCodeString
|
|
//
|
|
// Synopsis: Fetch the result code from the schedule service process.
|
|
//
|
|
// Arguments: [dwResultMsgID] -- Result message string ID.
|
|
// [dwResultCode] -- result code.
|
|
//
|
|
// Returns: TCHAR * result code string
|
|
// NULL on error
|
|
//
|
|
// Notes: FormatMessage allocates the return string. Use LocalFree() to
|
|
// deallocate.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
TCHAR *
|
|
GetSchedulerResultCodeString(
|
|
DWORD dwResultMsgID,
|
|
DWORD dwResultCode)
|
|
{
|
|
TCHAR tszResultCodeValue[SCH_SMBUF_LEN];
|
|
TCHAR tszErrMsg[SCH_MEDBUF_LEN];
|
|
TCHAR * ptszErrMsg = NULL, * ptszResultCode = NULL;
|
|
DWORD ccLength;
|
|
TCHAR * rgtszInserts[] = { tszResultCodeValue, ptszErrMsg };
|
|
|
|
IntegerToString(dwResultCode, tszResultCodeValue);
|
|
|
|
TCHAR tszMsgBuf[MAX_PATH], * ptsz;
|
|
|
|
if (LoadString(g_hInstance, dwResultMsgID, tszMsgBuf, MAX_PATH) == 0)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
|
|
if (dwResultCode != 0)
|
|
{
|
|
BOOL fDelete = FALSE;
|
|
//
|
|
// Try to obtain an error message from the system.
|
|
//
|
|
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
NULL,
|
|
dwResultCode,
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
(TCHAR *)&ptszErrMsg,
|
|
1,
|
|
NULL)))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
|
|
if (!LoadString(g_hInstance,
|
|
IDS_GENERIC_ERROR_MSG,
|
|
tszErrMsg,
|
|
SCH_MEDBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
|
|
ptszErrMsg = tszErrMsg;
|
|
}
|
|
else
|
|
{
|
|
fDelete = TRUE;
|
|
|
|
//
|
|
// Overwrite \r\n with a null characters.
|
|
//
|
|
ptsz = ptszErrMsg + ccLength - 2;
|
|
|
|
*ptsz++ = TEXT('\0');
|
|
*ptsz = TEXT('\0');
|
|
}
|
|
|
|
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
tszMsgBuf,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszResultCode,
|
|
2,
|
|
(va_list *) rgtszInserts)))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
if (fDelete) LocalFree(ptszErrMsg);
|
|
return NULL;
|
|
}
|
|
|
|
if (fDelete) LocalFree(ptszErrMsg);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No result code. All of the info is encapsulated in dwResultMsgID,
|
|
// which has no inserts.
|
|
//
|
|
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
tszMsgBuf,
|
|
0,
|
|
0,
|
|
(TCHAR *)&ptszResultCode,
|
|
1,
|
|
NULL)))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return(ptszResultCode);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IntegerToString
|
|
//
|
|
// Synopsis: Converts a 32 bit integer to a string.
|
|
//
|
|
// Arguments: [n] -- Converted int.
|
|
// [ptszBuf] -- Caller allocated buffer.
|
|
//
|
|
// Returns: Buffer ptr passed.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
TCHAR *
|
|
IntegerToString(ULONG n, TCHAR * tszBuf)
|
|
{
|
|
//
|
|
// Assemble hex representation into passed buffer, reversed,
|
|
// then reverse in-place into correct format
|
|
//
|
|
// This code deliberately eschews ultoa, since div and mod 16
|
|
// optimize so very nicely.
|
|
//
|
|
|
|
UINT ich = 0;
|
|
|
|
do
|
|
{
|
|
UINT nDigitValue = (UINT)(n % 16);
|
|
|
|
n /= 16;
|
|
|
|
if (nDigitValue > 9)
|
|
{
|
|
tszBuf[ich++] = (WCHAR)nDigitValue - 10 + TEXT('a');
|
|
}
|
|
else
|
|
{
|
|
tszBuf[ich++] = (WCHAR)nDigitValue + TEXT('0');
|
|
}
|
|
|
|
} while (n > 0);
|
|
|
|
tszBuf[ich] = TEXT('\0');
|
|
|
|
_tcsrev(tszBuf);
|
|
|
|
return(tszBuf);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetDateTime
|
|
//
|
|
// Synopsis: Formats the date and time.
|
|
//
|
|
// Arguments: [pst] - The time to use; if NULL, then the current time
|
|
// is obtained.
|
|
// [ptszDate] - The date string buffer.
|
|
// [ptszTime] - The time string buffer.
|
|
//
|
|
// Returns: TRUE for success, FALSE for failure.
|
|
//
|
|
// Notes: Note that the buffers must be at least SCH_MEDBUF_LEN in size.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
GetDateTime(const SYSTEMTIME * pst, LPTSTR ptszDate, LPTSTR ptszTime)
|
|
{
|
|
SYSTEMTIME st;
|
|
|
|
if (pst == NULL)
|
|
{
|
|
GetLocalTime(&st);
|
|
pst = &st;
|
|
}
|
|
|
|
if (!GetDateFormat(LOCALE_USER_DEFAULT,
|
|
LOCALE_NOUSEROVERRIDE,
|
|
pst,
|
|
NULL,
|
|
ptszDate,
|
|
SCH_MEDBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!GetTimeFormat(LOCALE_USER_DEFAULT,
|
|
LOCALE_NOUSEROVERRIDE,
|
|
pst,
|
|
NULL,
|
|
ptszTime,
|
|
SCH_MEDBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|