//+---------------------------------------------------------------------------
//
//  Scheduling Agent Service
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1996.
//
//  File:       misc.cxx
//
//  Contents:   Miscellaneous helper functions
//
//  Classes:    None.
//
//  Functions:  StringFromTrigger, CreateFolders, GetDaysOfWeekString,
//              GetExitCodeString, GetSageExitCodeString
//
//  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:   UnicodeToAnsi
//
//  Synopsis:   Convert unicode string [pwsz] to multibyte in buffer [sz].
//
//  Arguments:  [szTo]     - destination buffer
//              [pwszFrom] - source string
//              [cbTo]     - size of destination buffer, in bytes
//
//  Returns:    S_OK               - conversion succeeded
//              HRESULT_FROM_WIN32 - WideCharToMultiByte failed
//
//  Modifies:   *[szTo]
//
//  History:    10-29-96   DavidMun   Created
//
//  Notes:      The string in [szTo] will be NULL terminated even on
//              failure.
//
//----------------------------------------------------------------------------

HRESULT
UnicodeToAnsi(
    LPSTR   szTo,
    LPCWSTR pwszFrom,
    ULONG   cbTo)
{
    HRESULT hr = S_OK;
    ULONG   cbWritten;

    cbWritten = WideCharToMultiByte(CP_ACP,
                                    0,
                                    pwszFrom,
                                    -1,
                                    szTo,
                                    cbTo,
                                    NULL,
                                    NULL);

    if (!cbWritten)
    {
        szTo[cbTo - 1] = '\0'; // ensure NULL termination

        hr = HRESULT_FROM_WIN32(GetLastError());
        schDebugOut((DEB_ERROR,
                     "UnicodeToAnsi: WideCharToMultiByte hr=0x%x\n",
                     hr));
    }
    return hr;
}




//+---------------------------------------------------------------------------
//
//  Function:   AnsiToUnicode
//
//  Synopsis:   Convert ANSI string [szFrom] to Unicode string in buffer
//              [pwszTo].
//
//  Arguments:  [pwszTo] - destination buffer
//              [szFrom] - source string
//              [cchTo]  - size of destination buffer, in WCHARS
//
//  Returns:    S_OK               - conversion succeeded
//              HRESULT_FROM_WIN32 - MultiByteToWideChar failed
//
//  Modifies:   *[pwszTo]
//
//  History:    10-29-96   DavidMun   Created
//
//  Notes:      The string in [pwszTo] will be NULL terminated even on
//              failure.
//
//----------------------------------------------------------------------------

HRESULT
AnsiToUnicode(
    LPWSTR pwszTo,
    LPCSTR szFrom,
    LONG   cchTo)
{
    HRESULT hr = S_OK;
    ULONG   cchWritten;

    cchWritten = MultiByteToWideChar(CP_ACP, 0, szFrom, -1, pwszTo, cchTo);

    if (!cchWritten)
    {
        pwszTo[cchTo - 1] = L'\0';  // ensure NULL termination

        hr = HRESULT_FROM_WIN32(GetLastError());
        schDebugOut((DEB_ERROR,
                     "AnsiToUnicode: MultiByteToWideChar hr=0x%x\n",
                     hr));
    }
    return hr;
}




//+----------------------------------------------------------------------------
//
//  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;
            }
            wsprintf(tszTrigType, 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;
            }
            wsprintf(tszTrigType, tszFormat, tszDOW);
        }
        else
        {
            //
            // Run every WeeksInterval weeks: "every %s of every %s weeks"
            //
            wsprintf(tszNumber, 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;
            }
            wsprintf(tszTrigType, tszFormat, wFirstDay);
        }
        else
        {
            //
            // Specific months: "on day %s of %s"
            //
            wsprintf(tszNumber, 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;
            }
            wsprintf(tszDaily, 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.
                //
                wsprintf(tszNum, tszNumFmt, pTrigger->MinutesInterval);
                uIntTimeStr = IDS_MINUTES_PAREN;
            }
            else
            {
                //
                // Runs on an hourly schedule.
                //
                wsprintf(tszNum, 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.
                    //
                    wsprintf(tszDuration, tszNumFmt, pTrigger->MinutesDuration);
                    uDurTimeStr = IDS_MINUTES;
                }
                else
                {
                    //
                    // No remainder, so express the duration in hours.
                    //
                    wsprintf(tszDuration, 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;
#ifdef UNICODE
        if (lpDetails) {
            if (lpDetails->fmt & LVCFMT_RIGHT_TO_LEFT) {
                dwDateFlags |=  DATE_RTLREADING;
            } else if (lpDetails->fmt & LVCFMT_LEFT_TO_RIGHT) {
                dwDateFlags |=  DATE_LTRREADING;
            }
        }
#endif
        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;
#ifdef UNICODE
                if (lpDetails) {
                    if (lpDetails->fmt & LVCFMT_RIGHT_TO_LEFT) {
                        dwDateFlags |=  DATE_RTLREADING;
                    } else if (lpDetails->fmt & LVCFMT_LEFT_TO_RIGHT) {
                        dwDateFlags |=  DATE_LTRREADING;
                    }
                }
#endif
                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 cb;
    //
    // Get the size to allocate for the returned string.
    //

#if defined(UNICODE)

    cb = wcslen(tszTriggerString) + 1;  // Include the null in the count.

#else

    cb = MultiByteToWideChar(CP_ACP, 0, tszTriggerString, -1, NULL, 0);

    if (!cb)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ERR_OUT("Create trigger string: MultiByteToWideChar", hr);
        return hr;
    }

#endif

    //
    // Allocate the returned string.
    //

    *ppwszTrigger = (LPWSTR)CoTaskMemAlloc(cb * sizeof(WCHAR));

    if (*ppwszTrigger == NULL)
    {
        return E_OUTOFMEMORY;
    }

#if defined(UNICODE)

    wcscpy(*ppwszTrigger, tszTriggerString);

#else   // convert ANSI string to UNICODE

    hr = AnsiToUnicode(*ppwszTrigger, tszTriggerString, cb);

    if (FAILED(hr))
    {
        CoTaskMemFree(*ppwszTrigger);
        *ppwszTrigger = NULL;
        ERR_OUT("Create trigger string: AnsiToUnicode", hr);
        return hr;
    }

#endif
    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;

    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(sg_lcid, LOCALE_SABBREVDAYNAME1, ptszBuf, cchBuf))
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            ERR_OUT("GetLocaleInfo", hr);
            return hr;
        }
        fMoreThanOne = TRUE;
    }

    if (rgfDaysOfTheWeek & TASK_TUESDAY)
    {
        if (fMoreThanOne)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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)
        {
            lstrcat(ptszBuf, tszSep);
        }
        cch = lstrlen(ptszBuf);
        if (!GetLocaleInfo(sg_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;
    }

    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)
            {
                lstrcat(ptszBuf, tszSep);
            }
            cch = lstrlen(ptszBuf);
            if (!GetLocaleInfo(sg_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;
        }

        wsprintf(szErrCode, 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:   CreateFolders
//
//  Synopsis:   Creates any missing  directories for any slash delimited folder
//              names in the path.
//
//  Arguments:  [ptszPathName] - the path name
//              [fHasFileName] - if true, the path name includes a file name.
//
//  Returns:    HRESULTS
//
//  Notes:      ptszPathName should never end in a slash.
//              Treats forward and back slashes identically.
//-----------------------------------------------------------------------------
HRESULT
CreateFolders(LPCTSTR ptszPathName, BOOL fHasFileName)
{
    //schDebugOut((DEB_ITRACE, "CreateFolders\n"));
    //
    // Copy the string so we can munge it
    //
    TCHAR * ptszPath = new TCHAR[lstrlen(ptszPathName) + 2];
    if (!ptszPath)
    {
        return E_OUTOFMEMORY;
    }
    lstrcpy(ptszPath, ptszPathName);

    if (!fHasFileName)
    {
        //
        // If no file name, append a slash so the following logic works
        // correctly.
        //
        lstrcat(ptszPath, TEXT("\\"));
    }

    //
    // Get a pointer to the last slash in the name.
    //

    TCHAR * ptszSlash = _tcsrchr(ptszPath, TEXT('\\'));

    if (ptszSlash == NULL)
    {
        //
        // no slashes found, so nothing to do
        //
        delete [] ptszPath;
        return S_OK;
    }

    if (fHasFileName)
    {
        //
        // Chop off the file name, leaving the slash as the last char.
        //
        ptszSlash[1] = TEXT('\0');
    }

    BOOL fFullPath = (lstrlen(ptszPath) > 2        &&
                      s_isDriveLetter(ptszPath[0]) &&
                      ptszPath[1] == TEXT(':'));

    //
    // Walk the string looking for slashes. Each found slash is temporarily
    // replaced with a null and that substring passed to CreateDir.
    //
    TCHAR * ptszTail = ptszPath;
    while (ptszSlash = _tcspbrk(ptszTail, TEXT("\\/")))
    {
        //
        // If the path name starts like C:\ then the first slash will be at
        // the third character
        //

        if (fFullPath && (ptszSlash - ptszTail == 2))
        {
            //
            // We are looking at the root of the drive, so don't try to create
            // a root directory.
            //
            ptszTail = ptszSlash + 1;
            continue;
        }
        *ptszSlash = TEXT('\0');
        if (!CreateDirectory(ptszPath, NULL))
        {
            DWORD dwErr = GetLastError();
            if (dwErr != ERROR_ALREADY_EXISTS)
            {
                delete [] ptszPath;
                return (HRESULT_FROM_WIN32(dwErr));
            }
        }
        *ptszSlash = TEXT('\\');
        ptszTail = ptszSlash + 1;
    }
    delete [] ptszPath;
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  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:   GetSageExitCodeString
//
//  Synopsis:   Retrieve the string associated with the exit code the SAGE
//              way. Algorithm:
//
//              Consult the Software\Microsoft\System Agent\SAGE subkey.
//
//              Enumerate the subkeys to find a subkey with a "Program" string
//              value specifying an executable name matching that of the job.
//
//              If such a subkey was found, open the Result Codes subkey and
//              fetch the value name matching the exit code. The string value
//              is the exit code string.
//
//  Arguments:  [ptszExitCodeValue] -- Job exit code in string form.
//              [ptszJobExecutable] -- 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 *
GetSageExitCodeString(TCHAR * ptszExitCodeValue, TCHAR * ptszJobExecutable)
{
    static TCHAR tszSage[] = TEXT("SOFTWARE\\Microsoft\\Plus!\\System Agent\\SAGE");
    static TCHAR tszProgram[]     = TEXT("Program");
    static TCHAR tszResultCodes[] = TEXT("Result Codes");
    TCHAR        tszBuffer[MAX_PATH + 1];
    DWORD        cbBufferSize;
    TCHAR *      ptszExitCode     = NULL;
    HKEY         hKeySage        = NULL;
    HKEY         hSubKey         = NULL;
    HKEY         hKeyResultCodes = NULL;
    DWORD        dwType;

    // Open the Plus!\SAGE key, if it exists.
    //
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, tszSage, 0, KEY_READ, &hKeySage))
    {
        return(NULL);   // Didn't find it, or failed trying.
    }

    DWORD dwIndex;
    LONG  lRet;

    for (dwIndex = 0, lRet = 0; lRet != ERROR_NO_MORE_ITEMS && !lRet;
                            dwIndex++)
    {
        // Enum SAGE registry subkeys under software\msft\plus!\sage.
        //
        cbBufferSize = MAX_PATH + 1;    // RegEnumKeyEx character count

        lRet = RegEnumKeyEx(hKeySage,
                             dwIndex,
                             tszBuffer,
                             &cbBufferSize,
                             NULL,
                             NULL,
                             NULL,
                             NULL);     // BUGBUG : Last arg may fault.

        if (!lRet)
        {
            // Open the key & fetch the 'Program' key value.
            //
            if (RegOpenKeyEx(hKeySage, tszBuffer, 0, KEY_READ, &hSubKey))
            {
                break;                  // No sense continuing.
            }

            cbBufferSize = sizeof(tszBuffer);

            if (RegQueryValueEx(hSubKey,
                                tszProgram,
                                NULL,
                                &dwType,
                                (UCHAR *)tszBuffer,
                                &cbBufferSize) && dwType == REG_SZ)
            {
                break;                  // No sense continuing.
            }

            // Compare program name & job app name. If there's
            // a match, fetch the exit code string.
            //
            if (!lstrcmpi(tszBuffer, ptszJobExecutable))
            {
                // Open the 'Result Codes' sub key & fetch the exit code
                // string.
                //
                if (RegOpenKeyEx(hSubKey,
                                 tszResultCodes,
                                 0,
                                 KEY_READ,
                                 &hKeyResultCodes))
                {
                    break;              // No sense continuing.
                }

                // First obtain the string size, allocate a buffer,
                // then fetch it.
                //
                cbBufferSize = 0;       // To obtain string size.

                if (RegQueryValueEx(hKeyResultCodes,
                                    ptszExitCodeValue,
                                    NULL,
                                    &dwType,
                                    (UCHAR *)tszBuffer,
                                    &cbBufferSize) != ERROR_MORE_DATA)
                {
                    break;              // No sense continuing.
                }

                ptszExitCode = (TCHAR *)LocalAlloc(LMEM_FIXED, cbBufferSize);

                if (ptszExitCode == NULL)
                {
                    CHECK_HRESULT(E_OUTOFMEMORY);
                    break;              // No sense continuing.
                }

                cbBufferSize = sizeof(tszBuffer);

                if (!RegQueryValueEx(hKeyResultCodes,
                                     ptszExitCodeValue,
                                     NULL,
                                     &dwType,
                                     (UCHAR *)tszBuffer,
                                     &cbBufferSize) && dwType == REG_SZ)
                {
                    CopyMemory(ptszExitCode, tszBuffer, cbBufferSize);
                    break;
                }
                else
                {
                    LocalFree(ptszExitCode);
                    ptszExitCode = NULL;
                    break;
                }
            }
        }
    }

    // Cleanup.
    //
    if (hKeySage != NULL) RegCloseKey(hKeySage);
    if (hSubKey != NULL) RegCloseKey(hSubKey);
    if (hKeyResultCodes != NULL) RegCloseKey(hKeyResultCodes);

    return(ptszExitCode);
}

//+---------------------------------------------------------------------------
//
//  Function:   GetParentDirectory
//
//  Synopsis:   Return the parent directory of the path indicated.
//
//  Arguments:  [ptszPath]     -- Input path.
//              [tszDirectory] -- Caller-allocated returned directory.
//
//  Returns:    None.
//
//  Notes:      None.
//
//----------------------------------------------------------------------------
void
GetParentDirectory(LPCTSTR ptszPath, TCHAR tszDirectory[])
{
    lstrcpy(tszDirectory, 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;
        }

        lstrcpy(ptszNewPath, tszAppPathVar);

        if (0 != cchOriginalPath)
        {
            ptszNewPath[cchNewPath - 1] = TEXT(';');
            lstrcpy(&ptszNewPath[cchNewPath], *pptszSavedPath);
        }

        //
        // Finally ready to set the path environment variable
        //

        fChangedPath = SetEnvironmentVariable(PATH_ENV_VAR, ptszNewPath);
        delete [] ptszNewPath;
    } while (0);

    return fChangedPath;
}


#if !defined(_CHICAGO_)

//+---------------------------------------------------------------------------
//
//  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);
    }

    lstrcpy(tszPath, g_TasksFolderInfo.ptszPath);
    lstrcat(tszPath, TEXT("\\"));
    lstrcat(tszPath, 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;
}



#endif // !defined(_CHICAGO_)