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.
1861 lines
57 KiB
1861 lines
57 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Scheduling Agent Service
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996.
|
|
//
|
|
// File: misc.cxx
|
|
//
|
|
// Contents: Miscellaneous helper functions
|
|
//
|
|
// Classes: None.
|
|
//
|
|
// Functions: StringFromTrigger, GetDaysOfWeekString, GetExitCodeString
|
|
//
|
|
// History: 08-Dec-95 EricB Created.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include <regstr.h> // for app path reg key constant
|
|
|
|
#include "..\inc\resource.h"
|
|
#include "..\inc\misc.hxx"
|
|
#include "..\inc\debug.hxx"
|
|
|
|
const TCHAR PATH_ENV_VAR[] = TEXT("PATH");
|
|
|
|
static const CHAR gszJobScheduler[] = "SOFTWARE\\Microsoft\\SchedulingAgent";
|
|
|
|
static LCID sg_lcid = GetUserDefaultLCID();
|
|
|
|
HRESULT GetMonthsString(WORD rgfMonths, LPTSTR ptszBuf, UINT cchBuf);
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: StringFromTrigger
|
|
//
|
|
// Synopsis: Returns the string representation of the passed in trigger
|
|
// data structure.
|
|
//
|
|
// Arguments: [pTrigger] - the TASK_TRIGGER struct
|
|
// [ppwszTrigger] - the returned string
|
|
// [lpDetails] - the SHELLDETAILS struct
|
|
//
|
|
// Returns: HRESULTS
|
|
//
|
|
// Notes: The string is allocated by this function with CoTaskMemAlloc
|
|
// and is caller freed with CoTaskMemFree.
|
|
//
|
|
// A non-event trigger string is composed of three parts: the
|
|
// daily portion (tszDaily) which states when during the day the
|
|
// trigger will fire, a trigger type portion (tszTrigType) which
|
|
// states what days the trigger will fire, and the calendar
|
|
// bracketing portion (tszStartDate and optionally tszEndDate).
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
StringFromTrigger(const PTASK_TRIGGER pTrigger, LPWSTR * ppwszTrigger, LPSHELLDETAILS lpDetails)
|
|
{
|
|
if (IsBadWritePtr(ppwszTrigger, sizeof(WCHAR *)))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
*ppwszTrigger = NULL;
|
|
HRESULT hr;
|
|
UINT uStrID = 0;
|
|
DWORD dwDateFlags;
|
|
|
|
TCHAR tszNumFmt[] = TEXT("%d");
|
|
TCHAR tszNumber[SCH_SMBUF_LEN];
|
|
TCHAR tszDOW[SCH_BIGBUF_LEN];
|
|
TCHAR tszWhichWeek[SCH_MED0BUF_LEN];
|
|
TCHAR tszMedBuf[SCH_MED0BUF_LEN];
|
|
TCHAR tszBigBuf[SCH_XBIGBUF_LEN];
|
|
TCHAR tszFormat[SCH_BUF_LEN];
|
|
TCHAR tszTrigType[SCH_BIGBUF_LEN];
|
|
LPTSTR rgptsz[5];
|
|
|
|
//
|
|
// If the trigger has not been set, return a phrase to that effect.
|
|
//
|
|
if (pTrigger->rgFlags & JOB_TRIGGER_I_FLAG_NOT_SET)
|
|
{
|
|
uStrID = IDS_TRIGGER_NOT_SET;
|
|
goto DoString;
|
|
}
|
|
|
|
//
|
|
// Compose the trigger-type string tszTrigType.
|
|
//
|
|
switch (pTrigger->TriggerType)
|
|
{
|
|
case TASK_TIME_TRIGGER_ONCE:
|
|
//
|
|
// Nothing to do here, handled below.
|
|
//
|
|
break;
|
|
|
|
case TASK_EVENT_TRIGGER_ON_IDLE:
|
|
//
|
|
// Event triggers. Since event triggers don't have a set run time,
|
|
// they load a string from the resource fork that describes the
|
|
// event.
|
|
//
|
|
uStrID = IDS_IDLE_TRIGGER;
|
|
break;
|
|
|
|
case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
|
|
//
|
|
// Event trigger.
|
|
//
|
|
uStrID = IDS_STARTUP_TRIGGER;
|
|
break;
|
|
|
|
case TASK_EVENT_TRIGGER_AT_LOGON:
|
|
//
|
|
// Event trigger.
|
|
//
|
|
uStrID = IDS_LOGON_TRIGGER;
|
|
break;
|
|
|
|
// Not yet implemented:
|
|
// case TASK_EVENT_TRIGGER_ON_APM_RESUME:
|
|
// //
|
|
// // Event trigger.
|
|
// //
|
|
// uStrID = IDS_RESUME_TRIGGER;
|
|
// break;
|
|
|
|
case TASK_TIME_TRIGGER_DAILY:
|
|
//
|
|
// Run every n days.
|
|
//
|
|
if (pTrigger->Type.Daily.DaysInterval == 1)
|
|
{
|
|
//
|
|
// Run every day.
|
|
//
|
|
if (!LoadString(g_hInstance, IDS_EVERY_DAY, tszTrigType,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Run every DaysInterval days: "every %d days"
|
|
//
|
|
if (!LoadString(g_hInstance, IDS_DAILY_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
StringCchPrintf(tszTrigType, SCH_BIGBUF_LEN, tszFormat, pTrigger->Type.Daily.DaysInterval);
|
|
}
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_WEEKLY:
|
|
//
|
|
// Run on mon, tues, etc every n weeks.
|
|
//
|
|
hr = GetDaysOfWeekString(pTrigger->Type.Weekly.rgfDaysOfTheWeek,
|
|
tszDOW, SCH_BUF_LEN);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
if (pTrigger->Type.Weekly.WeeksInterval == 1)
|
|
{
|
|
//
|
|
// Run every week: "every %s of every week"
|
|
//
|
|
if (!LoadString(g_hInstance, IDS_EVERY_WEEK_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
StringCchPrintf(tszTrigType, SCH_BIGBUF_LEN, tszFormat, tszDOW);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Run every WeeksInterval weeks: "every %s of every %s weeks"
|
|
//
|
|
StringCchPrintf(tszNumber, SCH_SMBUF_LEN, tszNumFmt, pTrigger->Type.Weekly.WeeksInterval);
|
|
|
|
rgptsz[0] = tszDOW;
|
|
rgptsz[1] = tszNumber;
|
|
|
|
if (!LoadString(g_hInstance, IDS_WEEKLY_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat,
|
|
0, 0, tszTrigType, SCH_BIGBUF_LEN,
|
|
(va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDATE:
|
|
//
|
|
// On specific days of specific months.
|
|
//
|
|
//
|
|
// Get the first run day and append etc if more than one.
|
|
//
|
|
WORD wFirstDay, cDays, i;
|
|
cDays = 0;
|
|
for (i = 0; i < JOB_DAYS_PER_MONTHMAX; i++)
|
|
{
|
|
if ((pTrigger->Type.MonthlyDate.rgfDays >> i) & 0x1)
|
|
{
|
|
cDays++;
|
|
if (cDays == 1)
|
|
{
|
|
wFirstDay = i + 1;
|
|
}
|
|
}
|
|
}
|
|
if (pTrigger->Type.MonthlyDate.rgfMonths == JOB_RGFMONTHS_MAX)
|
|
{
|
|
//
|
|
// Every month: "on day %d(, etc.) of every month"
|
|
//
|
|
if (!LoadString(g_hInstance,
|
|
(cDays == 1) ? IDS_EVERY_MONTHLYDATE_FORMAT :
|
|
IDS_EVERY_MONTHLYDATE_FORMAT_ETC,
|
|
tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
StringCchPrintf(tszTrigType, SCH_BIGBUF_LEN, tszFormat, wFirstDay);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Specific months: "on day %s of %s"
|
|
//
|
|
StringCchPrintf(tszNumber, SCH_SMBUF_LEN, tszNumFmt, wFirstDay);
|
|
hr = GetMonthsString(pTrigger->Type.MonthlyDate.rgfMonths,
|
|
tszBigBuf, SCH_XBIGBUF_LEN);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
rgptsz[0] = tszNumber;
|
|
rgptsz[1] = tszBigBuf;
|
|
|
|
if (!LoadString(g_hInstance,
|
|
(cDays == 1) ? IDS_MONTHLYDATE_FORMAT :
|
|
IDS_MONTHLYDATE_FORMAT_ETC,
|
|
tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat, 0, 0,
|
|
tszTrigType, SCH_BIGBUF_LEN, (va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDOW:
|
|
//
|
|
// On certain weeks of specific months.
|
|
//
|
|
|
|
if (!LoadString(g_hInstance,
|
|
IDS_FIRST + pTrigger->Type.MonthlyDOW.wWhichWeek - 1,
|
|
tszWhichWeek, SCH_MED0BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
hr = GetDaysOfWeekString(pTrigger->Type.MonthlyDOW.rgfDaysOfTheWeek,
|
|
tszDOW, SCH_BUF_LEN);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
if (pTrigger->Type.MonthlyDOW.rgfMonths == JOB_RGFMONTHS_MAX)
|
|
{
|
|
//
|
|
// Runs every month: " on the %s %s of every month"
|
|
//
|
|
rgptsz[0] = tszWhichWeek;
|
|
rgptsz[1] = tszDOW;
|
|
|
|
if (!LoadString(g_hInstance, IDS_EVERY_MONTHLYDOW_FORMAT,
|
|
tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat, 0, 0,
|
|
tszTrigType, SCH_BIGBUF_LEN, (va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Runs on specific months:
|
|
// "on the %s %s of %s"
|
|
//
|
|
hr = GetMonthsString(pTrigger->Type.MonthlyDOW.rgfMonths,
|
|
tszBigBuf, SCH_XBIGBUF_LEN);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
rgptsz[0] = tszWhichWeek;
|
|
rgptsz[1] = tszDOW;
|
|
rgptsz[2] = tszBigBuf;
|
|
|
|
if (!LoadString(g_hInstance, IDS_MONTHLYDOW_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat, 0, 0,
|
|
tszTrigType, SCH_BIGBUF_LEN, (va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
schDebugOut((DEB_ERROR, "invalid TriggerType value: 0x%x\n",
|
|
pTrigger->TriggerType));
|
|
return ERROR_INVALID_DATA;
|
|
}
|
|
|
|
DoString:
|
|
|
|
//
|
|
// Event trigger or Daily part.
|
|
//
|
|
TCHAR tszTriggerString[SCH_XBIGBUF_LEN];
|
|
TCHAR tszStartDate[SCH_DATEBUF_LEN];
|
|
TCHAR tszEndDate[SCH_DATEBUF_LEN];
|
|
TCHAR tszDaily[SCH_BUF_LEN];
|
|
|
|
switch (uStrID)
|
|
{
|
|
case IDS_RESUME_TRIGGER:
|
|
case IDS_STARTUP_TRIGGER:
|
|
case IDS_LOGON_TRIGGER:
|
|
case IDS_IDLE_TRIGGER:
|
|
case IDS_TRIGGER_NOT_SET:
|
|
//
|
|
// Event-based or invalid trigger, load the description string.
|
|
//
|
|
if (!LoadString(g_hInstance, uStrID, tszTriggerString,
|
|
SCH_XBIGBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string, LoadString", hr);
|
|
return hr;
|
|
}
|
|
break;
|
|
|
|
case 0:
|
|
//
|
|
// It is a time-based trigger.
|
|
//
|
|
TCHAR tszNum[SCH_SMBUF_LEN];
|
|
|
|
//
|
|
// Get the daily run time(s): tszDaily.
|
|
//
|
|
TCHAR tszFromTime[SCH_TIMEBUF_LEN];
|
|
TCHAR tszToTime[SCH_TIMEBUF_LEN];
|
|
SYSTEMTIME st = {0};
|
|
st.wHour = pTrigger->wStartHour;
|
|
st.wMinute = pTrigger->wStartMinute;
|
|
|
|
if (!GetTimeFormat(sg_lcid, TIME_NOSECONDS, &st, NULL,
|
|
tszFromTime, SCH_TIMEBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: GetTimeFormat", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (pTrigger->MinutesInterval == 0)
|
|
{
|
|
//
|
|
// Runs once a day at a specific time.
|
|
//
|
|
if (!LoadString(g_hInstance, IDS_ONCE_DAY_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
StringCchPrintf(tszDaily, SCH_BUF_LEN, tszFormat, tszFromTime);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// On an interval daily from a starting time for a specified
|
|
// number of minutes.
|
|
//
|
|
UINT uIntTimeStr, uDurTimeStr, uIntStr;
|
|
if (pTrigger->MinutesInterval % JOB_MINS_PER_HOUR)
|
|
{
|
|
//
|
|
// Runs on a minutes schedule.
|
|
//
|
|
StringCchPrintf(tszNum, SCH_SMBUF_LEN, tszNumFmt, pTrigger->MinutesInterval);
|
|
uIntTimeStr = IDS_MINUTES_PAREN;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Runs on an hourly schedule.
|
|
//
|
|
StringCchPrintf(tszNum, SCH_SMBUF_LEN, tszNumFmt, pTrigger->MinutesInterval / JOB_MINS_PER_HOUR);
|
|
uIntTimeStr = IDS_HOURS_PAREN;
|
|
}
|
|
|
|
TCHAR tszDuration[SCH_SMBUF_LEN];
|
|
TCHAR tszTimePart[SCH_MED0BUF_LEN];
|
|
|
|
if (!LoadString(g_hInstance, uIntTimeStr, tszTimePart,
|
|
SCH_MED0BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (pTrigger->rgFlags & JOB_TRIGGER_I_FLAG_DURATION_AS_TIME)
|
|
{
|
|
WORD wMinutes = pTrigger->wStartHour * JOB_MINS_PER_HOUR
|
|
+ pTrigger->wStartMinute;
|
|
wMinutes += (WORD)pTrigger->MinutesDuration;
|
|
while (wMinutes > JOB_MINS_PER_DAY)
|
|
{
|
|
wMinutes -= JOB_MINS_PER_DAY;
|
|
}
|
|
st.wHour = wMinutes / JOB_MINS_PER_HOUR;
|
|
st.wMinute = wMinutes % JOB_MINS_PER_HOUR;
|
|
if (!GetTimeFormat(sg_lcid, TIME_NOSECONDS, &st, NULL,
|
|
tszToTime, SCH_TIMEBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: GetTimeFormat", hr);
|
|
return hr;
|
|
}
|
|
uIntStr = IDS_MULTI_DAILY_FORMAT;
|
|
|
|
rgptsz[0] = tszNum;
|
|
rgptsz[1] = tszTimePart;
|
|
rgptsz[2] = tszFromTime;
|
|
rgptsz[3] = tszToTime;
|
|
}
|
|
else
|
|
{
|
|
if (pTrigger->MinutesDuration % JOB_MINS_PER_HOUR)
|
|
{
|
|
//
|
|
// Express the duration in minutes.
|
|
//
|
|
StringCchPrintf(tszDuration, SCH_SMBUF_LEN, tszNumFmt, pTrigger->MinutesDuration);
|
|
uDurTimeStr = IDS_MINUTES;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No remainder, so express the duration in hours.
|
|
//
|
|
StringCchPrintf(tszDuration, SCH_SMBUF_LEN, tszNumFmt, pTrigger->MinutesDuration / JOB_MINS_PER_HOUR);
|
|
uDurTimeStr = IDS_HOURS_PAREN;
|
|
}
|
|
|
|
if (!LoadString(g_hInstance, uDurTimeStr, tszToTime,
|
|
SCH_TIMEBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
uIntStr = IDS_MULTI_DURATION_FORMAT;
|
|
|
|
rgptsz[0] = tszNum;
|
|
rgptsz[1] = tszTimePart;
|
|
rgptsz[2] = tszFromTime;
|
|
rgptsz[3] = tszDuration;
|
|
rgptsz[4] = tszToTime;
|
|
}
|
|
|
|
if (!LoadString(g_hInstance, uIntStr, tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat, 0, 0,
|
|
tszDaily, SCH_BUF_LEN, (va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Starting date: tszStartDate.
|
|
//
|
|
st.wYear = pTrigger->wBeginYear;
|
|
st.wMonth = pTrigger->wBeginMonth;
|
|
st.wDay = pTrigger->wBeginDay;
|
|
dwDateFlags = DATE_SHORTDATE;
|
|
|
|
if (lpDetails) {
|
|
if (lpDetails->fmt & LVCFMT_RIGHT_TO_LEFT) {
|
|
dwDateFlags |= DATE_RTLREADING;
|
|
} else if (lpDetails->fmt & LVCFMT_LEFT_TO_RIGHT) {
|
|
dwDateFlags |= DATE_LTRREADING;
|
|
}
|
|
}
|
|
|
|
if (!GetDateFormat(sg_lcid, dwDateFlags, &st, NULL, tszStartDate,
|
|
SCH_DATEBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: GetDateFormat", hr);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Compose the complete trigger string from its parts.
|
|
//
|
|
if (pTrigger->TriggerType == TASK_TIME_TRIGGER_ONCE)
|
|
{
|
|
//
|
|
// Trigger runs just on a single day: "%s on %s"
|
|
//
|
|
rgptsz[0] = tszDaily;
|
|
rgptsz[1] = tszStartDate;
|
|
|
|
if (!LoadString(g_hInstance, IDS_RUNS_ONCE_FORMAT, tszFormat,
|
|
SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat, 0, 0,
|
|
tszTriggerString, SCH_XBIGBUF_LEN,
|
|
(va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pTrigger->rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE)
|
|
{
|
|
//
|
|
// Job has an ending date.
|
|
//
|
|
st.wYear = pTrigger->wEndYear;
|
|
st.wMonth = pTrigger->wEndMonth;
|
|
st.wDay = pTrigger->wEndDay;
|
|
dwDateFlags = DATE_SHORTDATE;
|
|
|
|
if (lpDetails) {
|
|
if (lpDetails->fmt & LVCFMT_RIGHT_TO_LEFT) {
|
|
dwDateFlags |= DATE_RTLREADING;
|
|
} else if (lpDetails->fmt & LVCFMT_LEFT_TO_RIGHT) {
|
|
dwDateFlags |= DATE_LTRREADING;
|
|
}
|
|
}
|
|
|
|
if (!GetDateFormat(sg_lcid, dwDateFlags, &st, NULL,
|
|
tszEndDate, SCH_DATEBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string, GetDateFormat", hr);
|
|
return hr;
|
|
}
|
|
//
|
|
// Compose the trigger string with an end date.
|
|
// "%s %s, starting %s & ending %s"
|
|
//
|
|
rgptsz[0] = tszDaily;
|
|
rgptsz[1] = tszTrigType;
|
|
rgptsz[2] = tszStartDate;
|
|
rgptsz[3] = tszEndDate;
|
|
|
|
if (!LoadString(g_hInstance, IDS_HAS_END_DATE_FORMAT,
|
|
tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat,
|
|
0, 0, tszTriggerString, SCH_XBIGBUF_LEN,
|
|
(va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Trigger does not have an ending date.
|
|
// "%s %s, starting %s"
|
|
//
|
|
rgptsz[0] = tszDaily;
|
|
rgptsz[1] = tszTrigType;
|
|
rgptsz[2] = tszStartDate;
|
|
|
|
if (!LoadString(g_hInstance, IDS_NO_END_DATE_FORMAT,
|
|
tszFormat, SCH_BUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY, tszFormat,
|
|
0, 0, tszTriggerString, SCH_XBIGBUF_LEN,
|
|
(va_list *)rgptsz))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("Create trigger string: FormatMessage", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
ULONG cch;
|
|
//
|
|
// Get the size to allocate for the returned string.
|
|
//
|
|
cch = wcslen(tszTriggerString) + 1; // Include the null in the count.
|
|
|
|
//
|
|
// Allocate the returned string.
|
|
//
|
|
|
|
*ppwszTrigger = (LPWSTR)CoTaskMemAlloc(cch * sizeof(WCHAR));
|
|
|
|
if (*ppwszTrigger == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
StringCchCopy(*ppwszTrigger, cch, tszTriggerString);
|
|
return S_OK;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetDaysOfWeekString
|
|
//
|
|
// Synopsis: Builds a string containing the names of the days of the week
|
|
// that correspond to bits set in the bitset array.
|
|
//
|
|
// Arguments: [rgfDaysOfTheWeek] - a bitset array.
|
|
// [pwszBuf] - return in this string buffer.
|
|
// [cchBuf] - size of string buffer.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetDaysOfWeekString(WORD rgfDaysOfTheWeek, LPTSTR ptszBuf, UINT cchBuf)
|
|
{
|
|
HRESULT hr;
|
|
LCID lcid = GetSystemDefaultLCID();
|
|
|
|
|
|
if (rgfDaysOfTheWeek == 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
BOOL fMoreThanOne = FALSE;
|
|
int cch;
|
|
TCHAR tszSep[SCH_SMBUF_LEN];
|
|
if (!LoadString(g_hInstance, IDS_LIST_SEP, tszSep, SCH_SMBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetDaysOfWeekString: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
*ptszBuf = TEXT('\0');
|
|
|
|
if (rgfDaysOfTheWeek & TASK_MONDAY)
|
|
{
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME1, ptszBuf, cchBuf))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
|
|
if (rgfDaysOfTheWeek & TASK_TUESDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME2, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
if (rgfDaysOfTheWeek & TASK_WEDNESDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME3, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
if (rgfDaysOfTheWeek & TASK_THURSDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME4, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
if (rgfDaysOfTheWeek & TASK_FRIDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME5, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
if (rgfDaysOfTheWeek & TASK_SATURDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME6, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
if (rgfDaysOfTheWeek & TASK_SUNDAY)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVDAYNAME7, ptszBuf + cch,
|
|
cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetMonthsString
|
|
//
|
|
// Synopsis: Builds a string containing the names of the months
|
|
// that correspond to bits set in the bitset array.
|
|
//
|
|
// Arguments: [rgfMonths] - a bitset array.
|
|
// [pwszBuf] - return in this string buffer.
|
|
// [cchBuf] - size of string buffer.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetMonthsString(WORD rgfMonths, LPTSTR ptszBuf, UINT cchBuf)
|
|
{
|
|
if (rgfMonths == 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
LCID lcid = GetSystemDefaultLCID();
|
|
|
|
|
|
HRESULT hr;
|
|
|
|
BOOL fMoreThanOne = FALSE;
|
|
int cch;
|
|
TCHAR tszSep[SCH_SMBUF_LEN];
|
|
if (!LoadString(g_hInstance, IDS_LIST_SEP, tszSep, SCH_SMBUF_LEN))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetMonthsString: LoadString", hr);
|
|
return hr;
|
|
}
|
|
|
|
*ptszBuf = TEXT('\0');
|
|
|
|
for (WORD i = 0; i < JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
if ((rgfMonths >> i) & 0x1)
|
|
{
|
|
if (fMoreThanOne)
|
|
{
|
|
StringCchCat(ptszBuf, cchBuf, tszSep);
|
|
}
|
|
cch = lstrlen(ptszBuf);
|
|
if (!GetLocaleInfo(lcid, LOCALE_SABBREVMONTHNAME1 + i,
|
|
ptszBuf + cch, cchBuf - cch))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
ERR_OUT("GetMonthsString: GetLocaleInfo", hr);
|
|
return hr;
|
|
}
|
|
fMoreThanOne = TRUE;
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SchedMapRpcError
|
|
//
|
|
// Purpose: Remap RPC exception codes that are unsuitable for displaying
|
|
// to the user to more comprehensible errors.
|
|
//
|
|
// Arguments: [dwError] - the error returned by RpcExceptionCode().
|
|
//
|
|
// Returns: An HRESULT.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SchedMapRpcError(
|
|
DWORD dwError)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (dwError == EPT_S_NOT_REGISTERED)
|
|
{
|
|
hr = SCHED_E_SERVICE_NOT_RUNNING;
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ComposeErrorMsg
|
|
//
|
|
// Purpose: Take the two message IDs and the error code and create an
|
|
// error reporting string that can be used by both service
|
|
// logging and UI dialogs.
|
|
//
|
|
// Arguments: same as above.
|
|
//
|
|
// Returns: A string or NULL on failure.
|
|
//
|
|
// Notes: Release the string memory when done using LocalFree.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LPTSTR
|
|
ComposeErrorMsg(
|
|
UINT uErrorClassMsgID,
|
|
DWORD dwErrCode,
|
|
UINT uHelpHintMsgID,
|
|
BOOL fIndent)
|
|
{
|
|
TCHAR szErrClassMsg[SCH_BIGBUF_LEN];
|
|
|
|
if (!LoadString(g_hInstance,
|
|
uErrorClassMsgID,
|
|
szErrClassMsg,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
|
|
BOOL fDelete = FALSE;
|
|
DWORD ccLength = 0;
|
|
|
|
TCHAR szErrCode[SCH_MED0BUF_LEN], szGenericErr[SCH_BUF_LEN];
|
|
TCHAR * psz;
|
|
LPTSTR pszErrStr = NULL;
|
|
DWORD dwWinErr = dwErrCode;
|
|
|
|
if (dwErrCode != 0)
|
|
{
|
|
//
|
|
// Format the error code as a hex string.
|
|
//
|
|
|
|
TCHAR szErrNumFormat[SCH_MED0BUF_LEN];
|
|
|
|
if (!LoadString(g_hInstance,
|
|
IDS_ERROR_NUMBER_FORMAT,
|
|
szErrNumFormat,
|
|
SCH_MED0BUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
|
|
StringCchPrintf(szErrCode, SCH_MED0BUF_LEN, szErrNumFormat, dwErrCode);
|
|
|
|
//
|
|
// If a Win32 error code, strip the HRESULT stuff.
|
|
//
|
|
|
|
if (HRESULT_FACILITY(dwErrCode) == FACILITY_WINDOWS ||
|
|
HRESULT_FACILITY(dwErrCode) == FACILITY_WIN32)
|
|
{
|
|
dwWinErr = HRESULT_CODE(dwErrCode);
|
|
}
|
|
|
|
//
|
|
// Try to obtain the error message from the system.
|
|
//
|
|
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
NULL,
|
|
dwWinErr,
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
(LPTSTR) &pszErrStr,
|
|
1,
|
|
NULL)))
|
|
{
|
|
DWORD di = GetLastError( );
|
|
//
|
|
// Well, that didn't work, so try to get it from the service.
|
|
//
|
|
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
g_hInstance,
|
|
dwErrCode,
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
(LPTSTR) &pszErrStr,
|
|
1,
|
|
NULL)))
|
|
{
|
|
//
|
|
// That didn't work either, so give a generic message.
|
|
//
|
|
if (!LoadString(g_hInstance,
|
|
IDS_GENERIC_ERROR_MSG,
|
|
szGenericErr,
|
|
SCH_BUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
return NULL;
|
|
}
|
|
pszErrStr = szGenericErr;
|
|
}
|
|
}
|
|
|
|
if (ccLength != 0)
|
|
{
|
|
fDelete = TRUE;
|
|
|
|
//
|
|
// Overwrite \r\n with null characters.
|
|
//
|
|
psz = pszErrStr + ccLength - 2;
|
|
|
|
*psz++ = '\0';
|
|
*psz = '\0';
|
|
}
|
|
}
|
|
|
|
TCHAR * pwszLogStr = NULL;
|
|
|
|
TCHAR * rgpszInserts[4];
|
|
|
|
rgpszInserts[0] = szErrClassMsg;
|
|
|
|
UINT uFormatID;
|
|
|
|
TCHAR szHelpMsg[SCH_BIGBUF_LEN];
|
|
|
|
if (uHelpHintMsgID == 0 && dwWinErr != 0)
|
|
{
|
|
//
|
|
// Caller didn't specify a help hint. Try to map the error to one.
|
|
//
|
|
|
|
switch (dwWinErr)
|
|
{
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_BAD_PATHNAME:
|
|
case ERROR_DIRECTORY:
|
|
case ERROR_ACCESS_DENIED:
|
|
case ERROR_NO_NET_OR_BAD_PATH:
|
|
case ERROR_INVALID_DRIVE:
|
|
case ERROR_INVALID_COMPUTERNAME:
|
|
case ERROR_INVALID_SHARENAME:
|
|
uHelpHintMsgID = IDS_HELP_HINT_BROWSE;
|
|
break;
|
|
|
|
case ERROR_TOO_MANY_OPEN_FILES:
|
|
case ERROR_NOT_ENOUGH_MEMORY:
|
|
case ERROR_OUTOFMEMORY:
|
|
case ERROR_TOO_MANY_NAMES:
|
|
case ERROR_TOO_MANY_SESS:
|
|
case ERROR_OUT_OF_STRUCTURES:
|
|
case ERROR_NO_PROC_SLOTS:
|
|
case ERROR_TOO_MANY_SEMAPHORES:
|
|
case ERROR_NO_MORE_SEARCH_HANDLES:
|
|
case ERROR_TOO_MANY_TCBS:
|
|
case ERROR_MAX_THRDS_REACHED:
|
|
case ERROR_DLL_INIT_FAILED:
|
|
case ERROR_NO_SYSTEM_RESOURCES:
|
|
case ERROR_NONPAGED_SYSTEM_RESOURCES:
|
|
case ERROR_PAGED_SYSTEM_RESOURCES:
|
|
case RPC_S_OUT_OF_RESOURCES:
|
|
uHelpHintMsgID = IDS_HELP_HINT_CLOSE_APPS;
|
|
break;
|
|
|
|
case ERROR_NOT_SUPPORTED:
|
|
case ERROR_REM_NOT_LIST:
|
|
case ERROR_DUP_NAME:
|
|
case ERROR_BAD_NETPATH:
|
|
case ERROR_NETWORK_BUSY:
|
|
case ERROR_DEV_NOT_EXIST:
|
|
case ERROR_TOO_MANY_CMDS:
|
|
case ERROR_ADAP_HDW_ERR:
|
|
case ERROR_BAD_NET_RESP:
|
|
case ERROR_UNEXP_NET_ERR:
|
|
case ERROR_BAD_REM_ADAP:
|
|
case ERROR_NETNAME_DELETED:
|
|
case ERROR_NETWORK_ACCESS_DENIED:
|
|
case ERROR_BAD_DEV_TYPE:
|
|
case ERROR_BAD_NET_NAME:
|
|
case ERROR_SHARING_PAUSED:
|
|
case ERROR_REQ_NOT_ACCEP:
|
|
case ERROR_REMOTE_SESSION_LIMIT_EXCEEDED:
|
|
case ERROR_NO_NETWORK:
|
|
case ERROR_NETWORK_UNREACHABLE:
|
|
case ERROR_HOST_UNREACHABLE:
|
|
case ERROR_PROTOCOL_UNREACHABLE:
|
|
case ERROR_PORT_UNREACHABLE:
|
|
case ERROR_CONNECTION_COUNT_LIMIT:
|
|
case ERROR_NO_LOGON_SERVERS:
|
|
uHelpHintMsgID = IDS_HELP_HINT_CALLPSS;
|
|
break;
|
|
|
|
case ERROR_BADDB:
|
|
case ERROR_BADKEY:
|
|
case ERROR_CANTOPEN:
|
|
case ERROR_CANTREAD:
|
|
case ERROR_CANTWRITE:
|
|
case ERROR_REGISTRY_CORRUPT:
|
|
case ERROR_REGISTRY_IO_FAILED:
|
|
case ERROR_KEY_DELETED:
|
|
case ERROR_DLL_NOT_FOUND:
|
|
uHelpHintMsgID = IDS_HELP_HINT_REINSTALL;
|
|
break;
|
|
|
|
case EPT_S_NOT_REGISTERED:
|
|
uHelpHintMsgID = IDS_HELP_HINT_STARTSVC;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (uHelpHintMsgID != 0)
|
|
{
|
|
//
|
|
// A help hint string has been specified.
|
|
//
|
|
|
|
if (!LoadString(g_hInstance,
|
|
uHelpHintMsgID,
|
|
szHelpMsg,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (dwErrCode != 0)
|
|
{
|
|
//
|
|
// An error code has also been given.
|
|
//
|
|
|
|
rgpszInserts[1] = szErrCode;
|
|
rgpszInserts[2] = pszErrStr;
|
|
rgpszInserts[3] = szHelpMsg;
|
|
|
|
uFormatID = (fIndent ? IDS_ERROR_FORMAT_WCODE_WHELP_I :
|
|
IDS_ERROR_FORMAT_WCODE_WHELP);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Help string but no error code.
|
|
//
|
|
|
|
rgpszInserts[1] = szHelpMsg;
|
|
|
|
uFormatID = (fIndent ? IDS_ERROR_FORMAT_WOCODE_WHELP_I :
|
|
IDS_ERROR_FORMAT_WOCODE_WHELP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No help hint.
|
|
//
|
|
|
|
if (dwErrCode != 0)
|
|
{
|
|
//
|
|
// An error code has been given.
|
|
//
|
|
|
|
rgpszInserts[1] = szErrCode;
|
|
rgpszInserts[2] = pszErrStr;
|
|
|
|
uFormatID = (fIndent ? IDS_ERROR_FORMAT_WCODE_WOHELP_I :
|
|
IDS_ERROR_FORMAT_WCODE_WOHELP);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No help string or error code.
|
|
//
|
|
|
|
uFormatID = (fIndent ? IDS_ERROR_FORMAT_WOCODE_WOHELP_I :
|
|
IDS_ERROR_FORMAT_WOCODE_WOHELP);
|
|
}
|
|
}
|
|
|
|
TCHAR szFormat[SCH_BIGBUF_LEN];
|
|
|
|
if (!LoadString(g_hInstance,
|
|
uFormatID,
|
|
szFormat,
|
|
SCH_BIGBUF_LEN))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
szFormat,
|
|
0,
|
|
0,
|
|
(LPTSTR) &pwszLogStr,
|
|
1,
|
|
(va_list *) rgpszInserts))
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
goto CleanUp;
|
|
}
|
|
|
|
CleanUp:
|
|
if (fDelete)
|
|
{
|
|
LocalFree(pszErrStr);
|
|
}
|
|
return pwszLogStr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetExitCodeString
|
|
//
|
|
// Synopsis: Retrieve the string associated with the exit code from a
|
|
// message file. Algorithm:
|
|
//
|
|
// Consult the Software\Microsoft\Job Scheduler subkey.
|
|
//
|
|
// Attempt to retrieve the ExitCodeMessageFile string value from
|
|
// a subkey matching the job executable prefix (i.e., minus the
|
|
// extension).
|
|
//
|
|
// The ExitCodeMessageFile specifies a binary from which the
|
|
// message string associated with the exit code value is fetched.
|
|
//
|
|
// Arguments: [dwExitCode] -- Job exit code.
|
|
// [pwszExitCodeValue] -- Job exit code in string form.
|
|
// [pszJobExecutable] -- Binary name executed with the job.
|
|
//
|
|
// Returns: TCHAR * exit code string
|
|
// NULL on error
|
|
//
|
|
// Notes: FormatMessage allocates the return string. Use LocalFree() to
|
|
// deallocate.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
TCHAR *
|
|
GetExitCodeString(
|
|
DWORD dwExitCode,
|
|
TCHAR * ptszExitCodeValue,
|
|
TCHAR * ptszJobExecutable)
|
|
{
|
|
static TCHAR tszExitCodeMsgFile[] = TEXT("ExitCodeMessageFile");
|
|
TCHAR tszBuffer[MAX_PATH + 1];
|
|
DWORD cbBufferSize = sizeof(tszBuffer);
|
|
TCHAR * ptszExt;
|
|
TCHAR * ptszExitCode = NULL;
|
|
DWORD dwType;
|
|
|
|
// Isolate job app name prefix.
|
|
//
|
|
if ((ptszExt = _tcsrchr(ptszJobExecutable, TEXT('.'))) != NULL)
|
|
{
|
|
*ptszExt = TEXT('\0');
|
|
}
|
|
|
|
HKEY hKey;
|
|
HKEY hSubKey;
|
|
|
|
// BUGBUG : Cache job scheduler key.
|
|
|
|
if (!RegOpenKeyExA(HKEY_LOCAL_MACHINE,
|
|
gszJobScheduler,
|
|
0,
|
|
KEY_READ,
|
|
&hKey))
|
|
{
|
|
// Open the JobScheduler\<app> subkey and fetch the exit code
|
|
// message file name.
|
|
//
|
|
if (!RegOpenKeyEx(hKey, ptszJobExecutable, 0, KEY_READ, &hSubKey))
|
|
{
|
|
if (!RegQueryValueEx(hSubKey,
|
|
tszExitCodeMsgFile,
|
|
NULL,
|
|
&dwType,
|
|
(UCHAR *)tszBuffer,
|
|
&cbBufferSize) && dwType == REG_SZ)
|
|
{
|
|
// Load the resource as a data file, so no code executes
|
|
// in our process.
|
|
//
|
|
HINSTANCE hInst = LoadLibraryEx(tszBuffer,
|
|
NULL,
|
|
LOAD_LIBRARY_AS_DATAFILE);
|
|
|
|
if (hInst != NULL)
|
|
{
|
|
ULONG ccSize;
|
|
|
|
if (ccSize = FormatMessage(
|
|
FORMAT_MESSAGE_FROM_HMODULE |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
hInst,
|
|
dwExitCode,
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
(TCHAR *)&ptszExitCode,
|
|
1,
|
|
NULL))
|
|
{
|
|
// Overwrite \r\n with null characters.
|
|
//
|
|
TCHAR * ptsz = ptszExitCode + lstrlen(ptszExitCode) - 2;
|
|
|
|
*ptsz++ = TEXT('\0');
|
|
*ptsz = TEXT('\0');
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
|
|
FreeLibrary(hInst);
|
|
}
|
|
}
|
|
RegCloseKey(hSubKey);
|
|
}
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
if (ptszExt != NULL) // Restore '.'
|
|
{
|
|
*ptszExt = '.';
|
|
}
|
|
|
|
return(ptszExitCode);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetParentDirectory
|
|
//
|
|
// Synopsis: Return the parent directory of the path indicated.
|
|
//
|
|
// Arguments: [ptszPath] -- Input path.
|
|
// [tszDirectory] -- Caller-allocated returned directory.
|
|
// [cchBuf] -- size of caller-allocated buffer
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
GetParentDirectory(LPCTSTR ptszPath, TCHAR tszDirectory[], size_t cchBuf)
|
|
{
|
|
StringCchCopy(tszDirectory, cchBuf, ptszPath);
|
|
|
|
LPTSTR ptsz = _tcsrchr(tszDirectory, _T('\\'));
|
|
|
|
if (ptsz == NULL)
|
|
{
|
|
tszDirectory[0] = _T('\\');
|
|
}
|
|
else
|
|
{
|
|
*ptsz = _T('\0');
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetAppNameFromPath
|
|
//
|
|
// Synopsis: Copy the filename portion (without double quotes) of full
|
|
// or partial path in [tszAppPathName] into buffer pointed to
|
|
// by [tszCopyTo].
|
|
//
|
|
// Arguments: [tszAppPathName] - full or partial path
|
|
// [tszCopyTo] - destination buffer
|
|
// [cchMax] - max size, in chars, of buffer
|
|
//
|
|
// Modifies: *[tszCopyTo]
|
|
//
|
|
// History: 09-17-96 DavidMun Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
GetAppNameFromPath(
|
|
LPCTSTR tszAppPathName,
|
|
LPTSTR tszCopyTo,
|
|
ULONG cchMax)
|
|
{
|
|
LPCTSTR ptszStart;
|
|
LPCTSTR ptszLastSlash;
|
|
|
|
if (tszAppPathName[0] == TEXT('"'))
|
|
{
|
|
ptszStart = &tszAppPathName[1];
|
|
}
|
|
else
|
|
{
|
|
ptszStart = tszAppPathName;
|
|
}
|
|
|
|
ptszLastSlash = _tcsrchr(ptszStart, TEXT('\\'));
|
|
|
|
if (ptszLastSlash)
|
|
{
|
|
lstrcpyn(tszCopyTo, ptszLastSlash + 1, cchMax);
|
|
}
|
|
else
|
|
{
|
|
lstrcpyn(tszCopyTo, ptszStart, cchMax);
|
|
}
|
|
|
|
LPTSTR ptszQuote = _tcschr(tszCopyTo, TEXT('"'));
|
|
|
|
if (ptszQuote)
|
|
{
|
|
*ptszQuote = TEXT('\0');
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SetAppPath
|
|
//
|
|
// Synopsis: If the application in the full or partial path
|
|
// [tszAppPathName] has an app path registry entry, save the
|
|
// current value of the path variable in [pptszSavedPath] and
|
|
// prepend the app path to the current path.
|
|
//
|
|
// Arguments: [tszAppPathName] - full or partial path to application
|
|
// [pptszSavedPath] - filled with ptr to saved path string
|
|
//
|
|
// Modifies: *[pptszSavedPath], PATH environment variable
|
|
//
|
|
// Returns: nonzero - path environment var was set
|
|
// zero - path environment var not set
|
|
//
|
|
// History: 09-17-96 DavidMun Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL SetAppPath(LPCTSTR tszAppPathName, LPTSTR *pptszSavedPath)
|
|
{
|
|
BOOL fChangedPath = FALSE;
|
|
LPTSTR tszAppName[MAX_PATH];
|
|
TCHAR tszAppPathVar[MAX_PATH_VALUE];
|
|
|
|
do
|
|
{
|
|
//
|
|
// Init out var
|
|
//
|
|
|
|
*pptszSavedPath = NULL;
|
|
|
|
//
|
|
// See if there's an app key with a PATH value. If not we're done.
|
|
//
|
|
|
|
GetAppNameFromPath(tszAppPathName, (LPTSTR)tszAppName, MAX_PATH);
|
|
|
|
GetAppPathInfo((LPCTSTR)tszAppName,
|
|
NULL,
|
|
0,
|
|
tszAppPathVar,
|
|
MAX_PATH_VALUE);
|
|
|
|
if (!*tszAppPathVar)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Save the original path, if it exists.
|
|
//
|
|
|
|
ULONG cchOriginalPath = GetEnvironmentVariable(PATH_ENV_VAR, NULL,0);
|
|
|
|
if (0 != cchOriginalPath)
|
|
{
|
|
//
|
|
// It exists, try to alloc mem to save it. If we can't, we'll try
|
|
// to run the app without setting its app path, but CreateProcess
|
|
// will probably fail anyway due to lack of resources.
|
|
//
|
|
|
|
*pptszSavedPath = new TCHAR[cchOriginalPath];
|
|
|
|
if (NULL == *pptszSavedPath)
|
|
{
|
|
ERR_OUT("SetAppPath: trying to save path", E_OUTOFMEMORY);
|
|
break;
|
|
}
|
|
|
|
GetEnvironmentVariable(PATH_ENV_VAR,
|
|
*pptszSavedPath,
|
|
cchOriginalPath);
|
|
}
|
|
|
|
//
|
|
// Now build the new path by prepending the app's path to the original
|
|
// path. Note cchNewPath includes 1 for terminating nul, and
|
|
// cchOriginalPath does also, if it isn't 0. This will give us 1
|
|
// character extra if we are to be concatenating cchOriginalPath.
|
|
// We'll use that extra character for the semicolon separating new and
|
|
// original paths.
|
|
//
|
|
|
|
ULONG cchNewPath = lstrlen(tszAppPathVar) + 1;
|
|
LPTSTR ptszNewPath = new TCHAR[cchNewPath + cchOriginalPath];
|
|
|
|
if (NULL == ptszNewPath)
|
|
{
|
|
ERR_OUT("SetAppPath: trying to get AppPath", E_OUTOFMEMORY);
|
|
break;
|
|
}
|
|
|
|
StringCchCopy(ptszNewPath, cchNewPath, tszAppPathVar);
|
|
|
|
if (0 != cchOriginalPath)
|
|
{
|
|
ptszNewPath[cchNewPath - 1] = TEXT(';');
|
|
StringCchCopy(&ptszNewPath[cchNewPath], cchOriginalPath, *pptszSavedPath);
|
|
}
|
|
|
|
//
|
|
// Finally ready to set the path environment variable
|
|
//
|
|
|
|
fChangedPath = SetEnvironmentVariable(PATH_ENV_VAR, ptszNewPath);
|
|
delete [] ptszNewPath;
|
|
} while (0);
|
|
|
|
return fChangedPath;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LoadAtJob
|
|
//
|
|
// Synopsis: Load the At job indicated.
|
|
//
|
|
// Arguments: [pJob] - Job object to use.
|
|
// [pwszAtJobFilename] - Relative path to the target At job.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
LoadAtJob(CJob * pJob, TCHAR * ptszAtJobFilename)
|
|
{
|
|
static ULONG ccJobFolderPathSize = 0;
|
|
TCHAR tszPath[MAX_PATH + 1];
|
|
|
|
if (ccJobFolderPathSize == 0)
|
|
{
|
|
ccJobFolderPathSize = lstrlen(g_TasksFolderInfo.ptszPath);
|
|
}
|
|
|
|
//
|
|
// Compute string size to prevent possible buffer overrun.
|
|
//
|
|
// NB : size + 2 for additional for '\' + NULL character.
|
|
//
|
|
|
|
ULONG ccAtJobPathSize = lstrlen(ptszAtJobFilename) +
|
|
ccJobFolderPathSize + 2;
|
|
|
|
if (ccAtJobPathSize > MAX_PATH)
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(ERROR_INVALID_NAME));
|
|
return HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
|
|
}
|
|
|
|
StringCchCopy(tszPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
|
|
StringCchCat(tszPath, MAX_PATH + 1, TEXT("\\"));
|
|
StringCchCat(tszPath, MAX_PATH + 1, ptszAtJobFilename);
|
|
|
|
return pJob->LoadP(tszPath, 0, FALSE, TRUE);
|
|
}
|
|
|
|
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IsValidAtFilename
|
|
//
|
|
// Synopsis: Returns TRUE if [wszFilename] is of the form ATn.JOB, where
|
|
// n is 1 or more digits, leading zeros not allowed, n >= 1.
|
|
//
|
|
// History: 09-30-96 DavidMun Created
|
|
//
|
|
// Notes: Assumes [wszFilename] has valid extension.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL IsValidAtFilename(LPCWSTR wszFilename)
|
|
{
|
|
ULONG cchFilename;
|
|
static ULONG s_cchExtension;
|
|
|
|
cchFilename = lstrlen(wszFilename);
|
|
|
|
if (!s_cchExtension)
|
|
{
|
|
s_cchExtension = ARRAY_LEN(TSZ_DOTJOB) - 1;
|
|
}
|
|
|
|
//
|
|
// The filename must be of the form PREFIXdigitsEXTENSION, with at least
|
|
// one digit. This means that the min length is the length of PREFIX +
|
|
// EXTENSION + 1. We get the +1 from the ARRAY_LEN macro.
|
|
//
|
|
|
|
if (cchFilename < (ARRAY_LEN(TSZ_AT_JOB_PREFIX) + s_cchExtension))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// After the prefix there must be only digits up to the extension.
|
|
// Leading zeros are not allowed.
|
|
//
|
|
|
|
ULONG i;
|
|
BOOL fZerosAreLeading = TRUE;
|
|
|
|
for (i = ARRAY_LEN(TSZ_AT_JOB_PREFIX) - 1; i < cchFilename; i++)
|
|
{
|
|
if (L'0' == wszFilename[i])
|
|
{
|
|
if (fZerosAreLeading)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (wszFilename[i] < L'0' || wszFilename[i] > L'9')
|
|
{
|
|
break; // should've just hit .job
|
|
}
|
|
else
|
|
{
|
|
fZerosAreLeading = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We stopped at a non-digit. We should now be at a bogus character or
|
|
// the start of the extension. Since the findfirst/next routines wouldn't
|
|
// have returned a filename that didn't have the appropriate extension,
|
|
// it's not necessary to do a string compare, we can just require that the
|
|
// number of characters remaining is the same as the length of the
|
|
// extension.
|
|
//
|
|
|
|
if (cchFilename - i != s_cchExtension)
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: TranslateAccount
|
|
//
|
|
// Synopsis: Translates local system accounts between internal and external representations.
|
|
// Passes other accounts through unaltered.
|
|
//
|
|
// If translating a local system account into the internal representation, if the password
|
|
// passed in was empty, then it sets the pointer to NULL for use with the local system account,
|
|
// since the user has no way of setting it to NULL themselves. The password isn't touched
|
|
// otherwise.
|
|
//
|
|
// Arguments:
|
|
// IN TRANSLATION_DIRECTION tdDirection -- which way is translation being performed
|
|
// IN LPCWSTR pwszAccountIn -- account to be translated
|
|
// IN OUT LPWSTR pwszAccountOut -- translated account
|
|
// IN DWORD cchAccountOut -- size of buffer for translated account
|
|
// IN OUT LPWSTR* ppwszPassword -- pointer to pointer to password
|
|
//
|
|
// Returns: HRESULT indicating success or failure
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
TranslateAccount(
|
|
TRANSLATION_DIRECTION tdDirection,
|
|
LPCWSTR pwszAccountIn,
|
|
LPWSTR pwszAccountOut,
|
|
DWORD cchAccountOut,
|
|
LPWSTR* ppwszPassword)
|
|
{
|
|
if (!pwszAccountIn || !pwszAccountOut)
|
|
return E_FAIL;
|
|
|
|
const WCHAR DISPLAY_LOCAL_SYSTEM[] = L"NT AUTHORITY\\SYSTEM";
|
|
const WCHAR* pwszDisplaySystem = DISPLAY_LOCAL_SYSTEM + 13; // points to "SYSTEM" substring in string above
|
|
|
|
if (tdDirection == TRANSLATE_FOR_DISPLAY)
|
|
{
|
|
if (pwszAccountIn[0] == L'\0') // internal representation of local system
|
|
{
|
|
lstrcpynW(pwszAccountOut, DISPLAY_LOCAL_SYSTEM, cchAccountOut);
|
|
}
|
|
else
|
|
{
|
|
lstrcpynW(pwszAccountOut, pwszAccountIn, cchAccountOut);
|
|
}
|
|
}
|
|
else // translating for internal use
|
|
{
|
|
if (lstrcmpiW(pwszAccountIn, DISPLAY_LOCAL_SYSTEM) == 0 || lstrcmpiW(pwszAccountIn, pwszDisplaySystem) == 0)
|
|
{
|
|
pwszAccountOut[0] = L'\0'; // internal representation of local system
|
|
|
|
if (ppwszPassword && (*ppwszPassword[0] == L'\0'))
|
|
*ppwszPassword = NULL;
|
|
}
|
|
else
|
|
{
|
|
lstrcpynW(pwszAccountOut, pwszAccountIn, cchAccountOut);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: OpenFileWithRetry
|
|
//
|
|
// Synopsis: Attempt to open file with access and share mode supplied.
|
|
// If open fails due to sharing violation, try again, up to two times.
|
|
//
|
|
// Arguments: [ptszFileName]
|
|
// [dwDesiredAccess]
|
|
// [dwDesiredShareMode]
|
|
// [hFile]
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
OpenFileWithRetry(LPCTSTR ptszFileName, DWORD dwDesiredAccess, DWORD dwDesiredShareMode, HANDLE* phFile)
|
|
{
|
|
DWORD Status = 0;
|
|
HANDLE hFile;
|
|
|
|
//
|
|
// Retry open if it is in use by another process, up to two more times
|
|
//
|
|
for (DWORD i = 3; i; i--)
|
|
{
|
|
hFile = CreateFile(ptszFileName,
|
|
dwDesiredAccess,
|
|
dwDesiredShareMode,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
Status = GetLastError();
|
|
|
|
if (Status == ERROR_SHARING_VIOLATION)
|
|
{
|
|
//
|
|
// Wait semi-pseudo-random amount of time before trying again.
|
|
//
|
|
Sleep(250 + (rand() % 250));
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (Status)
|
|
{
|
|
schDebugOut((DEB_ERROR, "Open of task file '%ws' failed with error %u\n", ptszFileName, Status));
|
|
return HRESULT_FROM_WIN32(Status);
|
|
}
|
|
|
|
*phFile = hFile;
|
|
|
|
return S_OK;
|
|
}
|
|
|