//  Copyright (c) 1998-1999 Microsoft Corporation
/******************************************************************************
*
*  MSG.C
*     Send a message to another user.
*
*  Syntax:
*
*    MSG [username]       [/TIME:seconds] [/V] [/W] [/?] [message]\n" \
*    MSG [WinStationName] [/TIME:seconds] [/V] [/W] [/?] [message]\n" \
*    MSG [logonid]        [/TIME:seconds] [/V] [/W] [/?] [message]\n" \
*    MSG [@filename]      [/TIME:seconds] [/V] [/W] [/?] [message]\n" \
*
*    /TIME:seconds - time delay to wait for receiver to acknowledge msg\n" \
*    /V            - display information about actions being performed\n" \
*    /W            - wait for response from user, useful with /V
*    /?            - display syntax and options\n"
*
*  Parameters:
*
*      username
*         Identifies all logins belonging to the specific username
*
*      winstationname
*         Identifies all logins connected to the winstation name regardless
*         of loginname.
*
*      logonid
*         Decimal number specifying a logon id to send the message to
*
*      @filename
*         Identifies a file that contains usernames or winstation names to
*         send messages to.
*
*   Options:
*
*      /SELF  >>>> UNPUBLISHED <<<<
*         Send message to caller of msg.  Used to send a message to
*         users when maintenace mode is enabled.
*
*      /TIME:seconds (time delay)
*         The amount of time to wait for an acknowledgement from the target
*         login that the message has been received.
*
*      /V (verbose)
*         Display information about the actions being performed.
*
*      /? (help)
*         Display the syntax for the utility and information about the
*         options.
*
*      message
*         The text of the message to send.  If the text is not specified
*         then the text is read from STDIN.
*
*   Remarks:
*
*     The message can be typed on the command line or be read from STDIN.
*     The message is sent via a popup.  The user receiving the popup can
*     hit a key to get rid of it or it will go away after a default timeout.
*
*     If the target of the message is a terminal, then the message is
*     sent to all logins on the target terminal.
*
*
*******************************************************************************/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

#include <stdio.h>
#include <windows.h>
#include <ntddkbd.h>
#include <ntddmou.h>
#include <winstaw.h>
#include <stdlib.h>
#include <time.h>
#include <utilsub.h>
#include <process.h>
#include <string.h>
#include <malloc.h>
#include <wchar.h>
#include <io.h>  // for isatty
#include <locale.h>

#include "msg.h"
#include "printfoa.h"

/*=============================================================================
==   Global Data
=============================================================================*/

ULONG      Seconds;
USHORT     file_flag=FALSE;   //wkp
USHORT     v_flag;
USHORT     self_flag;
USHORT     help_flag;
WCHAR      ids_input[MAX_IDS_LEN];
PWCHAR     MsgText, MsgTitle;
WCHAR      MsgLine[MAX_IDS_LEN];
ULONG      gCurrentLogonId = (ULONG)(-1);
BOOLEAN    wait_flag = FALSE;
HANDLE     hServerName = SERVERNAME_CURRENT;
WCHAR      ServerName[MAX_IDS_LEN+1];

/*
 * The token map structure is used for parsing program arguments
 */
TOKMAP ptm[] = {
   { TOKEN_INPUT,       TMFLAG_REQUIRED, TMFORM_STRING,   MAX_IDS_LEN,
                            ids_input },

   { TOKEN_SERVER,      TMFLAG_OPTIONAL, TMFORM_STRING,   MAX_IDS_LEN,
                            ServerName},

   { TOKEN_MESSAGE,     TMFLAG_OPTIONAL, TMFORM_X_STRING, MAX_IDS_LEN,
                            MsgLine },

   { TOKEN_TIME,        TMFLAG_OPTIONAL, TMFORM_ULONG,    sizeof(ULONG),
                            &Seconds },

   { TOKEN_VERBOSE,     TMFLAG_OPTIONAL, TMFORM_BOOLEAN,  sizeof(USHORT),
                            &v_flag },

   { TOKEN_WAIT,        TMFLAG_OPTIONAL, TMFORM_BOOLEAN,  sizeof(BOOLEAN),
                            &wait_flag },

   { TOKEN_SELF,        TMFLAG_OPTIONAL, TMFORM_BOOLEAN,  sizeof(USHORT),
                            &self_flag },

   { TOKEN_HELP,        TMFLAG_OPTIONAL, TMFORM_BOOLEAN,  sizeof(USHORT),
                            &help_flag },

   { 0, 0, 0, 0, 0}
};

/*
 * This is the list of names we are to send the message to
 */
int NameListCount = 0;
WCHAR **NameList = NULL;
WCHAR CurrUserName[USERNAME_LENGTH];

/*
 * Local function prototypes
 */
BOOLEAN SendMessageIfTarget( PLOGONID Id, ULONG Count,
                             LPWSTR pTitle, LPWSTR pMessage );
BOOLEAN CheckMatchList( PLOGONID );
BOOLEAN MessageSend( PLOGONID pLId, LPWSTR pTitle, LPWSTR pMessage );
BOOLEAN LoadFileToNameList( PWCHAR pName );
BOOL ReadFileByLine( HANDLE, PCHAR, DWORD, PDWORD );
void Usage( BOOLEAN bError );


/*****************************************************************************
*
*   MAIN
*
*   ENTRY:
*      argc - count of the command line arguments.
*      argv - vector of strings containing the command line arguments.
*
****************************************************************************/

int __cdecl
main(INT argc, CHAR **argv)
{
    // struct tm * pTimeDate;
    // time_t      curtime;
    SYSTEMTIME st;
    WCHAR       TimeStamp[ MAX_TIME_DATE_LEN ];
    WCHAR      *CmdLine;
    WCHAR      **argvW;
    WCHAR       szTitleFormat[50];
    DWORD       dwSize;
    PLOGONID pTerm;
    UINT       TermCount;
    ULONG      Status;
    int        i, rc, TitleLen;
    BOOLEAN MatchedOne = FALSE;

    setlocale(LC_ALL, ".OCP");

    /*
     *  Massage the command line.
     */

    argvW = MassageCommandLine((DWORD)argc);
    if (argvW == NULL) {
        ErrorPrintf(IDS_ERROR_MALLOC);
        return(FAILURE);
    }

    /*
     *  parse the cmd line without parsing the program name (argc-1, argv+1)
     */
    rc = ParseCommandLine(argc-1, argvW+1, ptm, 0);

    /*
     *  Check for error from ParseCommandLine
     */
    if (rc && (rc & PARSE_FLAG_NO_PARMS) )
       help_flag = TRUE;

    if ( help_flag || rc ) {
         if (!help_flag) {
            Usage(TRUE);
            return(FAILURE);

        } else {
            Usage(FALSE);
            return(SUCCESS);
        }
    }

        // If no remote server was specified, then check if we are running under Terminal Server
        if ((!IsTokenPresent(ptm, TOKEN_SERVER) ) && (!AreWeRunningTerminalServices()))
        {
            ErrorPrintf(IDS_ERROR_NOT_TS);
            return(FAILURE);
        }

    /*
     * Open the specified server
     */
    if( ServerName[0] ) {
        hServerName = WinStationOpenServer( ServerName );
        if( hServerName == NULL ) {
            StringErrorPrintf(IDS_ERROR_SERVER,ServerName);
            PutStdErr( GetLastError(), 0 );
            return(FAILURE);
        }
    }

    /*
     * if no timeout was specified, use default
     */
    if ( !IsTokenPresent(ptm, TOKEN_TIME) )
        Seconds = RESPONSE_TIMEOUT;

    /*
     * allocate a buffer for the message header
     */
    if ( (MsgText = (PWCHAR)malloc(MAX_IDS_LEN * sizeof(WCHAR))) == NULL ) {
        ErrorPrintf(IDS_ERROR_MALLOC);
        return(FAILURE);
    }
    MsgText[0] = 0;

    /*
     * set up message header text: sender and timestamp
     */
    GetCurrentUserName(CurrUserName, USERNAME_LENGTH);

    /*
     * Get the current Winstation Id for this process
     */
    gCurrentLogonId = GetCurrentLogonId();

    /*
     * Form message title string.
     */
    dwSize = sizeof(szTitleFormat) / sizeof(WCHAR);

    LoadString(NULL,IDS_TITLE_FORMAT,szTitleFormat,dwSize);
    
    TitleLen = (wcslen(szTitleFormat) + wcslen(CurrUserName) + 1) * sizeof(WCHAR) + ( 2 * sizeof( TimeStamp ) );

    MsgTitle = (PWCHAR)malloc(TitleLen);

    if( MsgTitle == NULL )
    {
        ErrorPrintf(IDS_ERROR_MALLOC);
        return(FAILURE);
    }


    _snwprintf(MsgTitle, TitleLen, szTitleFormat, CurrUserName);

    TimeStamp[0] = 0;

    GetLocalTime( &st );

    GetDateFormat( LOCALE_USER_DEFAULT , 
                   DATE_SHORTDATE ,
                   &st ,
                   NULL ,
                   TimeStamp,
                   MAX_TIME_DATE_LEN );


    wcscat(MsgTitle , TimeStamp);

    TimeStamp[0] = 0;

    GetTimeFormat( LOCALE_USER_DEFAULT , 
                   TIME_NOSECONDS ,
                   &st ,
                   NULL ,
                   TimeStamp,
                   MAX_TIME_DATE_LEN );

    wcscat(MsgTitle , L" " );
    wcscat(MsgTitle , TimeStamp);
    
    /*
     * if message was specified on the command line, add it to MsgText string
     */
    if ( IsTokenPresent(ptm, TOKEN_MESSAGE) ) {

        MsgText = realloc(MsgText, (wcslen(MsgText) + wcslen(MsgLine) + 1) * sizeof(WCHAR));
        if ( MsgText == NULL ) {
            ErrorPrintf(IDS_ERROR_MALLOC);
            return(FAILURE);
        }
        wcscat(MsgText, MsgLine);

    } else {

        /*
         * Message was not on the command line.  If STDIN is connected to
         * the keyboard, then prompt the user for the message to send,
         * otherwise just read STDIN.
         */

        if ( _isatty( _fileno(stdin) ) )
            Message(IDS_MESSAGE_PROMPT);


        while ( wfgets(MsgLine, MAX_IDS_LEN, stdin) != NULL ) {
            MsgText = (PWCHAR)realloc(
                            MsgText,
                            (wcslen(MsgText) + wcslen(MsgLine) + 1) * sizeof(WCHAR) );

            if ( MsgText == NULL ) {
                ErrorPrintf(IDS_ERROR_MALLOC);
                return(FAILURE);
            }
            wcscat(MsgText, MsgLine);
        }

        /*
         * When we fall through, we either have an eof or a problem with
         * STDIN
         */
        if ( feof(stdin) ) {

            /*
             * If we get here then we hit eof on STDIN.  First check to make
             * sure that we did not get an eof on first wfgets
             */
            if ( !wcslen(MsgText) ) {
                ErrorPrintf(IDS_ERROR_EMPTY_MESSAGE);
                return(FAILURE);
            }

        } else {

            /*
             * The return from wfgets was not eof so we have an STDIN
             * problem
             */
            ErrorPrintf(IDS_ERROR_STDIN_PROCESSING);
            return(FAILURE);
        }
    }

    /*
     * Is the ids_input really a file indirection?
     */
    if ( ids_input[0] == L'@' ) {

        /*
         * Open the input file and read the names into the NameList
         */
        if ( !LoadFileToNameList(&ids_input[1]) )
            return(FAILURE);

        /*
         * Ok, let's get in touch
         */
        file_flag = TRUE;

    } else {

        _wcslwr( ids_input );
        NameList = (WCHAR **)malloc( 2 * sizeof( WCHAR * ) );
        if ( NameList == NULL ) {
            ErrorPrintf(IDS_ERROR_MALLOC);
            return(FAILURE);
        }
        NameList[0] = ids_input;
        NameList[1] = NULL;
        NameListCount = 1;
    }

    /*
     * Enumerate across all the WinStations and send the message
     * to them if there are any matches in the NameList
     */
    if ( WinStationEnumerate(hServerName, &pTerm, &TermCount) ) {

        if ( SendMessageIfTarget(pTerm, TermCount, MsgTitle, MsgText) )
            MatchedOne = TRUE;

        WinStationFreeMemory(pTerm);

    } else{

        Status = GetLastError();
        ErrorPrintf(IDS_ERROR_WINSTATION_ENUMERATE, Status);
        return(FAILURE);
    }

    /*
     *  Check for at least one match
     */
    if ( !MatchedOne ) {

        if( file_flag )
            StringErrorPrintf(IDS_ERROR_NO_FILE_MATCHING, &ids_input[1]);
        else
            StringErrorPrintf(IDS_ERROR_NO_MATCHING, ids_input);

        return(FAILURE);

    }

    return(SUCCESS);

}  /* main() */


/******************************************************************************
 *
 *  SendMessageIfTarget - Send a Message to a group of WinStations if
 *                        their the target as specified by TargetName.
 *
 *  ENTRY
 *      LId (input)
 *          Pointer to an array of LOGONIDs returned from WinStationEnumerate()
 *      Count (input)
 *          Number of elements in LOGONID array.
 *      pTitle (input)
 *          Points to message title string.
 *      pMessage (input)
 *          Points to message string.
 *
 *  EXIT
 *      TRUE if message was sent to at least one WinStation; FALSE otherwise.
 *
 *****************************************************************************/

BOOLEAN
SendMessageIfTarget( PLOGONID Id,
                     ULONG Count,
                     LPWSTR pTitle,
                     LPWSTR pMessage )
{
    ULONG i;
    BOOLEAN MatchedOne = FALSE;

    for ( i=0; i < Count ; i++ ) {
        /*
         * Look at Id->WinStationName, get its User, etc. and compare
         * against targetname(s). Accept '*' as "everything".
         */
        if( CheckMatchList( Id ) )
        {
            MatchedOne = TRUE;

            MessageSend(Id, pTitle, pMessage);
                
        }
        Id++;
    }
    return( MatchedOne );

}  /* SendMessageIfTarget() */


/******************************************************************************
 *
 *  CheckMatchList - Returns TRUE if the current WinStation is a match for
 *                   sending a message due to either its name, id, or the
 *                   name of its logged on user being in the message target(s)
 *                   list.
 *
 *  ENTRY
 *      LId (input)
 *          Pointer to a LOGONID returned from WinStationEnumerate()
 *
 *  EXIT
 *      TRUE if this is a match, FALSE otherwise.
 *
 *****************************************************************************/

BOOLEAN
CheckMatchList( PLOGONID LId )
{
    int i;

    /*
     * Wild card matches everything
     */
    if ( ids_input[0] == L'*' ) {
        return(TRUE);
    }

    /*
     * Loop through name list to see if any given name applies to
     * this WinStation
     */
    for( i=0; i<NameListCount; i++ ) {
        if (WinStationObjectMatch( hServerName , LId, NameList[i]) ) {
            return(TRUE);
        }
    }

    return(FALSE);
}


/******************************************************************************
 *
 *  MessageSend - Send a message to the target WinStation
 *
 *  ENTRY
 *      LId (input)
 *          Pointer to a LOGONID returned from WinStationEnumerate()
 *      pTitle (input)
 *          Points to message title string.
 *      pMessage (input)
 *          Points to message string.
 *
 *  EXIT
 *      TRUE message is sent, FALSE otherwise.
 *
 *****************************************************************************/

BOOLEAN
MessageSend( PLOGONID LId,
             LPWSTR pTitle,
             LPWSTR pMessage )
{
    ULONG idResponse, ReturnLength;
    WINSTATIONINFORMATION WSInfo;

    /*
     * Make sure that the WinStation is in the 'connected' state
     */
    if ( !WinStationQueryInformation( hServerName,
                                      LId->LogonId,
                                      WinStationInformation,
                                      &WSInfo,
                                      sizeof(WSInfo),
                                      &ReturnLength ) ) {
        goto BadQuery;
    }

    if ( WSInfo.ConnectState != State_Connected &&
         WSInfo.ConnectState != State_Active ) {
        goto NotConnected;
    }

    /*
     * Send message.
     */
    if ( v_flag ) {
        if( LId->WinStationName[0] )
            StringDwordMessage(IDS_MESSAGE_WS, LId->WinStationName, Seconds);
        else
            Message(IDS_MESSAGE_ID, LId->LogonId, Seconds);

    }

    if ( !WinStationSendMessage( hServerName,
                                 LId->LogonId,
                                     pTitle,
                                 (wcslen(pTitle))*sizeof(WCHAR),
                                 pMessage,
                                 (wcslen(pMessage))*sizeof(WCHAR),
                                                 MB_OK,  // MessageBox() Style
                                                 Seconds,
                                                 &idResponse,
                                                 (BOOLEAN)(!wait_flag) ) ) {

        if( LId->WinStationName[0] )
            StringDwordErrorPrintf(IDS_ERROR_MESSAGE_WS, LId->WinStationName, GetLastError() );
        else
            ErrorPrintf(IDS_ERROR_MESSAGE_ID, LId->LogonId, GetLastError() );

        PutStdErr(GetLastError(), 0);
        goto BadMessage;
    }

    /*
     * Output response result if verbose mode.
     */
    if( v_flag ) {
        switch( idResponse ) {

            case IDTIMEOUT:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_TIMEOUT_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_TIMEOUT_ID, LId->LogonId);

                break;

            case IDASYNC:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_ASYNC_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_ASYNC_ID, LId->LogonId);
                break;

            case IDCOUNTEXCEEDED:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_COUNT_EXCEEDED_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_COUNT_EXCEEDED_ID,
                            LId->LogonId);
                break;

            case IDDESKTOPERROR:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_DESKTOP_ERROR_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_DESKTOP_ERROR_ID,
                            LId->LogonId);
                break;

            case IDERROR:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_ERROR_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_ERROR_ID,
                            LId->LogonId);
                break;

            case IDOK:
            case IDCANCEL:
                if( LId->WinStationName[0] )
                    StringMessage(IDS_MESSAGE_RESPONSE_WS,
                            LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_ID,
                            LId->LogonId);
                break;

            default:
                if( LId->WinStationName[0] )
                    DwordStringMessage(IDS_MESSAGE_RESPONSE_WS,
                            idResponse, LId->WinStationName);
                else
                    Message(IDS_MESSAGE_RESPONSE_ID,
                            idResponse, LId->LogonId);
                break;
        }
    }
    return(TRUE);

/*-------------------------------------
 * Error cleanup and return
 */
BadMessage:
NotConnected:
BadQuery:
    return(FALSE);

}  /* MessageSend() */


/******************************************************************************
 *
 *  LoadFileToNameList
 *
 *  Load names from a file into the input name list.
 *
 *  ENTRY:
 *    pName Name of the file to load from
 *
 *  EXIT:
 *      TRUE for sucessful name load from file; FALSE if error.
 *
 *      An appropriate error message will have been displayed on error.
 *
 *****************************************************************************/

BOOLEAN
LoadFileToNameList( PWCHAR pName )
{
    HANDLE  hFile;
    INT     CurrentSize;

    /*
     * Open input file.
     */

    hFile = CreateFile(
                pName,
                GENERIC_READ,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL
                );
    if (hFile == INVALID_HANDLE_VALUE) {
        StringErrorPrintf(IDS_ERROR_CANT_OPEN_INPUT_FILE, pName);
        PutStdErr(GetLastError(), 0);
        return(FALSE);
    }

    /*
     * Allocate a large array for the name string pointers
     */

    CurrentSize = 100;
    if ( !(NameList = (WCHAR **)malloc(CurrentSize * sizeof(WCHAR *))) ) {
        ErrorPrintf(IDS_ERROR_MALLOC);
        return(FAILURE);
    }

    NameListCount = 0;
    while( 1 ) {
        BOOL    fRet;
        CHAR    *pBuffer;
        DWORD   nBytesRead;
        WCHAR   *pwBuffer;

        /*
         * See if we need to grow the list
         */

        if( NameListCount == CurrentSize ) {

            if (!(NameList = (WCHAR **)realloc(NameList, CurrentSize+100))) {
                ErrorPrintf(IDS_ERROR_MALLOC);
                return(FAILURE);
            }
            CurrentSize += 100;
        }

        pBuffer = (CHAR *)LocalAlloc(LPTR, USERNAME_LENGTH * sizeof(CHAR));
        if (pBuffer == NULL) {
            ErrorPrintf(IDS_ERROR_MALLOC);
            return(FAILURE);
        }

        fRet = ReadFileByLine(
                    hFile,
                    pBuffer,
                    USERNAME_LENGTH,
                    &nBytesRead
                    );
        if (fRet && (nBytesRead > 0)) {
            INT cWChar;

            cWChar = MultiByteToWideChar(
                        CP_ACP,
                        MB_PRECOMPOSED,
                        pBuffer,
                        -1,
                        NULL,
                        0
                        );

            pwBuffer = (WCHAR *)LocalAlloc(LPTR, (cWChar + 1) * sizeof(WCHAR));
            if (pwBuffer != NULL) {
                MultiByteToWideChar(
                    CP_ACP,
                    MB_PRECOMPOSED,
                    pBuffer,
                    -1,
                    pwBuffer,
                    cWChar
                    );
            } else {
                ErrorPrintf(IDS_ERROR_MALLOC);
                return(FAILURE);
            }

            if (pwBuffer[wcslen(pwBuffer)-1] == L'\n') {
                pwBuffer[wcslen(pwBuffer)-1] = (WCHAR)NULL;
            }

            _wcslwr(pwBuffer);
            NameList[NameListCount++] = pwBuffer;
        } else {
            NameList[NameListCount] = NULL;
            CloseHandle(hFile);
            return(TRUE);
        }
    }

}  /* LoadFileToNameList() */

BOOL
ReadFileByLine(
    HANDLE  hFile,
    PCHAR   pBuffer,
    DWORD   cbBuffer,
    PDWORD  pcbBytesRead
    )
{
    BOOL    fRet;

    fRet = ReadFile(
                hFile,
                pBuffer,
                cbBuffer - 1,
                pcbBytesRead,
                NULL
                );
    if (fRet && (*pcbBytesRead > 0)) {
        CHAR*   pNewLine;

        pNewLine = strstr(pBuffer, "\r\n");
        if (pNewLine != NULL) {
            LONG    lOffset;

            lOffset = (LONG)(pNewLine + 2 - pBuffer) - (*pcbBytesRead);
            if (SetFilePointer(hFile, lOffset, NULL, FILE_CURRENT) ==
                0xFFFFFFFF) {
                return(FALSE);
            }

            *pNewLine = (CHAR)NULL;
        }

    }

    return(fRet);
}


/*******************************************************************************
 *
 *  Usage
 *
 *      Output the usage message for this utility.
 *
 *  ENTRY:
 *      bError (input)
 *          TRUE if the 'invalid parameter(s)' message should preceed the usage
 *          message and the output go to stderr; FALSE for no such error
 *          string and output goes to stdout.
 *
 * EXIT:
 *
 *
 ******************************************************************************/

void
Usage( BOOLEAN bError )
{
    if ( bError ) {
        ErrorPrintf(IDS_ERROR_INVALID_PARAMETERS);
        ErrorPrintf(IDS_USAGE1);
        ErrorPrintf(IDS_USAGE2);
        ErrorPrintf(IDS_USAGE3);
        ErrorPrintf(IDS_USAGE4);
        ErrorPrintf(IDS_USAGE5);
        ErrorPrintf(IDS_USAGE6);
        ErrorPrintf(IDS_USAGE7);
        ErrorPrintf(IDS_USAGE8);
        ErrorPrintf(IDS_USAGE9);
        ErrorPrintf(IDS_USAGEA);
        ErrorPrintf(IDS_USAGEB);
        ErrorPrintf(IDS_USAGEC);
        ErrorPrintf(IDS_USAGED);
        ErrorPrintf(IDS_USAGEE);
        ErrorPrintf(IDS_USAGEF);
    }
    else
    {
        Message(IDS_USAGE1);
        Message(IDS_USAGE2);
        Message(IDS_USAGE3);
        Message(IDS_USAGE4);
        Message(IDS_USAGE5);
        Message(IDS_USAGE6);
        Message(IDS_USAGE7);
        Message(IDS_USAGE8);
        Message(IDS_USAGE9);
        Message(IDS_USAGEA);
        Message(IDS_USAGEB);
        Message(IDS_USAGEC);
        Message(IDS_USAGED);
        Message(IDS_USAGEE);
        Message(IDS_USAGEF);
    }
}  /* Usage() */