// =========================================================================== // 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 1997 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. // // 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 #include #include #include #include #include //+-------------------------------------------------------------------------- // 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); //+-------------------------------------------------------------------------- // // 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 _CRTAPI1 main (int argc, char **argv) { HRESULT hr = S_OK; UINT uCodePage; char *lpcszDeleteFlag = "/DELETE"; WCHAR lpwszJobName[255], lpwszTime[32]; WCHAR lpwszUserName[64], lpwszPassword[64], lpwszCommand[255]; WCHAR lpwszProgName[255]; SYSTEMTIME tTime; // For protection g_pITaskScheduler = NULL; // String conversion initialization uCodePage = _getmbcp(); MultiByteToWideChar(uCodePage, 0, argv[0], -1, lpwszProgName, 255); // 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\n"); return hr; } switch(argc) { case 1: // User would like to enumerate work items hr = EnumerateJobs(); break; case 3: if (! lstrcmpiA(argv[2], lpcszDeleteFlag)) { // User would like to delete work item in argv[1] MultiByteToWideChar(uCodePage, 0, argv[1], -1, lpwszJobName, 255); hr = DeleteJob(lpwszJobName); } else { // User has made an error in command line args wprintf(L"Error: Must be %s taskname.job /DELETE\n", lpwszProgName); wprintf(L"\n %s /? for more help.\n", lpwszProgName); Cleanup(); return 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. uCodePage = _getmbcp(); MultiByteToWideChar(uCodePage, 0, argv[1], -1, lpwszJobName, 255); MultiByteToWideChar(uCodePage, 0, argv[2], -1, lpwszTime, 32); hr = ConvertToSystemTime(lpwszTime, &tTime); if (FAILED(hr)) { wprintf(L"Error: Conversion of command line time to system time failed.\n"); Cleanup(); return hr; } *lpwszUserName = L'\0'; *lpwszPassword = L'\0'; MultiByteToWideChar(uCodePage, 0, argv[3], -1, lpwszCommand, 255); if (argc > 4) { MultiByteToWideChar(uCodePage, 0, argv[4], -1, lpwszUserName, 64); } if (argc == 6) { MultiByteToWideChar(uCodePage, 0, argv[5], -1, lpwszPassword, 64); } hr = AddJob(lpwszJobName, &tTime, lpwszCommand, lpwszUserName, lpwszPassword); 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)) { 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)) { 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) { 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\n"); 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\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.\n"); break; } // Display task name 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\n"); 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\n"); pITask->Release(); break; } 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; IPersistFile *pIPF; ITask *pITask; ITaskTrigger *pITaskTrig; DWORD dwTaskFlags, dwTrigFlags; WORD wTrigNumber; TASK_TRIGGER TaskTrig; WCHAR lpwszAppName[255]; int i; // 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 failure\n"); return hr; } // 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\n"); pIU->Release(); return hr; } // Cleanup pIUnknown, as we are done with it. pIU->Release(); pIU = NULL; // // 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 // hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword); if (hr != SCHED_E_NO_SECURITY_SERVICES) { // Check to see if username is null if (*lpwszPassword == L'\0') { // If password was null, chance username is, too. if (*lpwszUserName == L'\0') { wprintf(L"Enter username to run job %s as: ",lpwszJobName); wscanf(L"%s",lpwszUserName); wprintf(L"\n"); } wprintf(L"Enter password to user %s: ",lpwszUserName); wscanf(L"%s",lpwszPassword); wprintf(L"\n"); } } // Set the account information using ITask::SetAccountInformation // This fails for Win9x, but we ignore the failure. hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword); if ((FAILED(hr)) && (hr != SCHED_E_NO_SECURITY_SERVICES)) { wprintf(L"Error: Failed to set credentials on task object %x\n",hr); pITask->Release(); return hr; } // 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++; 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)\n"); pITask->Release(); return hr; } // Set task parameters with ITask::SetParameters hr = pITask->SetParameters(lpwszCommand); if (FAILED(hr)) { wprintf(L"Error: Failed to set parameters\n"); pITask->Release(); return hr; } // Set the comment, so we know how this job go there // Uses ITask::SetComment hr = pITask->SetComment(L"This scheduled task created by command line SDK sample tool"); if (FAILED(hr)) { wprintf(L"Error: Task comment could not be set\n"); pITask->Release(); return hr; } // Set the flags on the task object // Use ITask::SetFlags dwTaskFlags = TASK_FLAG_DELETE_WHEN_DONE; hr = pITask->SetFlags(dwTaskFlags); if (FAILED(hr)) { wprintf(L"Error: Could not set task flags\n"); pITask->Release(); return hr; } // 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: Could not create a new trigger\n"); pITask->Release(); return hr; } // Now, fill in the trigger as necessary. dwTrigFlags = 0; TaskTrig.cbTriggerSize = sizeof(TASK_TRIGGER); TaskTrig.Reserved1 = 0; TaskTrig.wBeginYear = lptTime->wYear; TaskTrig.wBeginMonth = lptTime->wMonth; TaskTrig.wBeginDay = lptTime->wDay; TaskTrig.wEndYear = 0; TaskTrig.wEndMonth = 0; TaskTrig.wEndDay = 0; TaskTrig.wStartHour = lptTime->wHour; TaskTrig.wStartMinute = lptTime->wMinute; TaskTrig.MinutesDuration = 0; TaskTrig.MinutesInterval = 0; TaskTrig.rgFlags = dwTrigFlags; TaskTrig.TriggerType = TASK_TIME_TRIGGER_ONCE; TaskTrig.wRandomMinutesInterval = 0; TaskTrig.Reserved2 = 0; // 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\n"); pITaskTrig->Release(); pITask->Release(); return hr; } // Make the changes permanent // Requires using IPersistFile::Save() hr = pITask->QueryInterface(IID_IPersistFile, (void **) &pIPF); if (FAILED(hr)) { wprintf(L"Error: Could not get IPersistFile on task\n"); pITaskTrig->Release(); pITask->Release(); return hr; } hr = pIPF->Save(NULL, FALSE); if (FAILED(hr)) { wprintf(L"Error: Could not save object\n"); pITaskTrig->Release(); pITask->Release(); pIPF->Release(); return hr; } // We no longer need ITask pITask->Release(); pITask = NULL; // Done with ITaskTrigger pointer pITaskTrig->Release(); pITaskTrig = NULL; // Done with IPersistFile pIPF->Release(); pIPF = NULL; return hr; } //+-------------------------------------------------------------------------- // // Function: ConvertToSystemTime() // // Synopsis: Given a text string from the command line of the form // XX:YY where XX = 0 - 23, and YY = 0 - 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 if ((wcslen(lpwszTime) > 5) || (wcslen(lpwszTime) < 4)) { 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\n"); return E_FAIL; } lpwszTime++; } szTemp[i] = L'\0'; lpwszTime++; // Convert to value wHr = (WORD) wcstoul(szTemp, &szEnd1, 10); // Now do minutes i = wcslen(lpwszTime); 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 0 - 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; }