--- /dev/null
+/*
+ * 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 */
--- /dev/null
+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