2295 lines
54 KiB
C
2295 lines
54 KiB
C
/*++
|
||
|
||
Copyright (c) 1987-1992 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
atcmd.c
|
||
|
||
Abstract:
|
||
|
||
Code for AT command, to be used with SCHEDULE service on Windows NT.
|
||
|
||
The module was taken from LanManager\at.c and then modified considerably
|
||
to work with NT Schedule service.
|
||
|
||
Author:
|
||
|
||
Vladimir Z. Vulovic (vladimv) 06 - November - 1992
|
||
|
||
Environment:
|
||
|
||
User Mode - Win32
|
||
|
||
Revision History:
|
||
|
||
06-Nov-1992 vladimv
|
||
Created
|
||
|
||
20-Feb-1993 yihsins
|
||
Get rid of hard coded strings and parse/print time according
|
||
to user profile
|
||
|
||
25-May-1993 RonaldM
|
||
Convert strings to OEM before printing to the console, since
|
||
the console doesn't yet support unicode.
|
||
|
||
28-Jun-1993 RonaldM
|
||
Added the "confirm" yes and no strings, which are meant to be
|
||
localised. The original yes and no strings cannot be localised,
|
||
because this would create batch file incompatibilities.
|
||
|
||
07-Jul-1994 vladimv
|
||
Added support for interactive switch. Replaced "confirm" strings
|
||
with APE2_GEN_* strings - to eliminate redundancy. The rule is
|
||
that switches are not internationalizable while switch values are.
|
||
|
||
--*/
|
||
|
||
#include <nt.h> // DbgPrint prototype
|
||
#include <ntrtl.h> // DbgPrint
|
||
#include <nturtl.h> // Needed by winbase.h
|
||
|
||
#include <windows.h>
|
||
#include <winnls.h>
|
||
#include <shellapi.h>
|
||
|
||
#include <lmcons.h> // NET_API_STATUS
|
||
#include <lmerr.h> // NetError codes
|
||
#include <icanon.h> // NetpNameValidate
|
||
|
||
#include "lmatmsg.h" // private AT error codes & messages
|
||
#include <lmat.h> // AT_INFO
|
||
#include <stdlib.h> // exit()
|
||
#include <stdio.h> // printf
|
||
#include <wchar.h> // wcslen
|
||
#include <apperr.h> // APE_AT_USAGE
|
||
#include <apperr2.h> // APE2_GEN_MONDAY + APE2_*
|
||
#include <lmapibuf.h> // NetApiBufferFree
|
||
#include <timelib.h> // NetpGetTimeFormat
|
||
#include <luidate.h> // LUI_ParseTimeSinceStartOfDay
|
||
|
||
|
||
#define YES_FLAG 0
|
||
#define NO_FLAG 1
|
||
#define INVALID_FLAG -1
|
||
|
||
#define DUMP_ALL 0
|
||
#define DUMP_ID 1
|
||
#define ADD_TO_SCHEDULE 2
|
||
#define ADD_ONETIME 3
|
||
#define DEL_ID 4
|
||
#define DEL_ALL 5
|
||
#define ACTION_USAGE 6
|
||
#define MAX_COMMAND_LEN (MAX_PATH - 1) // == 259, based on value used in scheduler service for manipulating AT jobs
|
||
#define MAX_SCHED_FIELD_LENGTH 24
|
||
|
||
|
||
#define PutNewLine() GenOutput( TEXT("\n"))
|
||
#define PutNewLine2() GenOutput( TEXT("\n\n"))
|
||
|
||
#define MAX_MSG_BUFFER 1024
|
||
|
||
WCHAR ConBuf[MAX_MSG_BUFFER];
|
||
|
||
#define GenOutput(fmt) \
|
||
{wcscpy(ConBuf, fmt); \
|
||
ConsolePrint(ConBuf, wcslen(ConBuf));}
|
||
|
||
#define GenOutputArg(fmt, a1) \
|
||
{wsprintf(ConBuf, fmt, a1); \
|
||
ConsolePrint(ConBuf, wcslen(ConBuf));}
|
||
//
|
||
// Formats used by printf.
|
||
//
|
||
#define DUMP_FMT1 TEXT("%-7.7ws")
|
||
|
||
//
|
||
// DUMP_FMT2 is chosen so that the most common case (id numbers less than 100)
|
||
// looks good: two spaces for a number, three spaces for blanks.
|
||
// Larger numbers just like in LM21 will result to shifted display.
|
||
//
|
||
#define DUMP_FMT2 TEXT("%2d ")
|
||
#define MAX_TIME_FIELD_LENGTH 14
|
||
#define DUMP_FMT3 TEXT("%ws") // for printing JobTime
|
||
|
||
#define NULLC L'\0'
|
||
#define BLANK L' '
|
||
#define SLASH L'/'
|
||
#define BACKSLASH L'\\'
|
||
#define ELLIPSIS L"..."
|
||
|
||
#define QUESTION_SW L"/?"
|
||
#define QUESTION_SW_TOO L"-?"
|
||
#define SCHED_TOK_DELIM L"," // string of valid delimiters for days & dates
|
||
#define ARG_SEP_CHR L':'
|
||
|
||
typedef struct _SEARCH_LIST {
|
||
WCHAR * String;
|
||
DWORD MessageId;
|
||
DWORD Value;
|
||
} SEARCH_LIST, *PSEARCH_LIST, *LPSEARCH_LIST;
|
||
|
||
//
|
||
// All of the values below must be bitmasks. MatchString() depends on that!
|
||
//
|
||
#define AT_YES_VALUE 0x0001
|
||
#define AT_DELETE_VALUE 0x0002
|
||
#define AT_EVERY_VALUE 0x0004
|
||
#define AT_NEXT_VALUE 0x0008
|
||
#define AT_NO_VALUE 0x0010
|
||
#define AT_CONFIRM_YES_VALUE 0x0020
|
||
#define AT_CONFIRM_NO_VALUE 0x0040
|
||
#define AT_INTERACTIVE 0x0080
|
||
|
||
SEARCH_LIST GlobalListTable[] = {
|
||
{ NULL, IDS_YES, AT_YES_VALUE},
|
||
{ NULL, IDS_DELETE, AT_DELETE_VALUE},
|
||
{ NULL, IDS_EVERY, AT_EVERY_VALUE},
|
||
{ NULL, IDS_NEXT, AT_NEXT_VALUE},
|
||
{ NULL, IDS_NO, AT_NO_VALUE},
|
||
{ NULL, APE2_GEN_YES, AT_CONFIRM_YES_VALUE},
|
||
{ NULL, APE2_GEN_NO, AT_CONFIRM_NO_VALUE},
|
||
{ NULL, IDS_INTERACTIVE, AT_INTERACTIVE},
|
||
{ NULL, 0, 0 }
|
||
};
|
||
|
||
SEARCH_LIST DaysOfWeekSearchList[] = {
|
||
{ NULL, APE2_GEN_MONDAY_ABBREV, 0},
|
||
{ NULL, APE2_GEN_TUESDAY_ABBREV, 1},
|
||
{ NULL, APE2_GEN_WEDNSDAY_ABBREV, 2},
|
||
{ NULL, APE2_GEN_THURSDAY_ABBREV, 3},
|
||
{ NULL, APE2_GEN_FRIDAY_ABBREV, 4},
|
||
{ NULL, APE2_GEN_SATURDAY_ABBREV, 5},
|
||
{ NULL, APE2_TIME_SATURDAY_ABBREV2, 5},
|
||
{ NULL, APE2_GEN_SUNDAY_ABBREV, 6},
|
||
{ NULL, APE2_GEN_MONDAY, 0},
|
||
{ NULL, APE2_GEN_TUESDAY, 1},
|
||
{ NULL, APE2_GEN_WEDNSDAY, 2},
|
||
{ NULL, APE2_GEN_THURSDAY, 3},
|
||
{ NULL, APE2_GEN_FRIDAY, 4},
|
||
{ NULL, APE2_GEN_SATURDAY, 5},
|
||
{ NULL, APE2_GEN_SUNDAY, 6},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_MONDAY_ABBREV, 0},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_TUESDAY_ABBREV, 1},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY_ABBREV, 2},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_THURSDAY_ABBREV, 3},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_FRIDAY_ABBREV, 4},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV, 5},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_SATURDAY_ABBREV2, 5},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_SUNDAY_ABBREV, 6},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_MONDAY, 0},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_TUESDAY, 1},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_WEDNSDAY, 2},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_THURSDAY, 3},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_FRIDAY, 4},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_SATURDAY, 5},
|
||
{ NULL, APE2_GEN_NONLOCALIZED_SUNDAY, 6},
|
||
{ NULL, 0, 0 }
|
||
};
|
||
|
||
BOOL
|
||
AreYouSure(
|
||
VOID
|
||
);
|
||
BOOL
|
||
ArgIsServerName(
|
||
WCHAR * string
|
||
);
|
||
BOOL
|
||
ArgIsTime(
|
||
IN WCHAR * timestr,
|
||
OUT DWORD_PTR *pJobTime
|
||
);
|
||
BOOL
|
||
ArgIsDecimalString(
|
||
IN WCHAR * pDecimalString,
|
||
OUT PDWORD pNumber
|
||
);
|
||
DWORD
|
||
ConsolePrint(
|
||
IN LPWSTR pch,
|
||
IN int cch
|
||
);
|
||
int
|
||
FileIsConsole(
|
||
int fh
|
||
);
|
||
BOOL
|
||
IsDayOfMonth(
|
||
IN WCHAR * pToken,
|
||
OUT PDWORD pDay
|
||
);
|
||
BOOL
|
||
IsDayOfWeek(
|
||
IN WCHAR * pToken,
|
||
OUT PDWORD pDay
|
||
);
|
||
NET_API_STATUS
|
||
JobAdd(
|
||
VOID
|
||
);
|
||
NET_API_STATUS
|
||
JobEnum(
|
||
VOID
|
||
);
|
||
NET_API_STATUS
|
||
JobGetInfo(
|
||
VOID
|
||
);
|
||
DWORD
|
||
MatchString(
|
||
WCHAR * name,
|
||
DWORD mask
|
||
);
|
||
DWORD
|
||
MessageGet(
|
||
IN DWORD MessageId,
|
||
IN LPWSTR *buffer,
|
||
IN DWORD Size
|
||
);
|
||
DWORD
|
||
MessagePrint(
|
||
IN DWORD MessageId,
|
||
...
|
||
);
|
||
BOOL
|
||
ParseJobIdArgs(
|
||
WCHAR ** argv,
|
||
int argc,
|
||
int argno,
|
||
PBOOL pDeleteFound
|
||
);
|
||
BOOL
|
||
ParseTimeArgs(
|
||
WCHAR ** argv,
|
||
int argc,
|
||
int argno,
|
||
int * pargno
|
||
);
|
||
VOID
|
||
PrintDay(
|
||
int type,
|
||
DWORD DaysOfMonth,
|
||
UCHAR DaysOfWeek,
|
||
UCHAR Flags
|
||
);
|
||
VOID
|
||
PrintLine(
|
||
VOID
|
||
);
|
||
VOID
|
||
PrintTime(
|
||
DWORD_PTR JobTime
|
||
);
|
||
BOOL
|
||
TraverseSearchList(
|
||
PWCHAR String,
|
||
PSEARCH_LIST SearchList,
|
||
PDWORD pValue
|
||
);
|
||
VOID
|
||
Usage(
|
||
BOOL GoodCommand
|
||
);
|
||
BOOL
|
||
ValidateCommand(
|
||
IN int argc,
|
||
IN WCHAR ** argv,
|
||
OUT int * pCommand
|
||
);
|
||
|
||
VOID
|
||
GetTimeString(
|
||
DWORD_PTR Time,
|
||
WCHAR *Buffer,
|
||
int BufferLength
|
||
);
|
||
|
||
BOOL
|
||
InitList(
|
||
PSEARCH_LIST SearchList
|
||
);
|
||
|
||
VOID
|
||
TermList(
|
||
PSEARCH_LIST SearchList
|
||
);
|
||
|
||
DWORD
|
||
GetStringColumn(
|
||
WCHAR *
|
||
);
|
||
|
||
AT_INFO GlobalAtInfo; // buffer for scheduling new jobs
|
||
WCHAR GlobalAtInfoCommand[ MAX_COMMAND_LEN + 1];
|
||
|
||
DWORD GlobalJobId; // id of job in question
|
||
PWSTR GlobalServerName;
|
||
HANDLE GlobalMessageHandle;
|
||
BOOL GlobalYes;
|
||
BOOL GlobalDeleteAll;
|
||
BOOL GlobalErrorReported;
|
||
BOOL bDBCS;
|
||
|
||
CHAR ** GlobalCharArgv; // keeps original input
|
||
|
||
NET_TIME_FORMAT GlobalTimeFormat = {0};
|
||
|
||
// In OS/2 it used to be OK to call "exit()" with a negative number. In
|
||
// NT however, "exit()" should be called with a positive number only (a
|
||
// valid windows error code?!). Note that OS/2 AT command used to call
|
||
// exit(+1) for bad user input, and exit(-1) where -1 would get mapped to
|
||
// 255 for other errors. To keep things simple and to avoid calling exit()
|
||
// with a negative number, NT AT command calls exit(+1) for all possible
|
||
// errors.
|
||
|
||
#define AT_GENERIC_ERROR 1
|
||
|
||
|
||
VOID __cdecl
|
||
main(
|
||
int argc,
|
||
CHAR ** charArgv
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Main module. Note that strings (for now) arrive as asciiz even
|
||
if you compile app for UNICODE.
|
||
|
||
Arguments:
|
||
|
||
argc - argument count
|
||
charArgv - array of ascii strings
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS status = NERR_Success;
|
||
int command; // what to do
|
||
WCHAR ** argv;
|
||
DWORD cp;
|
||
CPINFO CurrentCPInfo;
|
||
|
||
GlobalYes = FALSE;
|
||
GlobalDeleteAll = FALSE;
|
||
GlobalErrorReported = FALSE;
|
||
GlobalCharArgv = charArgv;
|
||
|
||
/*
|
||
Added for bilingual message support. This is needed for FormatMessage
|
||
to work correctly. (Called from DosGetMessage).
|
||
Get current CodePage Info. We need this to decide whether
|
||
or not to use half-width characters.
|
||
*/
|
||
|
||
|
||
GetCPInfo(cp=GetConsoleOutputCP(), &CurrentCPInfo);
|
||
switch ( cp ) {
|
||
case 932:
|
||
case 936:
|
||
case 949:
|
||
case 950:
|
||
SetThreadLocale(
|
||
MAKELCID(
|
||
MAKELANGID(
|
||
PRIMARYLANGID(GetSystemDefaultLangID()),
|
||
SUBLANG_ENGLISH_US ),
|
||
SORT_DEFAULT
|
||
)
|
||
);
|
||
bDBCS = TRUE;
|
||
break;
|
||
|
||
default:
|
||
SetThreadLocale(
|
||
MAKELCID(
|
||
MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ),
|
||
SORT_DEFAULT
|
||
)
|
||
);
|
||
bDBCS = FALSE;
|
||
break;
|
||
}
|
||
|
||
GlobalMessageHandle = LoadLibrary( L"netmsg.dll");
|
||
if ( GlobalMessageHandle == NULL) {
|
||
MessagePrint( IDS_LOAD_LIBRARY_FAILURE, GetLastError());
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
if ( ( argv = CommandLineToArgvW( GetCommandLineW(), &argc)) == NULL) {
|
||
MessagePrint( IDS_UNABLE_TO_MAP_TO_UNICODE );
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
if ( ValidateCommand( argc, argv, &command) == FALSE) {
|
||
Usage( FALSE);
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
switch( command) {
|
||
|
||
case DUMP_ALL:
|
||
status = JobEnum();
|
||
break;
|
||
|
||
case DUMP_ID:
|
||
status = JobGetInfo();
|
||
break;
|
||
|
||
case ADD_TO_SCHEDULE:
|
||
status = JobAdd();
|
||
break;
|
||
|
||
case DEL_ALL:
|
||
if ( AreYouSure() == FALSE) {
|
||
break;
|
||
}
|
||
status = NetScheduleJobDel(
|
||
GlobalServerName,
|
||
0,
|
||
(DWORD)-1
|
||
);
|
||
if ( status == NERR_Success || status == APE_AT_ID_NOT_FOUND) {
|
||
break;
|
||
}
|
||
MessagePrint( status );
|
||
break;
|
||
|
||
case DEL_ID:
|
||
status = NetScheduleJobDel(
|
||
GlobalServerName,
|
||
GlobalJobId,
|
||
GlobalJobId
|
||
);
|
||
if ( status == NERR_Success) {
|
||
break;
|
||
}
|
||
MessagePrint( status );
|
||
break;
|
||
|
||
case ACTION_USAGE:
|
||
Usage( TRUE);
|
||
status = NERR_Success;
|
||
break;
|
||
}
|
||
|
||
TermList( GlobalListTable);
|
||
TermList( DaysOfWeekSearchList);
|
||
LocalFree( GlobalTimeFormat.AMString );
|
||
LocalFree( GlobalTimeFormat.PMString );
|
||
LocalFree( GlobalTimeFormat.DateFormat );
|
||
LocalFree( GlobalTimeFormat.TimeSeparator );
|
||
exit( status == NERR_Success ? ERROR_SUCCESS : AT_GENERIC_ERROR);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
AreYouSure(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Make sure user really wants to delete all jobs.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE if user really wants to go ahead.
|
||
FALSE otherwise.
|
||
|
||
--*/
|
||
{
|
||
register int retries = 0;
|
||
WCHAR rbuf[ 16];
|
||
WCHAR * smallBuffer = NULL;
|
||
DWORD Value;
|
||
int cch;
|
||
int retc;
|
||
|
||
if ( GlobalYes == TRUE) {
|
||
return( TRUE);
|
||
}
|
||
|
||
if ( MessagePrint( APE2_AT_DEL_WARNING ) == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
for ( ; ;) {
|
||
|
||
if ( MessageGet(
|
||
APE2_GEN_DEFAULT_NO, // MessageId
|
||
&smallBuffer, // lpBuffer
|
||
0
|
||
) == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
if ( MessagePrint( APE_OkToProceed, smallBuffer) == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
|
||
LocalFree( smallBuffer );
|
||
|
||
if (FileIsConsole(STD_INPUT_HANDLE)) {
|
||
retc = ReadConsole(GetStdHandle(STD_INPUT_HANDLE),rbuf,16,&cch,0);
|
||
if (retc) {
|
||
//
|
||
// Get rid of cr/lf
|
||
//
|
||
if (wcschr(rbuf, TEXT('\r')) == NULL) {
|
||
if (wcschr(rbuf, TEXT('\n')))
|
||
*wcschr(rbuf, TEXT('\n')) = NULLC;
|
||
}
|
||
else
|
||
*wcschr(rbuf, TEXT('\r')) = NULLC;
|
||
}
|
||
}
|
||
else {
|
||
CHAR oemBuf[ 16 ];
|
||
|
||
retc = (fgets(oemBuf, 16, stdin) != 0);
|
||
#if DBG
|
||
fprintf(stderr, "got >%s<\n", oemBuf);
|
||
#endif
|
||
cch = 0;
|
||
if (retc) {
|
||
if (strchr(oemBuf, '\n')) {
|
||
*strchr(oemBuf, '\n') = '\0';
|
||
}
|
||
cch = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED,
|
||
oemBuf, strlen(oemBuf)+1, rbuf, 16);
|
||
}
|
||
}
|
||
|
||
#if DBG
|
||
fprintf(stderr, "cch = %d, retc = %d\n", cch, retc);
|
||
#endif
|
||
if (!retc || cch == 0)
|
||
return( FALSE);
|
||
#if DBG
|
||
fprintf(stderr, "converted to >%ws<\n", rbuf);
|
||
#endif
|
||
|
||
Value = MatchString(_wcsupr(rbuf), AT_CONFIRM_NO_VALUE | AT_CONFIRM_YES_VALUE);
|
||
|
||
if ( Value == AT_CONFIRM_NO_VALUE) {
|
||
return( FALSE);
|
||
} else if ( Value == AT_CONFIRM_YES_VALUE) {
|
||
break;
|
||
}
|
||
|
||
if ( ++retries >= 3) {
|
||
MessagePrint( APE_NoGoodResponse );
|
||
return( FALSE);
|
||
}
|
||
|
||
if ( MessagePrint( APE_UtilInvalidResponse ) == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
}
|
||
return( TRUE);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
ArgIsServerName(
|
||
WCHAR * string
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Checks if string is a server name. Validation is really primitive, eg
|
||
strings like "\\\threeslashes" pass the test.
|
||
|
||
Arguments:
|
||
|
||
string - pointer to string that may represent a server name
|
||
|
||
Return Value:
|
||
|
||
TRUE - string is (or might be) a valid server name
|
||
FALSE - string is not a valid server name
|
||
|
||
--*/
|
||
{
|
||
|
||
NET_API_STATUS ApiStatus;
|
||
|
||
if (string[0] == BACKSLASH && string[1] == BACKSLASH && string[2] != 0) {
|
||
ApiStatus = NetpNameValidate(
|
||
NULL, // no server name.
|
||
&string[2], // name to validate
|
||
NAMETYPE_COMPUTER,
|
||
LM2X_COMPATIBLE); // flags
|
||
if (ApiStatus != NO_ERROR) {
|
||
return (FALSE);
|
||
}
|
||
GlobalServerName = string;
|
||
return( TRUE);
|
||
}
|
||
|
||
return( FALSE); // GlobalServerName is NULL at load time
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
ArgIsTime(
|
||
IN WCHAR * timestr,
|
||
OUT DWORD_PTR *pJobTime
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determines whether string is a time or not. Validates that string
|
||
passed into it is in the form of HH:MM. It searches the string for
|
||
a ":" and then validates that the preceeding data is numeric & in a
|
||
valid range for hours. It then validates the string after the ":"
|
||
is numeric & in a validate range for minutes. If all the tests are
|
||
passed the TRUE is returned.
|
||
|
||
Arguments:
|
||
|
||
timestr - string to check whether it is a time
|
||
JobTime - ptr to number of miliseconds
|
||
|
||
Return Value:
|
||
|
||
TRUE - timestr was a time in HH:MM format
|
||
FALSE - timestr wasn't at time
|
||
|
||
--*/
|
||
{
|
||
CHAR buffer[MAX_TIME_SIZE];
|
||
USHORT ParseLen;
|
||
BOOL fDummy;
|
||
|
||
if ( timestr == NULL )
|
||
return FALSE;
|
||
|
||
if ( !WideCharToMultiByte( CP_ACP,
|
||
0,
|
||
timestr,
|
||
-1,
|
||
buffer,
|
||
sizeof( buffer )/sizeof(CHAR),
|
||
NULL,
|
||
&fDummy ))
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
if ( LUI_ParseTimeSinceStartOfDay( buffer, pJobTime, &ParseLen, 0) )
|
||
return FALSE;
|
||
|
||
// LUI_ParseTimeSinceStartOfDay returns the time in seconds.
|
||
// Hence, we need to convert it to microseconds.
|
||
*pJobTime *= 1000;
|
||
|
||
return( TRUE);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
ArgIsDecimalString(
|
||
IN WCHAR * pDecimalString,
|
||
OUT PDWORD pNumber
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine converts a string into a DWORD if it possibly can.
|
||
The conversion is successful if string is decimal numeric and
|
||
does not lead to an overflow.
|
||
|
||
Arguments:
|
||
|
||
pDecimalString ptr to decimal string
|
||
pNumber ptr to number
|
||
|
||
Return Value:
|
||
|
||
FALSE invalid number
|
||
TRUE valid number
|
||
|
||
--*/
|
||
{
|
||
DWORD Value;
|
||
DWORD OldValue;
|
||
DWORD digit;
|
||
|
||
if ( pDecimalString == NULL || *pDecimalString == 0) {
|
||
return( FALSE);
|
||
}
|
||
|
||
Value = 0;
|
||
|
||
while ( (digit = *pDecimalString++) != 0) {
|
||
|
||
if ( digit < L'0' || digit > L'9') {
|
||
return( FALSE); // not a decimal string
|
||
}
|
||
|
||
OldValue = Value;
|
||
Value = digit - L'0' + 10 * Value;
|
||
if ( Value < OldValue) {
|
||
return( FALSE); // overflow
|
||
}
|
||
}
|
||
|
||
*pNumber = Value;
|
||
return( TRUE);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
IsDayOfMonth(
|
||
IN WCHAR * pToken,
|
||
OUT PDWORD pDay
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Converts a string into a number for the day of the month, if it can
|
||
possibly do so. Note that "first" == 1, ...
|
||
|
||
Arguments:
|
||
|
||
pToken pointer to schedule token for the day of the month
|
||
pDay pointer to index of day in a month
|
||
|
||
Return Value:
|
||
|
||
TRUE if a valid schedule token
|
||
FALSE otherwise
|
||
|
||
--*/
|
||
{
|
||
return ( ArgIsDecimalString( pToken, pDay) == TRUE && *pDay >= 1
|
||
&& *pDay <= 31);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
IsDayOfWeek(
|
||
WCHAR * pToken,
|
||
PDWORD pDay
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine converts a string day of the week into a integer
|
||
offset into the week if it possibly can. Note that Monday==0,
|
||
..., Sunday == 6.
|
||
|
||
Arguments:
|
||
|
||
pToken pointer to schedule token for the day of a week
|
||
pDay pointer to index of day in a month
|
||
|
||
Return Value:
|
||
|
||
TRUE if a valid schedule token
|
||
FALSE otherwise
|
||
|
||
--*/
|
||
{
|
||
if ( !InitList( DaysOfWeekSearchList ) )
|
||
{
|
||
// Error already reported
|
||
exit( -1 );
|
||
}
|
||
|
||
return( TraverseSearchList(
|
||
pToken,
|
||
DaysOfWeekSearchList,
|
||
pDay
|
||
));
|
||
}
|
||
|
||
|
||
|
||
NET_API_STATUS
|
||
JobAdd(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Adds a new item to schedule.
|
||
|
||
Arguments:
|
||
|
||
None. Uses globals.
|
||
|
||
Return Value:
|
||
|
||
NET_API_STATUS return value of remote api call
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS status;
|
||
|
||
for ( ; ; ) {
|
||
status = NetScheduleJobAdd(
|
||
GlobalServerName,
|
||
(LPBYTE)&GlobalAtInfo,
|
||
&GlobalJobId
|
||
);
|
||
if ( status == ERROR_INVALID_PARAMETER &&
|
||
GlobalAtInfo.Flags & JOB_NONINTERACTIVE) {
|
||
//
|
||
// We may have failed because we are talking to a down level
|
||
// server that does not know about JOB_NONINTERACTIVE bit.
|
||
// Clear the bit, and try again.
|
||
// A better approach would be to check the version of the
|
||
// server before making NetScheduleJobAdd() call, adjust the
|
||
// bit appropriately and only then call NetScheduleJobAdd().
|
||
//
|
||
GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE;
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
}
|
||
if ( status == NERR_Success) {
|
||
MessagePrint( IDS_ADD_NEW_JOB, GlobalJobId );
|
||
} else {
|
||
if ( MessagePrint( status ) == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
}
|
||
|
||
return( status);
|
||
}
|
||
|
||
|
||
|
||
NET_API_STATUS
|
||
JobEnum(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This does all of the processing necessary to dump out the entire
|
||
schedule file. It loops through on each record and formats its
|
||
information for printing and then goes to the next.
|
||
|
||
Arguments:
|
||
|
||
None. Uses globals.
|
||
|
||
Return Value:
|
||
|
||
ERROR_SUCCESS if everything enumerated OK
|
||
error returned by remote api otherwise
|
||
|
||
--*/
|
||
{
|
||
BOOL first = TRUE;
|
||
DWORD ResumeJobId = 0;
|
||
NET_API_STATUS status = NERR_Success;
|
||
PAT_ENUM pAtEnum;
|
||
DWORD EntriesRead;
|
||
DWORD TotalEntries;
|
||
LPVOID EnumBuffer;
|
||
DWORD length;
|
||
WCHAR * smallBuffer = NULL;
|
||
|
||
for ( ; ;) {
|
||
|
||
status = NetScheduleJobEnum(
|
||
GlobalServerName,
|
||
(LPBYTE *)&EnumBuffer,
|
||
(DWORD)-1,
|
||
&EntriesRead,
|
||
&TotalEntries,
|
||
&ResumeJobId
|
||
);
|
||
|
||
if ( status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
|
||
length = MessagePrint( status );
|
||
if ( length == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
return( status);
|
||
}
|
||
|
||
ASSERT( status == ERROR_SUCCESS ? TotalEntries == EntriesRead
|
||
: TotalEntries > EntriesRead);
|
||
|
||
if ( TotalEntries == 0) {
|
||
break; // no items found
|
||
}
|
||
|
||
if ( first == TRUE) {
|
||
length = MessagePrint( APE2_AT_DUMP_HEADER );
|
||
if ( length == 0) {
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
PrintLine(); // line across screen
|
||
first = FALSE;
|
||
}
|
||
|
||
for ( pAtEnum = EnumBuffer; EntriesRead-- > 0; pAtEnum++) {
|
||
if ( pAtEnum->Flags & JOB_EXEC_ERROR) {
|
||
if ( MessageGet( APE2_GEN_ERROR, &smallBuffer, 0 ) == 0) {
|
||
// error reported already
|
||
exit( AT_GENERIC_ERROR);
|
||
}
|
||
GenOutputArg( DUMP_FMT1, smallBuffer );
|
||
LocalFree( smallBuffer );
|
||
} else {
|
||
GenOutputArg( DUMP_FMT1, L"");
|
||
}
|
||
GenOutputArg( DUMP_FMT2, pAtEnum->JobId);
|
||
PrintDay( DUMP_ALL, pAtEnum->DaysOfMonth, pAtEnum->DaysOfWeek,
|
||
pAtEnum->Flags);
|
||
PrintTime( pAtEnum->JobTime);
|
||
GenOutputArg( TEXT("%ws\n"), pAtEnum->Command);
|
||
}
|
||
|
||
if ( EnumBuffer != NULL) {
|
||
(VOID)NetApiBufferFree( (LPVOID)EnumBuffer);
|
||
EnumBuffer = NULL;
|
||
}
|
||
|
||
if ( status == ERROR_SUCCESS) {
|
||
break; // we have read & displayed all the items
|
||
}
|
||
}
|
||
|
||
if ( first == TRUE) {
|
||
MessagePrint( APE_EmptyList );
|
||
}
|
||
|
||
return( ERROR_SUCCESS);
|
||
}
|
||
|
||
|
||
|
||
NET_API_STATUS
|
||
JobGetInfo(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This prints out the schedule of an individual items schedule.
|
||
|
||
Arguments:
|
||
|
||
None. Uses globals.
|
||
|
||
Return Value:
|
||
|
||
NET_API_STATUS value returned by remote api
|
||
|
||
--*/
|
||
|
||
{
|
||
PAT_INFO pAtInfo = NULL;
|
||
NET_API_STATUS status;
|
||
|
||
status = NetScheduleJobGetInfo(
|
||
GlobalServerName,
|
||
GlobalJobId,
|
||
(LPBYTE *)&pAtInfo
|
||
);
|
||
if ( status != NERR_Success) {
|
||
MessagePrint( status );
|
||
return( status);
|
||
}
|
||
|
||
PutNewLine();
|
||
MessagePrint( APE2_AT_DI_TASK );
|
||
GenOutputArg( TEXT("%d"), GlobalJobId);
|
||
PutNewLine();
|
||
|
||
MessagePrint( APE2_AT_DI_STATUS );
|
||
MessagePrint( (pAtInfo->Flags & JOB_EXEC_ERROR) != 0 ?
|
||
APE2_GEN_ERROR : APE2_GEN_OK );
|
||
PutNewLine();
|
||
|
||
MessagePrint( APE2_AT_DI_SCHEDULE );
|
||
PrintDay( DUMP_ID, pAtInfo->DaysOfMonth, pAtInfo->DaysOfWeek,
|
||
pAtInfo->Flags);
|
||
PutNewLine();
|
||
|
||
MessagePrint( APE2_AT_DI_TIMEOFDAY );
|
||
PrintTime( pAtInfo->JobTime);
|
||
PutNewLine();
|
||
|
||
MessagePrint( APE2_AT_DI_INTERACTIVE);
|
||
MessagePrint( (pAtInfo->Flags & JOB_NONINTERACTIVE) == 0 ?
|
||
APE2_GEN_YES : APE2_GEN_NO );
|
||
PutNewLine();
|
||
|
||
MessagePrint( APE2_AT_DI_COMMAND );
|
||
GenOutputArg( TEXT("%ws\n"), pAtInfo->Command);
|
||
PutNewLine2();
|
||
|
||
(VOID)NetApiBufferFree( (LPVOID)pAtInfo);
|
||
|
||
return( NERR_Success);
|
||
}
|
||
|
||
|
||
|
||
DWORD
|
||
MatchString(
|
||
WCHAR * name,
|
||
DWORD Values
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
|
||
Parses switch string and returns NULL for an invalid switch,
|
||
and -1 for an ambiguous switch.
|
||
|
||
Arguments:
|
||
|
||
name - pointer to string we need to examine
|
||
Values - bitmask of values of interest
|
||
|
||
Return Value:
|
||
|
||
Pointer to command, or NULL or -1.
|
||
--*/
|
||
{
|
||
WCHAR * String;
|
||
PSEARCH_LIST pCurrentList;
|
||
WCHAR * CurrentString;
|
||
DWORD FoundValue;
|
||
int nmatches;
|
||
int longest;
|
||
|
||
if ( !InitList( GlobalListTable ) )
|
||
{
|
||
// Error already reported
|
||
exit( -1 );
|
||
}
|
||
|
||
for ( pCurrentList = GlobalListTable,
|
||
longest = nmatches = 0,
|
||
FoundValue = 0;
|
||
(CurrentString = pCurrentList->String) != NULL;
|
||
pCurrentList++) {
|
||
|
||
if ( (Values & pCurrentList->Value) == 0) {
|
||
continue; // skip this List
|
||
}
|
||
|
||
for ( String = name; *String == *CurrentString++; String++) {
|
||
if ( *String == 0) {
|
||
return( pCurrentList->Value); // exact match
|
||
}
|
||
}
|
||
|
||
if ( !*String) {
|
||
|
||
if ( String - name > longest) {
|
||
|
||
longest = (int)(String - name);
|
||
nmatches = 1;
|
||
FoundValue = pCurrentList->Value;
|
||
|
||
} else if ( String - name == longest) {
|
||
|
||
nmatches++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 0 corresponds to no match at all (invalid List)
|
||
// while -1 corresponds to multiple match (ambiguous List).
|
||
|
||
if ( nmatches != 1) {
|
||
return ( (nmatches == 0) ? 0 : -1);
|
||
}
|
||
|
||
return( FoundValue);
|
||
}
|
||
|
||
|
||
DWORD
|
||
MessageGet(
|
||
IN DWORD MessageId,
|
||
OUT LPWSTR *buffer,
|
||
IN DWORD Size
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Fills in the unicode message corresponding to a given message id,
|
||
provided that a message can be found and that it fits in a supplied
|
||
buffer.
|
||
|
||
Arguments:
|
||
|
||
MessageId - message id
|
||
buffer - pointer to caller supplied buffer
|
||
Size - size (always in bytes) of supplied buffer,
|
||
If size is 0, buffer will be allocated by FormatMessage.
|
||
|
||
Return Value:
|
||
|
||
Count of characters, not counting the terminating null character,
|
||
returned in the buffer. Zero return value indicates failure.
|
||
|
||
--*/
|
||
{
|
||
DWORD length;
|
||
LPVOID lpSource;
|
||
DWORD dwFlags;
|
||
|
||
if ( MessageId < NERR_BASE) {
|
||
//
|
||
// Get message from system.
|
||
//
|
||
lpSource = NULL; // redundant step according to FormatMessage() spec
|
||
dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
|
||
|
||
} else if ( ( MessageId >= APE2_AT_DEL_WARNING
|
||
&& MessageId <= APE2_AT_DI_INTERACTIVE)
|
||
|| ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
|
||
&& MessageId <= IDS_INTERACTIVE )) {
|
||
//
|
||
// Get message from this module.
|
||
//
|
||
lpSource = NULL;
|
||
dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
|
||
|
||
} else {
|
||
//
|
||
// Get message from netmsg.dll.
|
||
//
|
||
lpSource = GlobalMessageHandle;
|
||
dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
|
||
}
|
||
|
||
if ( Size == 0 )
|
||
dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
|
||
|
||
length = FormatMessage(
|
||
dwFlags, // dwFlags
|
||
lpSource, // lpSource
|
||
MessageId, // MessageId
|
||
0, // dwLanguageId
|
||
(LPWSTR) buffer, // lpBuffer
|
||
Size, // nSize
|
||
NULL // lpArguments
|
||
);
|
||
|
||
if ( length == 0) {
|
||
MessagePrint( IDS_MESSAGE_GET_ERROR, MessageId, GetLastError());
|
||
}
|
||
return( length);
|
||
|
||
} // MessageGet()
|
||
|
||
|
||
|
||
int
|
||
FileIsConsole(
|
||
int fh
|
||
)
|
||
{
|
||
unsigned htype ;
|
||
|
||
htype = GetFileType(GetStdHandle(fh));
|
||
htype &= ~FILE_TYPE_REMOTE;
|
||
return htype == FILE_TYPE_CHAR;
|
||
}
|
||
|
||
|
||
|
||
DWORD
|
||
ConsolePrint(
|
||
LPWSTR pch,
|
||
int cch
|
||
)
|
||
{
|
||
int cchOut;
|
||
int err;
|
||
CHAR *pchOemBuffer;
|
||
|
||
if (FileIsConsole(STD_OUTPUT_HANDLE)) {
|
||
err = WriteConsole(
|
||
GetStdHandle(STD_OUTPUT_HANDLE),
|
||
pch, cch,
|
||
&cchOut, NULL);
|
||
if (!err || cchOut != cch)
|
||
goto try_again;
|
||
}
|
||
else if ( cch != 0) {
|
||
try_again:
|
||
cchOut = WideCharToMultiByte(CP_OEMCP, 0, pch, cch, NULL, 0, NULL,NULL);
|
||
if (cchOut == 0)
|
||
return 0;
|
||
|
||
if ((pchOemBuffer = (CHAR *)malloc(cchOut)) != NULL) {
|
||
WideCharToMultiByte(CP_OEMCP, 0, pch, cch,
|
||
pchOemBuffer, cchOut, NULL, NULL);
|
||
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
|
||
pchOemBuffer, cchOut, &cch, NULL);
|
||
free(pchOemBuffer);
|
||
}
|
||
}
|
||
return cchOut;
|
||
}
|
||
|
||
|
||
|
||
DWORD
|
||
MessagePrint(
|
||
IN DWORD MessageId,
|
||
...
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Finds the unicode message corresponding to the supplied message id,
|
||
merges it with caller supplied string(s), and prints the resulting
|
||
string.
|
||
|
||
Arguments:
|
||
|
||
MessageId - message id
|
||
|
||
Return Value:
|
||
|
||
Count of characters, not counting the terminating null character,
|
||
printed by this routine. Zero return value indicates failure.
|
||
|
||
--*/
|
||
{
|
||
va_list arglist;
|
||
WCHAR * buffer = NULL;
|
||
DWORD length;
|
||
LPVOID lpSource;
|
||
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER;
|
||
|
||
|
||
va_start( arglist, MessageId );
|
||
|
||
if ( MessageId < NERR_BASE) {
|
||
//
|
||
// Get message from system.
|
||
//
|
||
lpSource = NULL; // redundant step according to FormatMessage() spec
|
||
dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
|
||
|
||
} else if ( ( MessageId >= APE2_AT_DEL_WARNING
|
||
&& MessageId <= APE2_AT_DI_INTERACTIVE)
|
||
|| ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
|
||
&& MessageId <= IDS_INTERACTIVE )) {
|
||
//
|
||
// Get message from this module.
|
||
//
|
||
lpSource = NULL;
|
||
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
|
||
|
||
} else {
|
||
//
|
||
// Get message from netmsg.dll.
|
||
//
|
||
lpSource = GlobalMessageHandle;
|
||
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
|
||
}
|
||
|
||
length = FormatMessage(
|
||
dwFlags, // dwFlags
|
||
lpSource, // lpSource
|
||
MessageId, // MessageId
|
||
0L, // dwLanguageId
|
||
(LPTSTR)&buffer, // lpBuffer
|
||
0, // size
|
||
&arglist // lpArguments
|
||
);
|
||
|
||
length = ConsolePrint(buffer, length);
|
||
|
||
LocalFree(buffer);
|
||
|
||
return( length);
|
||
|
||
} // MessagePrint()
|
||
|
||
|
||
|
||
BOOL
|
||
ParseJobIdArgs(
|
||
WCHAR ** argv,
|
||
int argc,
|
||
int argno,
|
||
PBOOL pDeleteFound
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Parses arguments for commands containing JobId (these can be JobGetInfo
|
||
and JobDel commands). It loops through JobId arguments making sure that
|
||
we have at most one "yes-no" switch and at most one "delete" switch and
|
||
nothing else.
|
||
|
||
Arguments:
|
||
|
||
argv argument list
|
||
argc number of arguments to parse
|
||
argno index of argument to begin parsing from
|
||
pDeleteFound did we find a delete switch or not
|
||
|
||
Return Value:
|
||
|
||
FALSE invalid argument found
|
||
TRUE valid arguments
|
||
|
||
--*/
|
||
{
|
||
BOOL FoundDeleteSwitch;
|
||
|
||
for ( FoundDeleteSwitch = FALSE; argno < argc; argno++) {
|
||
|
||
WCHAR * argp;
|
||
DWORD length;
|
||
DWORD Value;
|
||
|
||
argp = argv[ argno];
|
||
|
||
if ( *argp++ != SLASH) {
|
||
return( FALSE); // not a switch
|
||
}
|
||
|
||
_wcsupr( argp);
|
||
length = wcslen( argp);
|
||
|
||
Value = MatchString( argp, AT_YES_VALUE | AT_DELETE_VALUE);
|
||
|
||
if ( Value == AT_YES_VALUE) {
|
||
|
||
if ( GlobalYes == TRUE) {
|
||
return( FALSE); // multiple instances of yes switch
|
||
}
|
||
|
||
GlobalYes = TRUE;
|
||
continue;
|
||
}
|
||
|
||
if ( Value == AT_DELETE_VALUE) {
|
||
|
||
if ( FoundDeleteSwitch == TRUE) {
|
||
return( FALSE); // duplicate delete switch
|
||
}
|
||
FoundDeleteSwitch = TRUE;
|
||
continue;
|
||
}
|
||
|
||
return( FALSE); // an unknown switch
|
||
}
|
||
|
||
*pDeleteFound = FoundDeleteSwitch;
|
||
return( TRUE);
|
||
} // ParseJobIdArgs()
|
||
|
||
|
||
|
||
BOOL
|
||
ParseTimeArgs(
|
||
WCHAR ** argv,
|
||
int argc,
|
||
int argno,
|
||
int * pargno
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Parses arguments for command addition.
|
||
|
||
Arguments:
|
||
|
||
argv argument list
|
||
argc count of args
|
||
argno index of the first arg to validate
|
||
pargno ptr to the index of the first non-switch arg
|
||
|
||
Return Value:
|
||
|
||
TRUE all arguments are valid
|
||
FALSE otherwise
|
||
|
||
--*/
|
||
{
|
||
DWORD day_no; // day number for scheduling
|
||
DWORD NextCount = 0; // count of next switches
|
||
DWORD EveryCount = 0; // count of every switches
|
||
WCHAR * argp; // ptr to arg string
|
||
WCHAR * schedp; // work ptr to arg string
|
||
DWORD Value; // bitmask
|
||
|
||
for ( NOTHING; argno < argc; argno++) {
|
||
|
||
argp = argv[ argno];
|
||
|
||
if ( *argp++ != SLASH) {
|
||
break; // found non-switch, we are done
|
||
}
|
||
|
||
|
||
schedp = wcschr( argp, ARG_SEP_CHR);
|
||
|
||
if ( schedp == NULL) {
|
||
return( FALSE);
|
||
}
|
||
|
||
_wcsupr( argp); // upper case entire input, not just the switch name
|
||
|
||
*schedp = 0;
|
||
|
||
Value = MatchString( argp, AT_NEXT_VALUE | AT_EVERY_VALUE);
|
||
|
||
if ( Value == AT_NEXT_VALUE) {
|
||
|
||
NextCount++;
|
||
|
||
} else if ( Value == AT_EVERY_VALUE) {
|
||
|
||
EveryCount++;
|
||
GlobalAtInfo.Flags |= JOB_RUN_PERIODICALLY;
|
||
|
||
} else {
|
||
|
||
return( FALSE); // an unexpected switch
|
||
}
|
||
|
||
if ( NextCount + EveryCount > 1) {
|
||
return( FALSE); // repeated switch option
|
||
}
|
||
|
||
*schedp++ = ARG_SEP_CHR;
|
||
|
||
schedp = wcstok( schedp, SCHED_TOK_DELIM);
|
||
|
||
if ( schedp == NULL) {
|
||
GlobalAtInfo.Flags |= JOB_ADD_CURRENT_DATE;
|
||
continue;
|
||
}
|
||
|
||
while( schedp != NULL) {
|
||
|
||
if ( IsDayOfMonth( schedp, &day_no) == TRUE) {
|
||
|
||
GlobalAtInfo.DaysOfMonth |= (1 << (day_no - 1));
|
||
|
||
} else if ( IsDayOfWeek( schedp, &day_no) == TRUE) {
|
||
|
||
GlobalAtInfo.DaysOfWeek |= (1 << day_no);
|
||
|
||
} else {
|
||
MessagePrint( APE_InvalidSwitchArg );
|
||
GlobalErrorReported = TRUE;
|
||
return( FALSE);
|
||
}
|
||
|
||
schedp = wcstok( NULL, SCHED_TOK_DELIM);
|
||
}
|
||
}
|
||
|
||
if ( argno == argc) {
|
||
return( FALSE); // all switches, no command
|
||
}
|
||
|
||
*pargno = argno;
|
||
return( TRUE);
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
ParseInteractiveArg(
|
||
IN OUT WCHAR * argp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns TRUE if argp is an interactive switch.
|
||
|
||
--*/
|
||
{
|
||
DWORD Value; // bitmask
|
||
|
||
if ( *argp++ != SLASH) {
|
||
return( FALSE); // not a switch
|
||
}
|
||
|
||
_wcsupr( argp); // all AT command switches can be safely uppercased
|
||
|
||
Value = MatchString( argp, AT_INTERACTIVE);
|
||
|
||
if ( Value == AT_INTERACTIVE) {
|
||
GlobalAtInfo.Flags &= ~JOB_NONINTERACTIVE; // clear noninteractive flag
|
||
return( TRUE);
|
||
}
|
||
|
||
return( FALSE); // some other switch
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
PrintDay(
|
||
int type,
|
||
DWORD DaysOfMonth,
|
||
UCHAR DaysOfWeek,
|
||
UCHAR Flags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Print out schedule days. This routine converts a schedule bit map
|
||
to the literals that represent the schedule.
|
||
|
||
Arguments:
|
||
|
||
type whether this is for JobEnum or not
|
||
DaysOfMonth bitmask for days of month
|
||
DaysOfWeek bitmaks for days of week
|
||
Flags extra info about the job
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
int i;
|
||
WCHAR Buffer[ 128];
|
||
DWORD BufferLength;
|
||
DWORD Length;
|
||
DWORD TotalLength = 0;
|
||
DWORD TotalColumnLength = 0;
|
||
WCHAR * LastSpace;
|
||
DWORD MessageId;
|
||
BOOL OverFlow = TRUE;
|
||
static int Ape2GenWeekdayLong[] = {
|
||
APE2_GEN_MONDAY,
|
||
APE2_GEN_TUESDAY,
|
||
APE2_GEN_WEDNSDAY,
|
||
APE2_GEN_THURSDAY,
|
||
APE2_GEN_FRIDAY,
|
||
APE2_GEN_SATURDAY,
|
||
APE2_GEN_SUNDAY
|
||
};
|
||
static int Ape2GenWeekdayAbbrev[] = {
|
||
APE2_GEN_MONDAY_ABBREV,
|
||
APE2_GEN_TUESDAY_ABBREV,
|
||
APE2_GEN_WEDNSDAY_ABBREV,
|
||
APE2_GEN_THURSDAY_ABBREV,
|
||
APE2_GEN_FRIDAY_ABBREV,
|
||
APE2_GEN_SATURDAY_ABBREV,
|
||
APE2_GEN_SUNDAY_ABBREV
|
||
};
|
||
|
||
//
|
||
// Subtract 4 to guard against days of week or days of month overflow.
|
||
//
|
||
BufferLength = sizeof( Buffer)/ sizeof( WCHAR) - 4;
|
||
if ( type == DUMP_ALL && BufferLength > MAX_SCHED_FIELD_LENGTH) {
|
||
BufferLength = MAX_SCHED_FIELD_LENGTH;
|
||
}
|
||
|
||
//
|
||
// First do the descriptive bit (eg. EACH, NEXT, etc) with the days.
|
||
//
|
||
|
||
if ( Flags & JOB_RUN_PERIODICALLY) {
|
||
|
||
MessageId = APE2_AT_EACH;
|
||
|
||
} else if ( (DaysOfWeek != 0) || (DaysOfMonth != 0)) {
|
||
|
||
MessageId = APE2_AT_NEXT;
|
||
|
||
} else if ( Flags & JOB_RUNS_TODAY) {
|
||
|
||
MessageId = APE2_AT_TODAY;
|
||
|
||
} else {
|
||
|
||
MessageId = APE2_AT_TOMORROW;
|
||
}
|
||
|
||
Length = MessageGet(
|
||
MessageId,
|
||
(LPWSTR *) &Buffer[TotalLength],
|
||
BufferLength
|
||
);
|
||
if ( Length == 0) {
|
||
goto PrintDay_exit; // Assume this is due to lack of space
|
||
}
|
||
TotalColumnLength = GetStringColumn( &Buffer[TotalLength] );
|
||
TotalLength = Length;
|
||
|
||
if ( DaysOfWeek != 0) {
|
||
|
||
for ( i = 0; i < 7; i++) {
|
||
|
||
if ( ( DaysOfWeek & (1 << i)) != 0) {
|
||
|
||
if( bDBCS ) {
|
||
Length = MessageGet(
|
||
Ape2GenWeekdayLong[ i],
|
||
(LPWSTR *) &Buffer[TotalLength],
|
||
BufferLength - TotalLength
|
||
);
|
||
} else {
|
||
Length = MessageGet(
|
||
Ape2GenWeekdayAbbrev[ i],
|
||
(LPWSTR *) &Buffer[TotalLength],
|
||
BufferLength - TotalLength
|
||
);
|
||
}
|
||
if ( Length == 0) {
|
||
//
|
||
// Not enough room for WeekDay symbol
|
||
//
|
||
goto PrintDay_exit;
|
||
|
||
}
|
||
//
|
||
// Get how many columns will be needed for display.
|
||
//
|
||
TotalColumnLength += GetStringColumn( &Buffer[TotalLength] );
|
||
|
||
if ( TotalColumnLength >= BufferLength) {
|
||
//
|
||
// Not enough room for space following WeekDay symbol
|
||
//
|
||
goto PrintDay_exit;
|
||
}
|
||
TotalLength +=Length;
|
||
Buffer[ TotalLength++] = BLANK;
|
||
TotalColumnLength++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( DaysOfMonth != 0) {
|
||
|
||
for ( i = 0; i < 31; i++) {
|
||
|
||
if ( ( DaysOfMonth & (1L << i)) != 0) {
|
||
|
||
Length = swprintf(
|
||
&Buffer[ TotalLength],
|
||
L"%d ",
|
||
i + 1
|
||
);
|
||
if ( TotalLength + Length > BufferLength) {
|
||
//
|
||
// Not enough room for MonthDay symbol followed by space
|
||
//
|
||
goto PrintDay_exit;
|
||
}
|
||
TotalLength +=Length;
|
||
TotalColumnLength +=Length;
|
||
}
|
||
}
|
||
}
|
||
|
||
OverFlow = FALSE;
|
||
|
||
PrintDay_exit:
|
||
|
||
Buffer[ TotalLength] = NULLC;
|
||
|
||
if ( OverFlow == TRUE) {
|
||
|
||
if ( TotalLength > 0 && Buffer[ TotalLength - 1] == BLANK) {
|
||
//
|
||
// Eliminate trailing space if there is one.
|
||
//
|
||
Buffer[ TotalLength - 1] = NULLC;
|
||
}
|
||
|
||
//
|
||
// Then get rid of the rightmost token (or even whole thing).
|
||
//
|
||
|
||
LastSpace = wcsrchr( Buffer, BLANK);
|
||
|
||
wcscpy( LastSpace != NULL ? LastSpace : Buffer, ELLIPSIS);
|
||
|
||
TotalLength = wcslen( Buffer);
|
||
|
||
}
|
||
|
||
if ( type == DUMP_ALL) {
|
||
while( TotalColumnLength++ < MAX_SCHED_FIELD_LENGTH) {
|
||
Buffer[ TotalLength++] = BLANK;
|
||
}
|
||
Buffer[ TotalLength] = UNICODE_NULL;
|
||
}
|
||
|
||
GenOutputArg( TEXT("%ws"), Buffer);
|
||
}
|
||
|
||
|
||
VOID
|
||
PrintLine(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Prints a line accross screen.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Note:
|
||
|
||
BUGBUG Is this treatment valid for UniCode? See also LUI_PrintLine()
|
||
BUGBUG in ui\common\src\lui\lui\border.c
|
||
|
||
--*/
|
||
#define SINGLE_HORIZONTAL L'\x02d'
|
||
#define SCREEN_WIDTH 79
|
||
{
|
||
WCHAR string[ SCREEN_WIDTH + 1];
|
||
DWORD offset;
|
||
|
||
|
||
for ( offset = 0; offset < SCREEN_WIDTH; offset++) {
|
||
string[ offset] = SINGLE_HORIZONTAL;
|
||
}
|
||
|
||
string[ SCREEN_WIDTH] = NULLC;
|
||
GenOutputArg(TEXT("%ws\n"), string);
|
||
}
|
||
|
||
|
||
VOID
|
||
PrintTime(
|
||
DWORD_PTR JobTime
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Prints time of a job in HH:MM{A,P}M format.
|
||
|
||
Arguments:
|
||
|
||
JobTime - time in miliseconds (measured from midnight)
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Note:
|
||
|
||
BUGBUG this does not make sure that JobTime is within the bounds.
|
||
BUGBUG Also, there is nothing unicode about printing this output.
|
||
|
||
--*/
|
||
{
|
||
WCHAR Buffer[15];
|
||
|
||
GetTimeString( JobTime, Buffer, sizeof( Buffer)/sizeof( WCHAR) );
|
||
GenOutputArg( DUMP_FMT3, Buffer );
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
TraverseSearchList(
|
||
IN PWCHAR String,
|
||
IN PSEARCH_LIST SearchList,
|
||
OUT PDWORD pValue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Examines search list until it find the correct entry, then returns
|
||
the value corresponding to this entry.
|
||
|
||
Arguments:
|
||
|
||
String - string to match
|
||
SearchList - array of entries containing valid strings
|
||
pValue - value corresponding to a matching valid string
|
||
|
||
Return Value:
|
||
|
||
TRUE a matching entry was found
|
||
FALSE otherwise
|
||
|
||
--*/
|
||
{
|
||
if ( SearchList != NULL) {
|
||
|
||
for ( NOTHING; SearchList->String != NULL; SearchList++) {
|
||
|
||
if ( _wcsicmp( String, SearchList->String) == 0) {
|
||
*pValue = SearchList->Value;
|
||
return( TRUE) ;
|
||
}
|
||
}
|
||
}
|
||
return( FALSE) ;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
Usage(
|
||
BOOL GoodCommand
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Usage of AT command.
|
||
|
||
Arguments:
|
||
|
||
GoodCommand - TRUE if we have a good command input (request for help)
|
||
FALSE if we have a bad command input
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
if ( GlobalErrorReported == TRUE) {
|
||
PutNewLine();
|
||
} else if ( GoodCommand == FALSE) {
|
||
MessagePrint( IDS_INVALID_COMMAND );
|
||
}
|
||
|
||
MessagePrint( IDS_USAGE );
|
||
}
|
||
|
||
#define REG_SCHEDULE_PARMS TEXT("System\\CurrentControlSet\\Services\\Schedule\\Parameters")
|
||
|
||
#define REG_SCHEDULE_USE_OLD TEXT("UseOldParsing")
|
||
|
||
BOOL
|
||
UseOldParsing()
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Checks the registry for
|
||
|
||
HKLM\CurrentControlSet\Services\Schedule\parameters\UseOldParsing
|
||
|
||
If present and equal to 1, then revert to 3.51 level of command line
|
||
parsing. Spaces in filenames will not work with this option. This is
|
||
intended as a migration path for customers who cannot change all their
|
||
command scripts that use AT.EXE right away.
|
||
|
||
--*/
|
||
{
|
||
BOOL fUseOld = FALSE;
|
||
LONG err = 0;
|
||
|
||
do { // Error breakout loop
|
||
|
||
HKEY hkeyScheduleParms;
|
||
DWORD dwType;
|
||
DWORD dwData = 0;
|
||
DWORD cbData = sizeof(dwData);
|
||
|
||
// Break out on any error and use the default, FALSE.
|
||
|
||
if (err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
||
REG_SCHEDULE_PARMS,
|
||
0,
|
||
KEY_READ,
|
||
&hkeyScheduleParms))
|
||
{
|
||
break;
|
||
}
|
||
if (err = RegQueryValueEx(hkeyScheduleParms,
|
||
REG_SCHEDULE_USE_OLD,
|
||
NULL,
|
||
&dwType,
|
||
(LPBYTE)&dwData,
|
||
&cbData ))
|
||
{
|
||
RegCloseKey( hkeyScheduleParms );
|
||
break;
|
||
}
|
||
|
||
if ( dwType == REG_DWORD && dwData == 1 )
|
||
{
|
||
fUseOld = TRUE;
|
||
}
|
||
|
||
RegCloseKey( hkeyScheduleParms );
|
||
|
||
|
||
} while (FALSE) ;
|
||
|
||
return fUseOld;
|
||
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
ValidateCommand(
|
||
IN int argc,
|
||
IN WCHAR ** argv,
|
||
OUT int * pCommand
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Examines command line to see what to do. This validates the command
|
||
line passed into the AT command processor. If this routine finds any
|
||
invalid data, the program exits with an appropriate error message.
|
||
|
||
Arguments:
|
||
|
||
pCommand - pointer to command
|
||
argc - count of arguments
|
||
argv - pointer to table of arguments
|
||
|
||
Return Value:
|
||
|
||
FALSE - if failure, i.e. command will not be executed
|
||
TRUE - if success
|
||
|
||
Comment:
|
||
|
||
Parsing assumes:
|
||
|
||
non-switch (positional) parameters come first and order among these
|
||
parameters is important
|
||
|
||
switch parameters come second and order among these parameters is
|
||
NOT important
|
||
|
||
command (if present) comes last
|
||
|
||
--*/
|
||
{
|
||
int i; // loop index
|
||
int next; // index of Time or JobId argument
|
||
int argno; // where to start in arg string
|
||
BOOL DeleteFound; // did we find a delete switch
|
||
WCHAR * recdatap; // ptr used to build atr_command
|
||
DWORD recdata_len; // len of arg to put in atr_command
|
||
DWORD_PTR JobTime;
|
||
BOOL fUseOldParsing = FALSE;
|
||
|
||
if (argc == 1) {
|
||
*pCommand = DUMP_ALL;
|
||
return( TRUE);
|
||
}
|
||
|
||
// First look for a help switch on the command line.
|
||
|
||
for ( i = 1; i < argc; i++ ) {
|
||
|
||
if ( !_wcsicmp( argv[i], QUESTION_SW)
|
||
|| !_wcsicmp( argv[i], QUESTION_SW_TOO)) {
|
||
|
||
*pCommand = ACTION_USAGE;
|
||
return( TRUE);
|
||
}
|
||
}
|
||
|
||
next = ( ArgIsServerName( argv[ 1]) == TRUE) ? 2 : 1;
|
||
if ( argc == next) {
|
||
*pCommand = DUMP_ALL;
|
||
return( TRUE);
|
||
}
|
||
|
||
if ( (ArgIsDecimalString( argv[ next], &GlobalJobId)) == TRUE) {
|
||
|
||
if ( argc == next + 1) {
|
||
*pCommand = DUMP_ID;
|
||
return( TRUE);
|
||
}
|
||
|
||
if ( ParseJobIdArgs( argv, argc, next + 1, &DeleteFound) == FALSE) {
|
||
return( FALSE); // an invalid argument
|
||
}
|
||
|
||
*pCommand = (DeleteFound == FALSE) ? DUMP_ID : DEL_ID;
|
||
return( TRUE);
|
||
}
|
||
|
||
//
|
||
// Try some variation of "AT [\\ServerName [/DELETE]"
|
||
//
|
||
if ( ParseJobIdArgs( argv, argc, next, &DeleteFound) == TRUE) {
|
||
*pCommand = (DeleteFound == FALSE) ? DUMP_ALL : DEL_ALL;
|
||
return( TRUE);
|
||
}
|
||
|
||
if ( ArgIsTime( argv[ next], &JobTime) == TRUE) {
|
||
|
||
*pCommand = ADD_TO_SCHEDULE;
|
||
|
||
if ( argc < next + 2) {
|
||
return( FALSE); // need something to do, not just time
|
||
}
|
||
|
||
memset( (PBYTE)&GlobalAtInfo, '\0', sizeof(GlobalAtInfo)); // initialize
|
||
GlobalAtInfo.Flags |= JOB_NONINTERACTIVE; // the default
|
||
|
||
if ( ParseInteractiveArg( argv[ next + 1])) {
|
||
next++;
|
||
}
|
||
if ( argc < next + 2) {
|
||
return( FALSE); // once more with feeling
|
||
}
|
||
|
||
if ( ParseTimeArgs( argv, argc, next + 1, &argno) == FALSE) {
|
||
return( FALSE);
|
||
}
|
||
|
||
// Copy argument strings to record.
|
||
|
||
recdatap = GlobalAtInfo.Command = GlobalAtInfoCommand;
|
||
recdata_len = 0;
|
||
|
||
fUseOldParsing = UseOldParsing();
|
||
|
||
for ( i = argno; i < argc; i++) {
|
||
|
||
DWORD temp;
|
||
|
||
//
|
||
// Fix for bug 22068 "AT command does not handle filenames with
|
||
// spaces." The command processor takes a quoted command line arg
|
||
// and puts everything between the quotes into one argv string.
|
||
// The quotes are stripped out. Thus, if any of the string args
|
||
// contain whitespace, then they must be requoted before being
|
||
// concatenated into the command value.
|
||
//
|
||
BOOL fQuote = (!fUseOldParsing && wcschr(argv[i], L' ') != NULL);
|
||
|
||
temp = wcslen(argv[i]) + (fQuote ? 3 : 1); // add 2 for quotes
|
||
|
||
recdata_len += temp;
|
||
|
||
if ( recdata_len > MAX_COMMAND_LEN) {
|
||
MessagePrint( APE_AT_COMMAND_TOO_LONG );
|
||
return( FALSE);
|
||
}
|
||
|
||
if (fQuote)
|
||
{
|
||
wcscpy(recdatap, L"\"");
|
||
wcscat(recdatap, argv[i]);
|
||
wcscat(recdatap, L"\"");
|
||
}
|
||
else
|
||
{
|
||
wcscpy(recdatap, argv[i]);
|
||
}
|
||
|
||
recdatap += temp;
|
||
|
||
// To construct lpszCommandLine argument to CreateProcess call
|
||
// we replace nuls with spaces.
|
||
|
||
*(recdatap - 1) = BLANK;
|
||
|
||
}
|
||
|
||
// Reset space back to null on last argument in string.
|
||
|
||
*(recdatap - 1) = NULLC;
|
||
GlobalAtInfo.JobTime = JobTime;
|
||
return( TRUE);
|
||
}
|
||
|
||
return( FALSE);
|
||
}
|
||
|
||
|
||
VOID
|
||
GetTimeString(
|
||
DWORD_PTR Time,
|
||
WCHAR *Buffer,
|
||
int BufferLength
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function converts a dword time to an ASCII string.
|
||
|
||
Arguments:
|
||
|
||
Time - Time difference in dword from start of the day (i.e. 12am
|
||
midnight ) in milliseconds
|
||
|
||
Buffer - Pointer to the buffer to place the ASCII representation.
|
||
|
||
BufferLength - The length of buffer in bytes.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
#define MINUTES_IN_HOUR 60
|
||
#define SECONDS_IN_MINUTE 60
|
||
{
|
||
WCHAR szTimeString[MAX_TIME_SIZE];
|
||
WCHAR *p = &szTimeString[1];
|
||
DWORD_PTR seconds, minutes, hours;
|
||
int numChars;
|
||
DWORD flags;
|
||
SYSTEMTIME st;
|
||
|
||
GetSystemTime(&st);
|
||
*p = NULLC;
|
||
|
||
// Check if the time format is initialized. If not, initialize it.
|
||
if ( GlobalTimeFormat.AMString == NULL )
|
||
NetpGetTimeFormat( &GlobalTimeFormat );
|
||
|
||
// Convert the time to hours, minutes, seconds
|
||
seconds = (Time/1000);
|
||
hours = seconds / (MINUTES_IN_HOUR * SECONDS_IN_MINUTE );
|
||
seconds -= hours * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
|
||
minutes = seconds / SECONDS_IN_MINUTE;
|
||
seconds -= minutes * SECONDS_IN_MINUTE;
|
||
|
||
st.wHour = (WORD)(hours);
|
||
st.wMinute = (WORD)(minutes);
|
||
st.wSecond = (WORD)(seconds);
|
||
st.wMilliseconds = 0;
|
||
|
||
flags = TIME_NOSECONDS;
|
||
if (!GlobalTimeFormat.TwelveHour)
|
||
flags |= TIME_FORCE24HOURFORMAT;
|
||
|
||
numChars = GetTimeFormatW(GetThreadLocale(),
|
||
flags, &st, NULL, p, MAX_TIME_SIZE-1);
|
||
|
||
if ( numChars > BufferLength )
|
||
numChars = BufferLength;
|
||
|
||
if (*(p+1) == ARG_SEP_CHR && GlobalTimeFormat.LeadingZero) {
|
||
*(--p) = TEXT('0');
|
||
numChars++;
|
||
}
|
||
wcsncpy( Buffer, p, numChars );
|
||
// Append spece for align print format. column based.
|
||
{
|
||
DWORD ColumnLength;
|
||
|
||
// character counts -> array index.
|
||
numChars--;
|
||
|
||
ColumnLength = GetStringColumn( Buffer );
|
||
|
||
while( ColumnLength++ < MAX_TIME_FIELD_LENGTH) {
|
||
Buffer[ numChars++] = BLANK;
|
||
}
|
||
Buffer[ numChars] = UNICODE_NULL;
|
||
}
|
||
}
|
||
|
||
|
||
BOOL
|
||
InitList( PSEARCH_LIST SearchList )
|
||
{
|
||
if ( SearchList != NULL) {
|
||
|
||
if ( SearchList->String != NULL ) // Already initialized
|
||
return TRUE;
|
||
|
||
for ( NOTHING; SearchList->MessageId != 0; SearchList++) {
|
||
if ( MessageGet( SearchList->MessageId,
|
||
&SearchList->String,
|
||
0 ) == 0 )
|
||
{
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
VOID
|
||
TermList( PSEARCH_LIST SearchList )
|
||
{
|
||
if ( SearchList != NULL) {
|
||
|
||
if ( SearchList->String == NULL ) // Not initialized
|
||
return;
|
||
|
||
for ( NOTHING; SearchList->String != NULL; SearchList++) {
|
||
LocalFree( SearchList->String );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
DWORD
|
||
GetStringColumn( WCHAR *lpwstr )
|
||
{
|
||
int cchNeed;
|
||
|
||
cchNeed = WideCharToMultiByte( GetConsoleOutputCP() , 0 ,
|
||
lpwstr , -1 ,
|
||
NULL , 0 ,
|
||
NULL , NULL );
|
||
|
||
return( (DWORD) cchNeed - 1 ); // - 1 : remove NULL
|
||
}
|