From a9d1e30a2adf0fa4f118bdaecc4ab0c5634cc921 Mon Sep 17 00:00:00 2001 From: Eric Kohl Date: Sun, 19 Mar 2017 00:11:31 +0000 Subject: [PATCH] [AT] Implement the AT command: - The /every and /next options are not supported yet. - The 12 hour time format cannot be parsed yet. svn path=/trunk/; revision=74190 --- .../base/applications/cmdutils/CMakeLists.txt | 1 + .../applications/cmdutils/at/CMakeLists.txt | 8 + reactos/base/applications/cmdutils/at/at.c | 515 ++++++++++++++++++ reactos/base/applications/cmdutils/at/at.rc | 17 + .../applications/cmdutils/at/lang/de-DE.rc | 40 ++ .../applications/cmdutils/at/lang/en-US.rc | 50 ++ .../base/applications/cmdutils/at/resource.h | 24 + 7 files changed, 655 insertions(+) create mode 100644 reactos/base/applications/cmdutils/at/CMakeLists.txt create mode 100644 reactos/base/applications/cmdutils/at/at.c create mode 100644 reactos/base/applications/cmdutils/at/at.rc create mode 100644 reactos/base/applications/cmdutils/at/lang/de-DE.rc create mode 100644 reactos/base/applications/cmdutils/at/lang/en-US.rc create mode 100644 reactos/base/applications/cmdutils/at/resource.h diff --git a/reactos/base/applications/cmdutils/CMakeLists.txt b/reactos/base/applications/cmdutils/CMakeLists.txt index 07642853147..c1f238b6ca2 100644 --- a/reactos/base/applications/cmdutils/CMakeLists.txt +++ b/reactos/base/applications/cmdutils/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(at) add_subdirectory(clip) add_subdirectory(comp) add_subdirectory(cscript) diff --git a/reactos/base/applications/cmdutils/at/CMakeLists.txt b/reactos/base/applications/cmdutils/at/CMakeLists.txt new file mode 100644 index 00000000000..d0ff8376965 --- /dev/null +++ b/reactos/base/applications/cmdutils/at/CMakeLists.txt @@ -0,0 +1,8 @@ + +include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils) + +add_executable(at at.c at.rc) +set_module_type(at win32cui UNICODE) +target_link_libraries(at conutils ${PSEH_LIB}) +add_importlibs(at msvcrt kernel32 user32 netapi32) +add_cd_file(TARGET at DESTINATION reactos/system32 FOR all) diff --git a/reactos/base/applications/cmdutils/at/at.c b/reactos/base/applications/cmdutils/at/at.c new file mode 100644 index 00000000000..3a98df43a5d --- /dev/null +++ b/reactos/base/applications/cmdutils/at/at.c @@ -0,0 +1,515 @@ +/* + * PROJECT: ReactOS AT utility + * COPYRIGHT: See COPYING in the top level directory + * FILE: base/applications/cmdutils/at/at.c + * PURPOSE: ReactOS AT utility + * PROGRAMMERS: Eric Kohl + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "resource.h" + + +static +BOOL +ParseTime( + PWSTR pszTime, + PULONG pulJobHour, + PULONG pulJobMinute) +{ + PWSTR startPtr, endPtr; + ULONG ulHour = 0, ulMinute = 0; + BOOL bResult = FALSE; + + startPtr = pszTime; + endPtr = NULL; + ulHour = wcstoul(startPtr, &endPtr, 10); + if (ulHour < 24 && endPtr != NULL && *endPtr == L':') + { + startPtr = endPtr + 1; + endPtr = NULL; + ulMinute = wcstoul(startPtr, &endPtr, 10); + if (ulMinute < 60 && endPtr != NULL && *endPtr == UNICODE_NULL) + { + bResult = TRUE; + + if (pulJobHour != NULL) + *pulJobHour = ulHour; + + if (pulJobMinute != NULL) + *pulJobMinute = ulMinute; + } + } + + return bResult; +} + + +static +BOOL +ParseId( + PWSTR pszId, + PULONG pulId) +{ + PWSTR startPtr, endPtr; + ULONG ulId = 0; + BOOL bResult = FALSE; + + startPtr = pszId; + endPtr = NULL; + ulId = wcstoul(startPtr, &endPtr, 10); + if (endPtr != NULL && *endPtr == UNICODE_NULL) + { + bResult = TRUE; + + if (pulId != NULL) + *pulId = ulId; + } + + return bResult; +} + + +static +VOID +PrintErrorMessage( + DWORD dwError) +{ + PWSTR pszBuffer = NULL; + + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dwError, + 0, + (PWSTR)&pszBuffer, + 0, + NULL); + + ConPuts(StdErr, pszBuffer); + LocalFree(pszBuffer); +} + + +static +VOID +PrintHorizontalLine(VOID) +{ + WCHAR szBuffer[80]; + INT i; + + for (i = 0; i < 79; i++) + szBuffer[i] = L'-'; + szBuffer[79] = UNICODE_NULL; + + ConPuts(StdOut, szBuffer); +} + + +static +DWORD_PTR +GetTimeAsJobTime(VOID) +{ + SYSTEMTIME Time; + DWORD_PTR JobTime; + + GetLocalTime(&Time); + + JobTime = (DWORD_PTR)Time.wHour * 3600000 + + (DWORD_PTR)Time.wMinute * 60000; + + return JobTime; +} + + +static +VOID +JobTimeToTimeString( + PWSTR pszBuffer, + INT cchBuffer, + WORD wHour, + WORD wMinute) +{ + SYSTEMTIME Time = {0, 0, 0, 0, 0, 0, 0, 0}; + + Time.wHour = wHour; + Time.wMinute = wMinute; + + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOSECONDS, + &Time, + NULL, + pszBuffer, + cchBuffer); +} + +static +INT +PrintJobDetails( + PWSTR pszComputerName, + ULONG ulJobId) +{ + AT_INFO *pBuffer = NULL; + DWORD_PTR CurrentTime; + WCHAR szStatusBuffer[16]; + WCHAR szDayBuffer[32]; + WCHAR szTimeBuffer[16]; + WCHAR szInteractiveBuffer[16]; + HINSTANCE hInstance; + NET_API_STATUS Status; + + hInstance = GetModuleHandle(NULL); + + Status = NetScheduleJobGetInfo(pszComputerName, + ulJobId, + (PBYTE *)&pBuffer); + if (Status != NERR_Success) + { + PrintErrorMessage(Status); + return 1; + } + + if (pBuffer->Flags & JOB_EXEC_ERROR) + LoadStringW(hInstance, IDS_ERROR, szStatusBuffer, ARRAYSIZE(szStatusBuffer)); + else + LoadStringW(hInstance, IDS_OK, szStatusBuffer, ARRAYSIZE(szStatusBuffer)); + + if (pBuffer->DaysOfMonth != 0) + { + wcscpy(szDayBuffer, L"TODO: DaysOfMonth!"); + } + else if (pBuffer->DaysOfWeek != 0) + { + wcscpy(szDayBuffer, L"TODO: DaysOfWeek!"); + } + else + { + CurrentTime = GetTimeAsJobTime(); + if (CurrentTime > pBuffer->JobTime) + LoadStringW(hInstance, IDS_TOMORROW, szDayBuffer, ARRAYSIZE(szDayBuffer)); + else + LoadStringW(hInstance, IDS_TODAY, szDayBuffer, ARRAYSIZE(szDayBuffer)); + } + + JobTimeToTimeString(szTimeBuffer, + ARRAYSIZE(szTimeBuffer), + (WORD)(pBuffer->JobTime / 3600000), + (WORD)((pBuffer->JobTime % 3600000) / 60000)); + + if (pBuffer->Flags & JOB_NONINTERACTIVE) + LoadStringW(hInstance, IDS_NO, szInteractiveBuffer, ARRAYSIZE(szInteractiveBuffer)); + else + LoadStringW(hInstance, IDS_YES, szInteractiveBuffer, ARRAYSIZE(szInteractiveBuffer)); + + ConResPrintf(StdOut, IDS_TASKID, ulJobId); + ConResPrintf(StdOut, IDS_STATUS, szStatusBuffer); + ConResPrintf(StdOut, IDS_SCHEDULE, szDayBuffer); + ConResPrintf(StdOut, IDS_TIME, szTimeBuffer); + ConResPrintf(StdOut, IDS_INTERACTIVE, szInteractiveBuffer); + ConResPrintf(StdOut, IDS_COMMAND, pBuffer->Command); + + NetApiBufferFree(pBuffer); + + return 0; +} + + +static +INT +PrintAllJobs( + PWSTR pszComputerName) +{ + PAT_ENUM pBuffer = NULL; + DWORD dwRead = 0, dwTotal = 0; + DWORD dwResume = 0, i; + DWORD_PTR CurrentTime; + NET_API_STATUS Status; + + WCHAR szDayBuffer[32]; + WCHAR szTimeBuffer[16]; + HINSTANCE hInstance; + + Status = NetScheduleJobEnum(pszComputerName, + (PBYTE *)&pBuffer, + MAX_PREFERRED_LENGTH, + &dwRead, + &dwTotal, + &dwResume); + if (Status != NERR_Success) + { + PrintErrorMessage(Status); + return 1; + } + + if (dwTotal == 0) + { + ConResPrintf(StdOut, IDS_NO_ENTRIES); + return 0; + } + + ConResPrintf(StdOut, IDS_JOBS_LIST); + PrintHorizontalLine(); + + hInstance = GetModuleHandle(NULL); + + for (i = 0; i < dwRead; i++) + { + if (pBuffer[i].DaysOfMonth != 0) + { + wcscpy(szDayBuffer, L"TODO: DaysOfMonth"); + } + else if (pBuffer[i].DaysOfWeek != 0) + { + wcscpy(szDayBuffer, L"TODO: DaysOfWeek"); + } + else + { + CurrentTime = GetTimeAsJobTime(); + if (CurrentTime > pBuffer[i].JobTime) + LoadStringW(hInstance, IDS_TOMORROW, szDayBuffer, ARRAYSIZE(szDayBuffer)); + else + LoadStringW(hInstance, IDS_TODAY, szDayBuffer, ARRAYSIZE(szDayBuffer)); + } + + JobTimeToTimeString(szTimeBuffer, + ARRAYSIZE(szTimeBuffer), + (WORD)(pBuffer[i].JobTime / 3600000), + (WORD)((pBuffer[i].JobTime % 3600000) / 60000)); + + ConPrintf(StdOut, + L" %7lu %-22s %-12s %s\n", + pBuffer[i].JobId, + szDayBuffer, + szTimeBuffer, + pBuffer[i].Command); + } + + NetApiBufferFree(pBuffer); + + return 0; +} + + +static +INT +AddJob( + PWSTR pszComputerName, + ULONG ulJobHour, + ULONG ulJobMinute, + BOOL bInteractiveJob, + PWSTR pszCommand) +{ + AT_INFO InfoBuffer; + ULONG ulJobId = 0; + NET_API_STATUS Status; + + InfoBuffer.JobTime = (DWORD_PTR)ulJobHour * 3600000 + + (DWORD_PTR)ulJobMinute * 60000; + InfoBuffer.DaysOfMonth = 0; + InfoBuffer.DaysOfWeek = 0; + InfoBuffer.Flags = bInteractiveJob ? 0 : JOB_NONINTERACTIVE; + InfoBuffer.Command = pszCommand; + + Status = NetScheduleJobAdd(pszComputerName, + (PBYTE)&InfoBuffer, + &ulJobId); + if (Status != NERR_Success) + { + PrintErrorMessage(Status); + return 1; + } + + ConResPrintf(StdOut, IDS_NEW_JOB, ulJobId); + + return 0; +} + + +static +INT +DeleteJob( + PWSTR pszComputerName, + ULONG ulJobId, + BOOL bForceDelete) +{ + NET_API_STATUS Status; + + if (ulJobId == (ULONG)-1 && bForceDelete == FALSE) + { + ConResPrintf(StdOut, IDS_CONFIRM_DELETE); + return 0; + } + + Status = NetScheduleJobDel(pszComputerName, + (ulJobId == (ULONG)-1) ? 0 : ulJobId, + (ulJobId == (ULONG)-1) ? -1 : ulJobId); + if (Status != NERR_Success) + { + PrintErrorMessage(Status); + return 1; + } + + return 0; +} + + +int wmain(int argc, WCHAR **argv) +{ + PWSTR pszComputerName = NULL; + PWSTR pszCommand = NULL; + ULONG ulJobId = (ULONG)-1; + ULONG ulJobHour = (ULONG)-1; + ULONG ulJobMinute = (ULONG)-1; + BOOL bDeleteJob = FALSE, bForceDelete = FALSE; + BOOL bInteractiveJob = FALSE; + BOOL bPrintUsage = FALSE; + INT nResult = 0; + INT i, minIdx; + + /* Initialize the Console Standard Streams */ + ConInitStdStreams(); + + /* Parse the computer name */ + i = 1; + minIdx = 1; + if (i < argc && + argv[i][0] == L'\\' && + argv[i][1] == L'\\') + { + pszComputerName = argv[i]; + i++; + minIdx++; + } + + /* Parse the time or job id */ + if (i < argc && argv[i][0] != L'/') + { + if (ParseTime(argv[i], &ulJobHour, &ulJobMinute)) + { + i++; + minIdx++; + } + else if (ParseId(argv[i], &ulJobId)) + { + i++; + minIdx++; + } + } + + /* Parse the options */ + for (; i < argc; i++) + { + if (argv[i][0] == L'/') + { + if (_wcsicmp(argv[i], L"/?") == 0) + { + bPrintUsage = TRUE; + goto done; + } + else if (_wcsicmp(argv[i], L"/delete") == 0) + { + bDeleteJob = TRUE; + } + else if (_wcsicmp(argv[i], L"/yes") == 0) + { + bForceDelete = TRUE; + } + else if (_wcsicmp(argv[i], L"/interactive") == 0) + { + bInteractiveJob = TRUE; + } +/* + else if (_wcsnicmp(argv[i], L"/every:", 7) == 0) + { + } + else if (_wcsnicmp(argv[i], L"/next:", 6) == 0) + { + } +*/ + else + { + bPrintUsage = TRUE; + nResult = 1; + goto done; + } + } + } + + /* Parse the command */ + if (argc > minIdx && argv[argc - 1][0] != L'/') + { + pszCommand = argv[argc - 1]; + } + + if (bDeleteJob == TRUE) + { + /* Check for invalid options or arguments */ + if (bInteractiveJob == TRUE || + ulJobHour != (ULONG)-1 || + ulJobMinute != (ULONG)-1 || + pszCommand != NULL) + { + bPrintUsage = TRUE; + nResult = 1; + goto done; + } + + nResult = DeleteJob(pszComputerName, ulJobId, bForceDelete); + } + else + { + if (ulJobHour != (ULONG)-1 && ulJobMinute != (ULONG)-1) + { + /* Check for invalid options or arguments */ + if (bForceDelete == TRUE || pszCommand == NULL) + { + bPrintUsage = TRUE; + nResult = 1; + goto done; + } + + nResult = AddJob(pszComputerName, + ulJobHour, + ulJobMinute, + bInteractiveJob, + pszCommand); + } + else + { + /* Check for invalid options or arguments */ + if (bForceDelete == TRUE || bInteractiveJob == TRUE) + { + bPrintUsage = TRUE; + nResult = 1; + goto done; + } + + if (ulJobId == (ULONG)-1) + { + nResult = PrintAllJobs(pszComputerName); + } + else + { + nResult = PrintJobDetails(pszComputerName, ulJobId); + } + } + } + +done: + if (bPrintUsage == TRUE) + ConResPuts(StdOut, IDS_USAGE); + + return nResult; +} + +/* EOF */ diff --git a/reactos/base/applications/cmdutils/at/at.rc b/reactos/base/applications/cmdutils/at/at.rc new file mode 100644 index 00000000000..6f264575d17 --- /dev/null +++ b/reactos/base/applications/cmdutils/at/at.rc @@ -0,0 +1,17 @@ +#include + +#include "resource.h" + +#define REACTOS_STR_FILE_DESCRIPTION "ReactOS AT Command" +#define REACTOS_STR_INTERNAL_NAME "at" +#define REACTOS_STR_ORIGINAL_FILENAME "at.exe" +#include + +/* UTF-8 */ +#pragma code_page(65001) +#ifdef LANGUAGE_DE_DE + #include "lang/de-DE.rc" +#endif +#ifdef LANGUAGE_EN_US + #include "lang/en-US.rc" +#endif diff --git a/reactos/base/applications/cmdutils/at/lang/de-DE.rc b/reactos/base/applications/cmdutils/at/lang/de-DE.rc new file mode 100644 index 00000000000..ef6d5d3945a --- /dev/null +++ b/reactos/base/applications/cmdutils/at/lang/de-DE.rc @@ -0,0 +1,40 @@ +LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_USAGE "Mit dem AT Befeht können Befehle und Programme zu einem vorbestimmten\n\ +Termin gestartet werden. Der Zeitplandienst muss gestartet sein, um den\n\ +Befeht AT zu verwenden.\n\n\ +AT [\\\\Computername] [ [Kennung] [/DELETE] | /DELETE [/YES]]\n\ +AT [\\\\Computername] Zeit [/INTERACTIVE]\n\ + [ /EVERY:Datum[,...] | /NEXT:Datum[,...]] ""Befehl""\n\n\ +\\\\Computername ...\n\ +Kennung ...\n\ +/DELETE ...\n\ +/YES ...\n\ +Zeit Gibt die Zeit an, zu der ein Befehl ausgeführt werden soll.\n\ +/INTERACTIVE ...\n\ +/EVERY:Datum[,...] ...\n\ +/NEXT:Datum[,...] ...\n\ +""Befehl"" Ist der auszuführende Befehl oder das Stapelprogramm.\n" + + IDS_CONFIRM_DELETE "Dieser Vorgang wird alle geplanten Aufräge löschen.\nMöchten Sie diesen Vorgang fortsetzen? (J/N) [N]" + IDS_NEW_JOB "Neuer Auftrag hinzugefügt. Kennung = %lu\n" + IDS_JOBS_LIST "Statuskenn. Tag Zeit Befehlszeile\n" + IDS_NO_ENTRIES "Es sind keine Einträge in der Liste.\n" + + IDS_TODAY "Heute" + IDS_TOMORROW "Morgen" + + IDS_YES "Ja" + IDS_NO "Nein" + IDS_ERROR "ERROR" + IDS_OK "OK" + + IDS_TASKID "Taskkennung: %lu\n" + IDS_STATUS "Status: %s\n" + IDS_SCHEDULE "Zeitplan: %s\n" + IDS_TIME "Zeit: %s\n" + IDS_INTERACTIVE "Interaktiv: %s\n" + IDS_COMMAND "Befehl: %s\n" +END diff --git a/reactos/base/applications/cmdutils/at/lang/en-US.rc b/reactos/base/applications/cmdutils/at/lang/en-US.rc new file mode 100644 index 00000000000..c401bf1f24a --- /dev/null +++ b/reactos/base/applications/cmdutils/at/lang/en-US.rc @@ -0,0 +1,50 @@ +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +STRINGTABLE +BEGIN + IDS_USAGE "The AT command schedules commands and programs to run on a computer at\n\ +a specified time and date. The Schedule service must be running to use\n\ +the AT command.\n\n\ +AT [\\\\computername] [ [id] [/DELETE] | /DELETE [/YES]]\n\ +AT [\\\\computername] time [/INTERACTIVE]\n\ + [ /EVERY:date[,...] | /NEXT:date[,...]] ""command""\n\n\ +\\\\computername Specifies a remote computer. Commands are scheduled on the \n\ + local computer if this parameter is omitted.\n\ +id Is an identification number assigned to a scheduled\n\ + command.\n\ +/DELETE Cancels a scheduled command. If id is omitted, all the\n\ + scheduled commands on the computer are canceled.\n\ +/YES Used with cancel all jobs command when no further\n\ + confirmation is desired.\n\ +Zeit Specifies the time when command is to run.\n\ +/INTERACTIVE Allows the ob to interact with the desktop of the user\n\ + who is logged on at the time the job runs.\n\ +/EVERY:date[,...] Runs the command on each specified day(s) of the week or\n\ + month. If date is omitted, the current day of the month\n\ + is assumed.\n\ +/NEXT:date[,...] Runs the specified command on the next occurrence of the\n\ + day (for example, next Thursday). If date is omitted, the \n\ + current day of the month is assumed.\n\ +""command"" Is the command or batch program to be run.\n" + + IDS_CONFIRM_DELETE "This operation will delete all seduled jobs.\nDo you want to continue this operation? (Y/N) [N]: " + IDS_NEW_JOB "Added a new job with job ID = %lu\n" + IDS_JOBS_LIST "Statuskenn. Tag Zeit Befehlszeile\n" + IDS_JOBS_LIST "Status ID Day Time Command Line\n" + IDS_NO_ENTRIES "There are no entries in the list.\n" + + IDS_TODAY "Today" + IDS_TOMORROW "Tomorrow" + + IDS_YES "Yes" + IDS_NO "No" + IDS_ERROR "ERROR" + IDS_OK "OK" + + IDS_TASKID "Task ID: %lu\n" + IDS_STATUS "Status: %s\n" + IDS_SCHEDULE "Schedule: %s\n" + IDS_TIME "Time of day: %s\n" + IDS_INTERACTIVE "Interactive: %s\n" + IDS_COMMAND "Command: %s\n" +END diff --git a/reactos/base/applications/cmdutils/at/resource.h b/reactos/base/applications/cmdutils/at/resource.h new file mode 100644 index 00000000000..a153e08eb52 --- /dev/null +++ b/reactos/base/applications/cmdutils/at/resource.h @@ -0,0 +1,24 @@ +#pragma once + +#define IDS_USAGE 100 + +#define IDS_CONFIRM_DELETE 105 +#define IDS_NEW_JOB 106 +#define IDS_JOBS_LIST 107 +#define IDS_NO_ENTRIES 108 + +#define IDS_TODAY 109 +#define IDS_TOMORROW 110 + + +#define IDS_YES 101 +#define IDS_NO 102 +#define IDS_ERROR 103 +#define IDS_OK 104 + +#define IDS_TASKID 122 +#define IDS_STATUS 123 +#define IDS_SCHEDULE 124 +#define IDS_TIME 125 +#define IDS_INTERACTIVE 126 +#define IDS_COMMAND 127 -- 2.17.1