[AT]
authorEric Kohl <eric.kohl@reactos.org>
Sun, 19 Mar 2017 00:11:31 +0000 (00:11 +0000)
committerEric Kohl <eric.kohl@reactos.org>
Sun, 19 Mar 2017 00:11:31 +0000 (00:11 +0000)
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

reactos/base/applications/cmdutils/CMakeLists.txt
reactos/base/applications/cmdutils/at/CMakeLists.txt [new file with mode: 0644]
reactos/base/applications/cmdutils/at/at.c [new file with mode: 0644]
reactos/base/applications/cmdutils/at/at.rc [new file with mode: 0644]
reactos/base/applications/cmdutils/at/lang/de-DE.rc [new file with mode: 0644]
reactos/base/applications/cmdutils/at/lang/en-US.rc [new file with mode: 0644]
reactos/base/applications/cmdutils/at/resource.h [new file with mode: 0644]

index 0764285..c1f238b 100644 (file)
@@ -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 (file)
index 0000000..d0ff837
--- /dev/null
@@ -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 (file)
index 0000000..3a98df4
--- /dev/null
@@ -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 <eric.kohl@reactos.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <windef.h>
+#include <winbase.h>
+#include <winuser.h>
+#include <wincon.h>
+#include <winnls.h>
+#include <lm.h>
+
+#include <conutils.h>
+
+#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 (file)
index 0000000..6f26457
--- /dev/null
@@ -0,0 +1,17 @@
+#include <windef.h>
+
+#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 <reactos/version.rc>
+
+/* 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 (file)
index 0000000..ef6d5d3
--- /dev/null
@@ -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 (file)
index 0000000..c401bf1
--- /dev/null
@@ -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 (file)
index 0000000..a153e08
--- /dev/null
@@ -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