1223 lines
38 KiB
C++
1223 lines
38 KiB
C++
// ===========================================================================
|
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (C) Microsoft Corporation. All Rights Reserved.
|
|
// ===========================================================================
|
|
//
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// File: sa.cpp
|
|
//
|
|
// Contents: All code needed to build the simple job submission tool
|
|
//
|
|
// Purpose: Demonstration of calls to methods of the following new
|
|
// interfaces:
|
|
// 1. ITaskScheduler
|
|
// 2. ITask
|
|
// 3. ITaskTrigger
|
|
// 4. IEnumWorkItems
|
|
//
|
|
// Comments: This application is similar in functionality to
|
|
// the "AT" command provided with Windows NT. However,
|
|
// the AT command functions through the NetSchedule APIs,
|
|
// as opposed to the COM interfaces demonstrated here.
|
|
//
|
|
// The Task Scheduler replaces ATSVC.EXE and implements
|
|
// those APIs as a restricted subset of the new ones
|
|
// demonstrated here.
|
|
//
|
|
// On Win9x systems, the Task Scheduler replaces
|
|
// the System Agent, providing more flexible and reliable
|
|
// scheduling options than previously available.
|
|
// However, although the SAGE jobs are converted
|
|
// to Task Scheduler work items, the actual
|
|
// SAGE API has been abandoned and is not implemented.
|
|
//
|
|
// The Task Scheduler APIs provide a greater degree of
|
|
// freedom and robustness than those APIs. This code
|
|
// demonstrates a stripped version of AT rewritten to
|
|
// use the new APIs.
|
|
//
|
|
// Additional: The Task Scheduler service must be running. It is easily
|
|
// started by the command "net start schedule" if it is not
|
|
// running. It is also trivial to start it through
|
|
// the service control manager APIs. Doing it in the code
|
|
// directly makes a good enhancement to the program below.
|
|
// Note that only administrators may start the service.
|
|
// Users of systems that expect non-administrators to
|
|
// schedule tasks should use the System Services snapin
|
|
// in Windows NT 5.0 to set the service to AutoStart,
|
|
// if it is not already.
|
|
//
|
|
// Since there is not a service controller on Win9x,
|
|
// the user may start the service by executing the
|
|
// binary directly from the command line (mstask.exe).
|
|
// Win9x users will see a system tray icon if the service
|
|
// is running.
|
|
//
|
|
// The service may also be started from the folder UI available
|
|
// directly by opening "My Computer" and then opening the
|
|
// "Scheduled Tasks" folder. The start command appears on the
|
|
// "Advanced" menu.
|
|
//
|
|
// Sample code to start and stop the service, on both
|
|
// Windows NT and Win9x based systems appears in the
|
|
// documentation.
|
|
//
|
|
// If the Task Scheduler service is not running, most
|
|
// of this program will fail to execute. If there are
|
|
// tasks waiting to execute after login, the service
|
|
// will have been started by default.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
#include <windows.h>
|
|
#include <mbctype.h>
|
|
#include <initguid.h>
|
|
#include <mstask.h>
|
|
#include <msterr.h>
|
|
|
|
//+--------------------------------------------------------------------------
|
|
// Global variables
|
|
//---------------------------------------------------------------------------
|
|
|
|
ITaskScheduler *g_pITaskScheduler = NULL;
|
|
|
|
//+--------------------------------------------------------------------------
|
|
// Function Prototypes
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT Init(void);
|
|
void Cleanup(void);
|
|
void DisplayHelp(LPWSTR);
|
|
HRESULT EnumerateJobs(void);
|
|
HRESULT DeleteJob(LPWSTR);
|
|
HRESULT AddJob(LPWSTR, LPSYSTEMTIME, LPWSTR, LPWSTR, LPWSTR);
|
|
HRESULT ConvertToSystemTime(LPWSTR, LPSYSTEMTIME);
|
|
|
|
//+--------------------------------------------------------------------------
|
|
// Defining some constants for better readability
|
|
//---------------------------------------------------------------------------
|
|
|
|
const int MAX_COMMAND_LENGTH = 255;
|
|
const int MAX_ACCOUNT_LENGTH = 127;
|
|
const int MAX_PASSWORD_LENGTH = 127;
|
|
const int MAX_TIME_LENGTH = 6;
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: main
|
|
//
|
|
// Synopsis: Entry point for code. Parses command line,
|
|
// and calls appropriate subfunction as a result.
|
|
//
|
|
// Arguments: See DisplayHelp(). Uses argv, argc to get
|
|
// command line args.
|
|
//
|
|
// Returns: S_OK for success or the failure code for failure.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
LONG main (int argc, char **argv)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UINT uCodePage;
|
|
char *lpcszDeleteFlag = "/DELETE";
|
|
WCHAR lpwszProgName[MAX_COMMAND_LENGTH];
|
|
WCHAR lpwszJobName[MAX_COMMAND_LENGTH];
|
|
WCHAR lpwszCommand[MAX_COMMAND_LENGTH];
|
|
WCHAR lpwszUserName[MAX_ACCOUNT_LENGTH];
|
|
WCHAR lpwszPassword[MAX_PASSWORD_LENGTH];
|
|
WCHAR lpwszTime[MAX_TIME_LENGTH];
|
|
SYSTEMTIME tTime;
|
|
|
|
// For protection
|
|
|
|
g_pITaskScheduler = NULL;
|
|
|
|
// It is good practice to check lengths on all input params prior to using as a safety precaution against attack
|
|
// strlen() is OK to use for this as the command line arguments will be NULL terminated
|
|
|
|
if (strlen(argv[0]) > MAX_COMMAND_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: Program name too long\n");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// String conversion initialization
|
|
|
|
uCodePage = _getmbcp();
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[0], -1, lpwszProgName, MAX_COMMAND_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
// Check number of command line arguments.
|
|
// 1 argument = enumerate work items
|
|
// 2 args = display help (don't init OLE)
|
|
// 3 args = delete work item
|
|
// 4, 5 or 6 args = add a work item
|
|
// all others = error, display help
|
|
|
|
if ((argc == 2) || (argc > 6))
|
|
{
|
|
DisplayHelp(lpwszProgName);
|
|
return hr;
|
|
}
|
|
|
|
// Attempt to initialize OLE and fill in the global g_pITaskScheduler
|
|
|
|
hr = Init();
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: OLE initialization and instantiation failed with 0x%x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
switch(argc)
|
|
{
|
|
case 1:
|
|
// User would like to enumerate work items
|
|
|
|
hr = EnumerateJobs();
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Job enumeration failed with 0x%x\n", hr);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if (! lstrcmpiA(argv[2], lpcszDeleteFlag))
|
|
{
|
|
// User would like to delete work item in argv[1]
|
|
|
|
// It is good practice to check lengths on all input params prior to using as a safety precaution against attack
|
|
// strlen() is OK to use for this as the command line arguments will be NULL terminated
|
|
|
|
if (strlen(argv[1]) > MAX_COMMAND_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: Job name too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[1], -1, lpwszJobName, MAX_COMMAND_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
hr = DeleteJob(lpwszJobName);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Job deletion failed with 0x%x\n", hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// User has made an error in command line args
|
|
|
|
// Note that it is usually not safe to have a user-supplied value output via a %s format in a printf statement;
|
|
// however, we have already verified the length of lpwszProgName, so we can be confident that these calls are safe.
|
|
|
|
wprintf(L"Error: Must be %s taskname.job /DELETE\n\n", lpwszProgName);
|
|
wprintf(L"%s /? for more help.\n", lpwszProgName);
|
|
hr = E_FAIL;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
// User would like to add a work item. The following
|
|
// argument cases apply:
|
|
// 4 - User has specified work item name, but not given
|
|
// username or password. Will prompt later.
|
|
// 5 - User has specified work item name and username,
|
|
// but no password. Will prompt later.
|
|
// 6 - User has specified all information.
|
|
|
|
// It is good practice to check lengths on all input params prior to using as a safety precaution against attack
|
|
// strlen() is OK to use for this as the command line arguments will be NULL terminated
|
|
|
|
if (strlen(argv[1]) > MAX_COMMAND_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: Job name too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (strlen(argv[2]) > MAX_TIME_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: Time too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (strlen(argv[3]) > MAX_COMMAND_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: Command too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
// Convert the strings; be sure to check return values
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[1], -1, lpwszJobName, MAX_COMMAND_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[2], -1, lpwszTime, MAX_TIME_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
hr = ConvertToSystemTime(lpwszTime, &tTime);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Conversion of command line time to system time failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[3], -1, lpwszCommand, MAX_COMMAND_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
*lpwszUserName = L'\0';
|
|
*lpwszPassword = L'\0';
|
|
|
|
if (argc > 4)
|
|
{
|
|
// It is good practice to check lengths on all input params prior to using as a safety precaution against attack
|
|
// strlen() is OK to use for this as the command line arguments will be NULL terminated
|
|
|
|
if (strlen(argv[4]) > MAX_ACCOUNT_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: User name too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[4], -1, lpwszUserName, MAX_ACCOUNT_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argc == 6)
|
|
{
|
|
// It is good practice to check lengths on all input params prior to using as a safety precaution against attack
|
|
// strlen() is OK to use for this as the command line arguments will be NULL terminated
|
|
|
|
if (strlen(argv[5]) > MAX_PASSWORD_LENGTH -1)
|
|
{
|
|
wprintf(L"Error: User name too long\n");
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, argv[5], -1, lpwszPassword, MAX_PASSWORD_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
hr = AddJob(lpwszJobName, &tTime, lpwszCommand, lpwszUserName, lpwszPassword);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Job creation failed with 0x%x\n", hr);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
DisplayHelp(lpwszProgName);
|
|
}
|
|
|
|
Cleanup();
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: Init()
|
|
//
|
|
// Synopsis: Called to initialize and instantiate a task
|
|
// scheduler object.
|
|
//
|
|
// Arguments: none (void)
|
|
//
|
|
// Returns: HRESULT indicating success or failure. S_OK on success.
|
|
//
|
|
// Side effect: Sets global pointer g_pITaskScheduler, for use in other
|
|
// functions.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT Init()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Bring in the library
|
|
|
|
hr = CoInitialize(NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to initialize COM with 0x%x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
// Create the pointer to Task Scheduler object
|
|
// CLSID from the header file mstask.h
|
|
|
|
hr = CoCreateInstance(
|
|
CLSID_CTaskScheduler,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_ITaskScheduler,
|
|
(void **) &g_pITaskScheduler);
|
|
|
|
// Should we fail, unload the library
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed CoCreateInstance with 0x%x\n", hr);
|
|
CoUninitialize();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: Cleanup()
|
|
//
|
|
// Synopsis: Called to clean up OLE, memory, etc. before termination
|
|
//
|
|
// Arguments: none (void)
|
|
//
|
|
// Returns: nothing (void)
|
|
//
|
|
// Side effect: Cancels the global pointer g_pITaskScheduler.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void Cleanup()
|
|
{
|
|
if (g_pITaskScheduler)
|
|
{
|
|
g_pITaskScheduler->Release();
|
|
g_pITaskScheduler = NULL;
|
|
}
|
|
|
|
// Unload the library, now that our pointer is freed.
|
|
|
|
CoUninitialize();
|
|
return;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: DisplayHelp()
|
|
//
|
|
// Synopsis: Prints out help and usage information
|
|
//
|
|
// Arguments: lpwszProgName - a pointer to a WSTR containing argv[0]
|
|
//
|
|
// Returns: nothing (void)
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void DisplayHelp(LPWSTR lpwszProgName)
|
|
{
|
|
// Note that it is usually not safe to have a user-supplied value output via a %s format in a printf statement;
|
|
// however, we have already verified the length of lpwszProgName, so we can be confident that these calls will not overflow.
|
|
|
|
wprintf(L"%s -- a small program to replicate some\n", lpwszProgName);
|
|
wprintf(L"\tfunctionality of the AT command, using the\n");
|
|
wprintf(L"\tnew Task Scheduler interfaces.\n\n");
|
|
|
|
wprintf(L"Usage:\n");
|
|
wprintf(L"\t%s /?\n\t\t\t\t\t\t- Display this help\n",lpwszProgName);
|
|
wprintf(L"\t%s\n\t\t\t\t\t\t- Show all work items\n",lpwszProgName);
|
|
wprintf(L"\t%s TaskName.job /DELETE \n\t\t\t\t\t\t- delete work item\n",lpwszProgName);
|
|
wprintf(L"\t%s TaskName.job Time Command [UserName [Password]]\n\t\t\t\t\t\t- submit a new work item\n\n",lpwszProgName);
|
|
|
|
wprintf(L"TaskName.job is the name of the work item object on disk.\n");
|
|
wprintf(L"The work item will appear in the Scheduled Tasks folder as TaskName\n");
|
|
wprintf(L"but must be given the extension \".job\" for the service \n");
|
|
wprintf(L"to recognize and run it.\n\n");
|
|
|
|
wprintf(L"Task Time is in 24 hour format (such as 15:30) and is the\n");
|
|
wprintf(L"next instance of this time within 24 hours.\n\n");
|
|
|
|
wprintf(L"The Command should contain the name of the executable to run.\n");
|
|
wprintf(L"Note that the pathname may NOT contain spaces. If the\n");
|
|
wprintf(L"program requires command line parameters, enclose the entire\n");
|
|
wprintf(L"command string in quotation marks.\n\n");
|
|
|
|
wprintf(L"Username and password are required to run the specified work item under Windows NT.\n");
|
|
wprintf(L"Under Windows NT, if not specified, you will be prompted.\n");
|
|
wprintf(L"Under Windows 95, both fields are ignored. If not specified\n");
|
|
wprintf(L"on the command line, you will not be prompted.\n\n");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: EnumerateJobs()
|
|
//
|
|
// Synopsis: Goes through all scheduled work items on a system and
|
|
// lists them, along with a short trigger string.
|
|
//
|
|
// Arguments: none (void). Requires g_pITaskScheduler.
|
|
//
|
|
// Returns: HRESULT indicating success (S_OK) or other.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT EnumerateJobs()
|
|
{
|
|
HRESULT hr = S_OK, hrLoop = S_OK;
|
|
IEnumWorkItems *pIEnumWorkItems;
|
|
IUnknown *pIU;
|
|
ITask *pITask;
|
|
ULONG ulTasksToGet = 1, ulActualTasksRetrieved = 0;
|
|
LPWSTR *rgpwszNames, pwszTrigger;
|
|
WORD wTrigCount = 0;
|
|
WORD wTemp;
|
|
|
|
|
|
//
|
|
// Get an enumeration pointer, using ITaskScheduler::Enum
|
|
//
|
|
|
|
hr = g_pITaskScheduler->Enum(&pIEnumWorkItems);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Failed to get enumerator with 0x%x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
do
|
|
{
|
|
// Get a single work item, using IEnumWorkItems::Next
|
|
|
|
hrLoop = pIEnumWorkItems->Next(ulTasksToGet, &rgpwszNames, &ulActualTasksRetrieved);
|
|
if (hrLoop == S_FALSE)
|
|
{
|
|
// There are no more waiting tasks to look at
|
|
break;
|
|
}
|
|
|
|
// Attach to the work item, using ITaskScheduler::Activate
|
|
|
|
hr = g_pITaskScheduler->Activate(rgpwszNames[0], IID_ITask, &pIU);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Activate failed with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
// QI pIU for pITask
|
|
|
|
hr = pIU->QueryInterface(IID_ITask, (void **) &pITask);
|
|
pIU->Release();
|
|
pIU = NULL;
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: QI for ITask failed in Activate with 0x%x\n", hr);
|
|
break;
|
|
}
|
|
|
|
// Display task name
|
|
|
|
// rgpwszNames[0] is the value returned by the Task Scheduler API
|
|
// and should be appropriately bounded and therefore safe to pass into %s
|
|
|
|
wprintf(L"Task: %s\n",rgpwszNames[0]);
|
|
|
|
// Use ITask::GetTriggerCount to get count of triggers
|
|
|
|
hr = pITask->GetTriggerCount(&wTrigCount);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to count triggers with 0x%x\n", hr);
|
|
pITask->Release();
|
|
break;
|
|
}
|
|
|
|
for (wTemp = 0; wTemp < wTrigCount; wTemp++)
|
|
{
|
|
// Dump Triggers using ITask::GetTriggerString
|
|
|
|
hr = pITask->GetTriggerString(wTemp, &pwszTrigger);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to get trigger string with 0x%x\n", hr);
|
|
pITask->Release();
|
|
break;
|
|
}
|
|
|
|
// pwszTrigger is the value returned by the Task Scheduler API
|
|
// and should be appropriately bounded and therefore safe to pass into %s
|
|
|
|
wprintf(L"\tTrigger: %s\n",pwszTrigger);
|
|
|
|
// Clean up the memory we were allocated for trig string
|
|
|
|
CoTaskMemFree(pwszTrigger);
|
|
}
|
|
|
|
// Clean up each element in the array of job names, then
|
|
// clean up the final array.
|
|
|
|
CoTaskMemFree(rgpwszNames[0]);
|
|
CoTaskMemFree(rgpwszNames);
|
|
|
|
// Free the ITask pointer
|
|
|
|
pITask->Release();
|
|
|
|
} while(1);
|
|
|
|
// Release the enumeration pointer
|
|
|
|
pITask = NULL;
|
|
|
|
pIEnumWorkItems->Release();
|
|
pIEnumWorkItems = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: DeleteJob()
|
|
//
|
|
// Synopsis: Deletes a work item from the Scheduled Tasks folder.
|
|
//
|
|
// Arguments: lpwszJobName - the name of the work item to delete
|
|
//
|
|
// Returns: HRESULT indicating success (S_OK) or failure.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT DeleteJob(LPWSTR lpwszJobName)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = g_pITaskScheduler->Delete(lpwszJobName);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: AddJob()
|
|
//
|
|
// Synopsis: Adds a new work item to the Scheduled Tasks folder.
|
|
//
|
|
// Arguments: lpwszJobName - name of the task file
|
|
// lptTime - pointer to SYSTEMTIME struct containing
|
|
// the time the job should run.
|
|
// lpwszCommand - name of application (command) to run.
|
|
// lpwszUserName- user name to run job under
|
|
// lpwszPassword- password for that user
|
|
//
|
|
// Returns: HRESULT indicating success (S_OK) or failure.
|
|
//
|
|
// Notes: The password or BOTH the username and password
|
|
// may be passed in as '\0' strings, in which case
|
|
// this function will prompt on stdout for password
|
|
// and username
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT AddJob(LPWSTR lpwszJobName, LPSYSTEMTIME lptTime,
|
|
LPWSTR lpwszCommand, LPWSTR lpwszUserName,
|
|
LPWSTR lpwszPassword)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IUnknown *pIU;
|
|
IScheduledWorkItem *pISWI;
|
|
IPersistFile *pIPF;
|
|
ITask *pITask;
|
|
ITaskTrigger *pITaskTrig;
|
|
DWORD dwTaskFlags, dwTrigFlags;
|
|
WORD wTrigNumber;
|
|
TASK_TRIGGER TaskTrig;
|
|
WCHAR lpwszAppName[MAX_COMMAND_LENGTH];
|
|
int i;
|
|
|
|
//
|
|
// Initialize the pointers to NULL, so that the fail-out routine works
|
|
// All pointer cleanup will be done at the end of the routine.
|
|
//
|
|
|
|
pITask = NULL;
|
|
pITaskTrig = NULL;
|
|
pISWI = NULL;
|
|
pIU = NULL;
|
|
pIPF = NULL;
|
|
|
|
// Add the task. Most likely failure is that work item already exists.
|
|
// Uses ITaskScheduler::NewWorkItem
|
|
|
|
hr = g_pITaskScheduler->NewWorkItem(lpwszJobName,
|
|
CLSID_CTask,
|
|
IID_ITask,
|
|
&pIU);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Create New Task failed with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// We now have an IUnknown pointer. This is queried for an ITask
|
|
// pointer on the work item we just added.
|
|
|
|
hr = pIU->QueryInterface(IID_ITask, (void **) &pITask);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: IUnknown failed to yield ITask with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
//
|
|
// In order for security to work and be set, later we need to
|
|
// attach the task object to a file on disk. Therefore,
|
|
// we must use IPersistFile to save the task object. This
|
|
// also will confirm that the object on disk doesn't already
|
|
// exist - which could be a potential source of later errors.
|
|
//
|
|
// The task scheduler does not normally commit jobs to disk
|
|
// immediately upon creation because disk access is slow compared
|
|
// to the in-memory operation of creating a task. Therefore,
|
|
// you can rapidly create lots of task objects (for instance,
|
|
// in response to incoming net events) and commit when things
|
|
// slow down.
|
|
//
|
|
// Here, we don't care about speed.
|
|
//
|
|
|
|
hr = pITask->QueryInterface(IID_IPersistFile, (void **) &pIPF);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: QI for IPersistFile failed with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
hr = pIPF->Save(NULL, FALSE);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Save object failed with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Extract parameters from the application name
|
|
// Note that there might not be any parameters at all.
|
|
// We butcher a path like C:\Program Files\foo.exe, because
|
|
// we break on space, but this is ok, because user could say
|
|
// C:\progra~1\foo.exe instead.
|
|
|
|
i = 0;
|
|
while ((*lpwszCommand != L' ') && (*lpwszCommand != '\0'))
|
|
{
|
|
lpwszAppName[i] = *lpwszCommand;
|
|
i++;
|
|
|
|
// we've already verified the length of lpwszCommand (including NULL terminator) is <= MAX_COMMAND_LENGTH,
|
|
// but it doesn't hurt to be extra paranoid regarding buffer overflows in case the code changes elsewhere
|
|
|
|
if (i > MAX_COMMAND_LENGTH - 1)
|
|
{
|
|
wprintf(L"Error: Attempted to exceed buffer size while copying application name\n");
|
|
hr = E_FAIL;
|
|
goto finish;
|
|
}
|
|
|
|
lpwszCommand++;
|
|
}
|
|
lpwszAppName[i] = L'\0';
|
|
if (*lpwszCommand == L' ')
|
|
{
|
|
lpwszCommand++;
|
|
}
|
|
|
|
// Set command name with ITask::SetApplicationName
|
|
|
|
hr = pITask->SetApplicationName(lpwszAppName);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to set command name (with parms) with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Set task parameters with ITask::SetParameters
|
|
|
|
hr = pITask->SetParameters(lpwszCommand);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to set parameters with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Set the comment, so we know how this job got there
|
|
// Uses ITask::SetComment
|
|
|
|
hr = pITask->SetComment(L"This scheduled task created by command line SDK sample tool");
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to set task comment with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Set the flags on the task object
|
|
// Use the base class member to do this - IScheduledWorkItem::SetFlags()
|
|
|
|
hr = pITask->QueryInterface(IID_IScheduledWorkItem, (void **) &pISWI);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to get base class interface to set flags with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
dwTaskFlags = TASK_FLAG_DELETE_WHEN_DONE;
|
|
hr = pISWI->SetFlags(dwTaskFlags);
|
|
|
|
// Release pISWI, since we no longer need it.
|
|
|
|
pISWI->Release();
|
|
pISWI = NULL;
|
|
|
|
// Check for failure to set flags. This really does not need
|
|
// to be fatal.
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to set task flags with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Now, create a trigger to run the task at our specified time.
|
|
// Uses ITask::CreateTrigger()
|
|
|
|
hr = pITask->CreateTrigger(&wTrigNumber, &pITaskTrig);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to create a new trigger with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Now, fill in the trigger as necessary.
|
|
|
|
dwTrigFlags = 0;
|
|
|
|
ZeroMemory((void*) &TaskTrig, sizeof(TASK_TRIGGER));
|
|
|
|
TaskTrig.cbTriggerSize = sizeof(TASK_TRIGGER);
|
|
TaskTrig.wBeginYear = lptTime->wYear;
|
|
TaskTrig.wBeginMonth = lptTime->wMonth;
|
|
TaskTrig.wBeginDay = lptTime->wDay;
|
|
TaskTrig.wStartHour = lptTime->wHour;
|
|
TaskTrig.wStartMinute = lptTime->wMinute;
|
|
TaskTrig.rgFlags = dwTrigFlags;
|
|
TaskTrig.TriggerType = TASK_TIME_TRIGGER_ONCE;
|
|
|
|
// Add this trigger to the task using ITaskTrigger::SetTrigger
|
|
|
|
hr = pITaskTrig->SetTrigger(&TaskTrig);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to set trigger to desired values with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
//
|
|
// We need to see if we support security, and we
|
|
// do this by calling ITask::SetAccountInformation
|
|
// and checking if the failure code is SCHED_E_NO_SECURITY_SERVICES
|
|
// We could also use GetVersionEx() to find out if we
|
|
// were running on Windows NT or not, but this is even easier.
|
|
//
|
|
|
|
hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword);
|
|
if (hr != SCHED_E_NO_SECURITY_SERVICES)
|
|
{
|
|
|
|
// Check to see if username is null
|
|
|
|
if (*lpwszPassword == L'\0')
|
|
{
|
|
// String conversion initialization
|
|
|
|
UINT uCodePage = _getmbcp();
|
|
|
|
// If password was null, chance username is, too.
|
|
|
|
if (*lpwszUserName == L'\0')
|
|
{
|
|
wprintf(L"Enter username to run job %s as: ",lpwszJobName);
|
|
|
|
// never use scanf() or wscanf(), as it is all too easy for a user to overflow the buffer
|
|
// fgets() and fgetws() allow us to specify a maximum number of characters to read
|
|
|
|
char lpszUserName[MAX_ACCOUNT_LENGTH];
|
|
if (!fgets(lpszUserName, MAX_ACCOUNT_LENGTH, stdin))
|
|
{
|
|
wprintf(L"Error: User name input failed\n");
|
|
goto finish;
|
|
}
|
|
|
|
// eliminate the new line character if it was read
|
|
// this will always be the case if the input is less than MAX_ACCOUNT_LENGTH - 1
|
|
|
|
if (char* pNewLine = strchr(lpszUserName, '\n'))
|
|
{
|
|
*pNewLine = '\0';
|
|
}
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, lpszUserName, -1, lpwszUserName, MAX_ACCOUNT_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
wprintf(L"\n");
|
|
}
|
|
|
|
wprintf(L"Enter password to user %s: ",lpwszUserName);
|
|
|
|
// never use scanf() or wscanf(), as it is all too easy for a user to overflow the buffer
|
|
// fgets() and fgetws() allow us to specify a maximum number of characters to read
|
|
|
|
char lpszPassword[MAX_PASSWORD_LENGTH];
|
|
if (!fgets(lpszPassword, MAX_PASSWORD_LENGTH, stdin))
|
|
{
|
|
wprintf(L"Error: Password input failed\n");
|
|
goto finish;
|
|
}
|
|
|
|
// eliminate the new line character if it was read
|
|
// this will always be the case if the input is less than MAX_ACCOUNT_LENGTH - 1
|
|
|
|
if (char* pNewLine = strchr(lpszPassword, '\n'))
|
|
{
|
|
*pNewLine = '\0';
|
|
}
|
|
|
|
// Convert the string; be sure to check return value
|
|
|
|
if (!MultiByteToWideChar(uCodePage, 0, lpszPassword, -1, lpwszPassword, MAX_PASSWORD_LENGTH))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
wprintf(L"Error: Multibyte to wide character conversion failed with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
wprintf(L"\n");
|
|
}
|
|
}
|
|
|
|
// Set the account information using ITask::SetAccountInformation
|
|
// This fails for Win9x, but we ignore the failure.
|
|
//
|
|
// Note that setting security is the LAST THING WE DO BEFORE SAVING
|
|
// THE TASK.
|
|
//
|
|
|
|
hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword);
|
|
if ((FAILED(hr)) && (hr != SCHED_E_NO_SECURITY_SERVICES))
|
|
{
|
|
wprintf(L"Error: Failed to set credentials on task object with 0x%x\n",hr);
|
|
goto finish;
|
|
}
|
|
|
|
// Make the changes permanent
|
|
// Requires using IPersistFile::Save()
|
|
|
|
if (! pIPF)
|
|
{
|
|
// Should not be in here if you got this far.
|
|
|
|
wprintf(L"You failed to get an IPersistFile interface, cannot save\n");
|
|
hr = E_FAIL;
|
|
goto finish;
|
|
}
|
|
|
|
hr = pIPF->Save(NULL, FALSE);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Error: Failed to save object with 0x%x\n", hr);
|
|
goto finish;
|
|
}
|
|
|
|
finish:
|
|
|
|
// We no longer need ITask
|
|
|
|
if (pITask)
|
|
{
|
|
pITask->Release();
|
|
pITask = NULL;
|
|
}
|
|
|
|
// Done with ITaskTrigger pointer
|
|
|
|
if (pITaskTrig)
|
|
{
|
|
pITaskTrig->Release();
|
|
pITaskTrig = NULL;
|
|
}
|
|
|
|
// Done with IPersistFile
|
|
|
|
if (pIPF)
|
|
{
|
|
pIPF->Release();
|
|
pIPF = NULL;
|
|
}
|
|
|
|
// If we have an IUnknown
|
|
|
|
if (pIU)
|
|
{
|
|
pIU->Release();
|
|
pIU = NULL;
|
|
}
|
|
|
|
// And if we have a IScheduledWorkItem
|
|
|
|
if (pISWI)
|
|
{
|
|
pISWI->Release();
|
|
pISWI = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: ConvertToSystemTime()
|
|
//
|
|
// Synopsis: Given a text string from the command line of the form
|
|
// HH:MM where HH = 0 - 23, and MM = 00 - 59,
|
|
// obtain the next instance in 24 hours of said time
|
|
// and return this time in a SYSTEMTIME structure
|
|
// so that it can be used to set triggers
|
|
//
|
|
// Arguments: lpwszTime - The text string of time
|
|
// lptTime - The SYSTEMTIME pointer
|
|
//
|
|
// Returns: HRESULT of success (S_OK) or failure
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT ConvertToSystemTime(LPWSTR lpwszTime, LPSYSTEMTIME lptTime)
|
|
{
|
|
WORD wMin = 0, wHr = 0, wMaxFeb = 28;
|
|
WCHAR szTemp[3], *szEnd1, *szEnd2;
|
|
SYSTEMTIME tNow;
|
|
int i, j;
|
|
|
|
// Extract minutes and hour from string
|
|
// Note that both h:mm and hh:mm are valid, but nothing else
|
|
// It is safe to call wcslen() on lpwszTime because we already know it is NULL terminated
|
|
|
|
if ((wcslen(lpwszTime) > MAX_TIME_LENGTH - 1) || (wcslen(lpwszTime) < MAX_TIME_LENGTH - 2))
|
|
{
|
|
wprintf(L"Error: Invalid time string given\n");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Hours first.
|
|
|
|
i = 0;
|
|
while (*lpwszTime != ':')
|
|
{
|
|
szTemp[i] = *lpwszTime;
|
|
i++;
|
|
if (i > 2)
|
|
{
|
|
// User gave a bad time string
|
|
wprintf(L"Error: Bad hours in time string (need 0 - 23)\n");
|
|
return E_FAIL;
|
|
}
|
|
lpwszTime++;
|
|
}
|
|
szTemp[i] = L'\0';
|
|
lpwszTime++;
|
|
|
|
// Convert to value
|
|
|
|
wHr = (WORD) wcstoul(szTemp, &szEnd1, 10);
|
|
|
|
// Now do minutes
|
|
// It is safe to call wcslen() on lpwszTime because we already know it is NULL terminated
|
|
|
|
i = wcslen(lpwszTime);
|
|
if (i != 2)
|
|
{
|
|
// User gave a bad time string
|
|
wprintf(L"Error: Bad minutes in time string (need 00 - 59)\n");
|
|
return E_FAIL;
|
|
}
|
|
|
|
j = 0;
|
|
while (i)
|
|
{
|
|
szTemp[j] = *lpwszTime;
|
|
j++;
|
|
i--;
|
|
lpwszTime++;
|
|
}
|
|
szTemp[j] = L'\0';
|
|
|
|
wMin = (WORD) wcstoul(szTemp, &szEnd2, 10);
|
|
|
|
// Now figure out if we are running today or tomorrow
|
|
|
|
GetLocalTime(&tNow);
|
|
if ((wHr < tNow.wHour) ||
|
|
((wHr == tNow.wHour) && (wMin < tNow.wMinute)))
|
|
{
|
|
// Job is tomorrow - we must figure out what tomorrow is
|
|
|
|
switch(tNow.wMonth)
|
|
{
|
|
case 4:
|
|
case 6:
|
|
case 9:
|
|
case 11:
|
|
// Thirty day months
|
|
|
|
if ((tNow.wDay + 1) > 30)
|
|
{
|
|
tNow.wDay = 1;
|
|
tNow.wMonth++;
|
|
}
|
|
else
|
|
{
|
|
tNow.wDay++;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
// February
|
|
// Leap Year?
|
|
|
|
if ((tNow.wYear % 4) == 0)
|
|
{
|
|
wMaxFeb = 29;
|
|
}
|
|
if (((tNow.wYear % 100) == 0) &&
|
|
((tNow.wYear % 400) != 0))
|
|
{
|
|
wMaxFeb = 28;
|
|
}
|
|
|
|
if ((tNow.wDay + 1) > wMaxFeb)
|
|
{
|
|
tNow.wDay = 1;
|
|
tNow.wMonth++;
|
|
}
|
|
else
|
|
{
|
|
tNow.wDay++;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// 31 day months. Handle Dec. later
|
|
|
|
if ((tNow.wDay + 1) > 31)
|
|
{
|
|
tNow.wDay = 1;
|
|
tNow.wMonth++;
|
|
}
|
|
else
|
|
{
|
|
tNow.wDay++;
|
|
}
|
|
|
|
if (tNow.wMonth > 12)
|
|
{
|
|
tNow.wMonth = 1;
|
|
tNow.wYear++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((wMin < 0) || (wMin > 59))
|
|
{
|
|
wprintf(L"Error: Invalid minutes (need 00 - 59)\n");
|
|
return E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
tNow.wMinute = wMin;
|
|
}
|
|
if ((wHr < 0) || (wHr > 23))
|
|
{
|
|
wprintf(L"Error: Invalid hours (need 0 - 23)\n");
|
|
return E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
tNow.wHour = wHr;
|
|
}
|
|
|
|
lptTime->wHour = tNow.wHour;
|
|
lptTime->wMinute = tNow.wMinute;
|
|
lptTime->wYear = tNow.wYear;
|
|
lptTime->wMonth = tNow.wMonth;
|
|
lptTime->wDay = tNow.wDay;
|
|
lptTime->wDayOfWeek = 0;
|
|
lptTime->wSecond = 0;
|
|
lptTime->wMilliseconds = 0;
|
|
return S_OK;
|
|
}
|