//+---------------------------------------------------------------------------- // // Job Scheduler Service // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1996. // // File: sch_itf.cxx // // Contents: job scheduler service interface impementation // // Classes: CSchedule // // Interfaces: ITaskScheduler // // History: 08-Sep-95 EricB created // //----------------------------------------------------------------------------- #include "..\pch\headers.hxx" #pragma hdrstop #include "Sched.hxx" #include "..\inc\misc.hxx" #include "..\inc\network.hxx" //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::GetTargetComputer, public // // Synopsis: Returns the name of the machine towards which the interface is // currently targetted. // // Arguments: [ppwszComputer] - the returned buffer with the machine name // // Returns: hresults // // Notes: The string is callee allocated and caller freed with // CoTaskMemFree. //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::GetTargetComputer(LPWSTR * ppwszComputer) { TRACE(CSchedule, GetTargetComputer); HRESULT hr; DWORD cch = SA_MAX_COMPUTERNAME_LENGTH + 1; TCHAR tszLocalName[SA_MAX_COMPUTERNAME_LENGTH + 3] = TEXT("\\\\"); TCHAR * ptszTargetMachine; WCHAR * pwszTargetMachine; if (m_ptszTargetMachine) { ptszTargetMachine = m_ptszTargetMachine; cch = lstrlen(ptszTargetMachine) + 1; } else // A NULL m_ptszTargetMachine means that we are targetted locally { if (!GetComputerName(tszLocalName + 2, &cch)) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("GetTargetComputer: GetComputerName", hr); return hr; } ptszTargetMachine = tszLocalName; cch += 3; // 2 for the leading slashes + 1 for the NULL } pwszTargetMachine = ptszTargetMachine; *ppwszComputer = (LPWSTR)CoTaskMemAlloc(cch * sizeof(WCHAR)); if (*ppwszComputer == NULL) { return E_OUTOFMEMORY; } s_wcscpy(*ppwszComputer, pwszTargetMachine); return S_OK; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::SetTargetComputer, public // // Synopsis: Sets the machine towards which subsequent ITaskScheduler // calls will be directed // // Arguments: [pwszComputer] - the machine name string // // Returns: hresults // // Notes: The string is Caller allocated and freed. The machine name // must include two leading backslashes. // The caller may indicate using the local machine in one of two // ways: by setting pwszComputer to NULL or to the UNC name of the // local machine. //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::SetTargetComputer(LPCWSTR pwszComputer) { TRACE(CSchedule, SetTargetComputer); HRESULT hr; DWORD cch; BOOL fLocal = FALSE; // // Parameter validation. A null param means to target the local computer. // if (!pwszComputer) { fLocal = TRUE; } LPCTSTR tszPassedInName = pwszComputer; if (!fLocal) { // // Get the local machine name to compare with that passed in. // TCHAR tszLocalName[SA_MAX_COMPUTERNAME_LENGTH + 1]; cch = SA_MAX_COMPUTERNAME_LENGTH + 1; if (!GetComputerName(tszLocalName, &cch)) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("SetTargetComputer: GetComputerName", hr); return hr; } TCHAR tszFQDN[SA_MAX_COMPUTERNAME_LENGTH + 1]; cch = SA_MAX_COMPUTERNAME_LENGTH + 1; if (!GetComputerNameEx(ComputerNamePhysicalDnsFullyQualified, tszFQDN, &cch)) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("SetTargetComputer: GetComputerNameEx", hr); return hr; } // // skip over first two characters ("\\") of tszPassedInName when comparing // fLocal = (lstrcmpi(tszPassedInName + 2, tszLocalName) == 0) || (lstrcmpi(tszPassedInName + 2, tszFQDN) == 0); } // // If targeted remotely, get the folder path out of that machine's // registry. // TCHAR tszFolderPath[MAX_PATH]; if (!fLocal) { // // Open the remote registry. // long lErr; HKEY hRemoteKey, hSchedKey; lErr = RegConnectRegistry(tszPassedInName, HKEY_LOCAL_MACHINE, &hRemoteKey); if (lErr != ERROR_SUCCESS) { schDebugOut((DEB_ERROR, "SetTargetComputer: RegConnectRegistry " "failed with error %ld\n", lErr)); return(HRESULT_FROM_WIN32(lErr)); } lErr = RegOpenKeyEx(hRemoteKey, SCH_AGENT_KEY, 0, KEY_READ, &hSchedKey); if (lErr != ERROR_SUCCESS) { RegCloseKey(hRemoteKey); if (lErr == ERROR_BADKEY || lErr == ERROR_FILE_NOT_FOUND) { return SCHED_E_SERVICE_NOT_INSTALLED; } schDebugOut((DEB_ERROR, "SetTargetComputer: RegOpenKeyEx " "of Scheduler key failed with error %ld\n", lErr)); return HRESULT_FROM_WIN32(lErr); } // // Get the jobs folder location from the remote registry. // DWORD cb = MAX_PATH * sizeof(TCHAR); TCHAR tszRegFolderValue[MAX_PATH]; lErr = RegQueryValueEx(hSchedKey, SCH_FOLDER_VALUE, NULL, NULL, (LPBYTE)tszRegFolderValue, &cb); if (lErr != ERROR_SUCCESS) { // use default if value absent lstrcpy(tszRegFolderValue, TEXT("%SystemRoot%\\Tasks")); } RegCloseKey(hSchedKey); // // BUGBUG: temporary code to expand %SystemRoot% or %WinDir% // The installer will have to write a full path to the registry 'cause // expanding arbitrary environment strings remotely is too much work. // cch = ARRAY_LEN("%SystemRoot%") - 1; if (_tcsncicmp(tszRegFolderValue, TEXT("%SystemRoot%"), cch) != 0) { cch = ARRAY_LEN("%WinDir%") - 1; if (_tcsncicmp(tszRegFolderValue, TEXT("%WinDir%"), cch) != 0) { cch = 0; } } if (cch != 0) { HKEY hCurVerKey; lErr = RegOpenKeyEx(hRemoteKey, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_ALL_ACCESS, &hCurVerKey); if (lErr != ERROR_SUCCESS) { RegCloseKey(hRemoteKey); schDebugOut((DEB_ERROR, "SetTargetComputer: RegOpenKeyEx " "of CurrentVersion key failed with error %ld\n", lErr)); return HRESULT_FROM_WIN32(lErr); } TCHAR tszSystemRoot[MAX_PATH]; cb = MAX_PATH * sizeof(TCHAR); lErr = RegQueryValueEx(hCurVerKey, TEXT("SystemRoot"), NULL, NULL, (LPBYTE)tszSystemRoot, &cb); if (lErr != ERROR_SUCCESS) { RegCloseKey(hCurVerKey); RegCloseKey(hRemoteKey); schDebugOut((DEB_ERROR, "SetTargetComputer: RegQueryValueEx " "of CurrentVersion key failed with error %ld\n", lErr)); return HRESULT_FROM_WIN32(lErr); } RegCloseKey(hCurVerKey); lstrcpy(tszFolderPath, tszSystemRoot); lstrcat(tszFolderPath, tszRegFolderValue + cch); } else { lstrcpy(tszFolderPath, tszRegFolderValue); } // // end of temporary code to expand %SystemRoot% // RegCloseKey(hRemoteKey); // // Check the folder path for being a fully qualified path name where // the first char is the drive designator and the second char is a // colon. // if (!s_isDriveLetter(tszFolderPath[0]) || tszFolderPath[1] != TEXT(':')) { ERR_OUT("SetTargetComputer: registry path", ERROR_BAD_PATHNAME); return HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME); } // // The UNC path to the job folder will be the result of concatonating // the machine name and the expanded folder path. The drive designator // in the folder path will be turned in an administrative share name // by replacing the colon with a dollar sign and will look like: // \\machine\c$\windir\jobs // so that the count below includes the slash trailing the machine name // plus the terminating null. // cch = lstrlen(tszPassedInName) + 1 + lstrlen(tszFolderPath) + 1; } else // Targetted locally. { // // Use the local path. Include one for the null terminator. // cch = lstrlen(g_TasksFolderInfo.ptszPath) + 1; } // // Allocate the ITaskScheduler folder path string buffer. // TCHAR * ptszPathBuf = new TCHAR[cch]; if (!ptszPathBuf) { ERR_OUT("SetTargetComputer: Job folder path buffer allocation", E_OUTOFMEMORY); return E_OUTOFMEMORY; } // // Allocate the ITaskScheduler machine name string buffer. // TCHAR * ptszTargetMachine; if (!fLocal) { cch = lstrlen(tszPassedInName) + 1; ptszTargetMachine = new TCHAR[cch]; if (!ptszTargetMachine) { ERR_OUT("CSchedule::SetTargetComputer", E_OUTOFMEMORY); delete ptszPathBuf; return E_OUTOFMEMORY; } } // // Now that all failable operation have completed sucessfully, we can // update the machine name and folder path members. // if (m_ptszTargetMachine) { delete m_ptszTargetMachine; } if (m_ptszFolderPath) { delete m_ptszFolderPath; } // // Save the new machine name. // if (fLocal) { // // If we are targetted locally, the machine name member is set to // NULL. // m_ptszTargetMachine = NULL; } else { m_ptszTargetMachine = ptszTargetMachine; lstrcpy(m_ptszTargetMachine, tszPassedInName); } // // Save the folder path name. // m_ptszFolderPath = ptszPathBuf; if (fLocal) { lstrcpy(m_ptszFolderPath, g_TasksFolderInfo.ptszPath); } else { // // Convert the folder location to an UNC path. // // Turn the drive designator into the admin share by replacing the // colon with the dollar sign. // tszFolderPath[1] = TEXT('$'); // // Compose the UNC path. // lstrcpy(m_ptszFolderPath, tszPassedInName); lstrcat(m_ptszFolderPath, TEXT("\\")); lstrcat(m_ptszFolderPath, tszFolderPath); } schDebugOut((DEB_ITRACE, "SetTargetComputer: path to sched folder: \"" FMT_TSTR "\"\n", m_ptszFolderPath)); return S_OK; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::Enum, public // // Synopsis: Returns a job/queue object enumerator. // // Arguments: [ppEnumJobs] - a place to return a pointer to the enumerator // // Returns: hresults // //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::Enum(IEnumWorkItems ** ppEnumJobs) { TRACE(CSchedule, Enum); HRESULT hr; CEnumJobs * pEnumJobs = new CEnumJobs; if (pEnumJobs == NULL) { *ppEnumJobs = NULL; return E_OUTOFMEMORY; } hr = pEnumJobs->Init(m_ptszFolderPath); if (FAILED(hr)) { delete pEnumJobs; *ppEnumJobs = NULL; } *ppEnumJobs = pEnumJobs; return hr; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::NewWorkItem, public // // Synopsis: Create a new job object. // // Arguments: [pwszJobName] - the name of the new job *REQUIRED* // [riid] - the interface desired // [ppunk] - a place to return a pointer to the new job object // // Returns: hresults // // Notes: ppwszJobName is caller allocated and freed. The CJob::Save // method will copy it before returning. The job name must conform // to NT file naming conventions but must not include // [back]slashes because nesting within the job object folder is // not allowed. //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::NewWorkItem(LPCWSTR pwszJobName, REFCLSID rclsid, REFIID riid, IUnknown ** ppunk) { TRACE(CSchedule, NewWorkItem); *ppunk = NULL; if (!IsEqualCLSID(rclsid, CLSID_CTask)) { return CLASS_E_CLASSNOTAVAILABLE; } TCHAR * ptszFullName; HANDLE hFile; HRESULT hr = CheckJobName(pwszJobName, &ptszFullName); if (FAILED(hr)) { ERR_OUT("CSchedule::NewWorkItem: CheckJobName", hr); return hr; } CJob * pJob = CJob::Create(); if (pJob == NULL) { delete [] ptszFullName; return E_OUTOFMEMORY; } // // Do the QI before the CreateFile so that if the caller asks for a non- // supported interface, the failure will not result in disk operations. // hr = pJob->QueryInterface(riid, (void **)ppunk); if (FAILED(hr)) { ERR_OUT("CSchedule::NewWorkItem: QueryInterface(riid)", hr); goto CleanExit; } // the above QI increased the refcount to 2, so set it back to 1 pJob->Release(); // // Per the spec for this method, the file must not already exist. // hFile = CreateFile(ptszFullName, 0, // desired access: none FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode: all NULL, // security attributes OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD dwErr = GetLastError(); if (dwErr == ERROR_FILE_NOT_FOUND) { // // This is good. Save the new filename. // pJob->m_ptszFileName = ptszFullName; return S_OK; } else { hr = HRESULT_FROM_WIN32(dwErr); ERR_OUT("CSchedule::NewWorkItem: CreateFile", hr); } } else { // // Opened successfully - the file exists // CloseHandle(hFile); hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS); ERR_OUT("CSchedule::NewWorkItem", hr); } CleanExit: delete [] ptszFullName; delete pJob; // on error, completely destroy the job object *ppunk = NULL; return hr; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::AddWorkItem, public // // Synopsis: Saves the job to the job scheduler folder. // // Arguments: [pwszJobName] - the name of the job *REQUIRED* // [pJob] - pointer to the job object // // Returns: hresults // // Notes: Same job name conditions as above. //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::AddWorkItem(LPCWSTR pwszJobName, IScheduledWorkItem * pWorkItem) { TRACE(CSchedule, AddWorkItem); TCHAR * ptszFullName; HRESULT hr = CheckJobName(pwszJobName, &ptszFullName); if (FAILED(hr)) { ERR_OUT("CSchedule::AddWorkItem: CheckJobName", hr); return hr; } IPersistFile * pFile; hr = pWorkItem->QueryInterface(IID_IPersistFile, (void **)&pFile); if (FAILED(hr)) { ERR_OUT("CSchedule::AddWorkItem: QI(IPersistFile)", hr); delete [] ptszFullName; return hr; } WCHAR * pwszName; #if !defined(UNICODE) int cch = lstrlen(ptszFullName) + 1; pwszName = new WCHAR[cch]; if (!pwszName) { ERR_OUT("CSchedule::AddWorkItem", E_OUTOFMEMORY); pFile->Release(); delete [] ptszFullName; return E_OUTOFMEMORY; } MultiByteToWideChar(CP_ACP, 0, ptszFullName, -1, pwszName, cch); #else pwszName = ptszFullName; #endif hr = pFile->Save(pwszName, TRUE); // Add this if we want nested folders // // if (hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) // { // // // // Create folders as needed // // // hr = CreateFolders(ptszFullName, TRUE); // if (FAILED(hr)) // { // ERR_OUT("AddWorkItem: CreateFolders", hr); // pFile->Release(); // delete [] ptszFullName; // #if !defined(UNICODE) // delete [] pwszName; // #endif // return hr; // } // // // // Try again // // // hr = pFile->Save(pwszName, TRUE); // } pFile->Release(); delete [] ptszFullName; #if !defined(UNICODE) delete [] pwszName; #endif return hr; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::Delete, public // // Synopsis: Deletes the job/queue. // // Arguments: [pwszJobName] - indicates the job/queue to delete // // Returns: hresults // //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::Delete(LPCWSTR pwszJobName) { TRACE(CSchedule, Delete); HRESULT hr; TCHAR * ptszFullName; hr = CheckJobName(pwszJobName, &ptszFullName); if (FAILED(hr)) { ERR_OUT("CSchedule::Delete: CheckJobName", hr); return hr; } if (!DeleteFile(ptszFullName)) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CSchedule::Delete: DeleteFile", hr); delete ptszFullName; return hr; } delete ptszFullName; return S_OK; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::Activate, public // // Synopsis: Given a valid name, returns a pointer to the activated job // object // // Arguments: [pwszName] - the name of the job to activate // [riid] - the interface to return // [ppunk] - a pointer to the job object interface // // Returns: hresults // //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::Activate(LPCWSTR pwszName, REFIID riid, IUnknown ** ppunk) { TRACE(CSchedule, Activate); TCHAR * ptszFullName; HRESULT hr = CheckJobName(pwszName, &ptszFullName); if (FAILED(hr)) { *ppunk = NULL; return hr; } CJob * pJob; // // CJob is a single-use, in-proc handler, so no need to get OLE in the // loop here. Use new (called by CJob::Create) instead of CoCreateInstance. // pJob = CJob::Create(); if (pJob == NULL) { *ppunk = NULL; delete [] ptszFullName; return E_OUTOFMEMORY; } hr = pJob->LoadP(ptszFullName, 0, TRUE, TRUE); delete [] ptszFullName; if (FAILED(hr)) { ERR_OUT("CSchedule::Activate, Load", hr); *ppunk = NULL; pJob->Release(); // on error, completely release the job object return hr; } hr = pJob->QueryInterface(riid, (void **)ppunk); if (FAILED(hr)) { ERR_OUT("CSchedule::Activate: QueryInterface(riid)", hr); *ppunk = NULL; pJob->Release(); // on error, completely release the job object return hr; } // // The above QI increased the refcount to 2, so set it back to 1. // pJob->Release(); return S_OK; } //+---------------------------------------------------------------------------- // // Member: CSchedule::ITaskScheduler::IsOfType, public // // Synopsis: Does this object support the desired interface? // // Arguments: [pwszName] - indicates the object name // [riid] - indicates the interface of interest, typically // IID_ITask or IID_IScheduledQueue // // Returns: S_OK if it is, S_FALSE otherwise. // //----------------------------------------------------------------------------- STDMETHODIMP CSchedule::IsOfType(LPCWSTR pwszName, REFIID riid) { TRACE(CSchedule, IsOfType); // CODEWORK: A heavyweight implementation for now. It could possibly // be optimized by doing the QueryInterface before the LoadP, and // doing a lightweight LoadP. IUnknown * punk; HRESULT hr = Activate(pwszName, riid, &punk); if (SUCCEEDED(hr)) { punk->Release(); hr = S_OK; } else { if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_DATA) || hr == SCHED_E_UNKNOWN_OBJECT_VERSION || hr == E_NOINTERFACE) { // // These errors mean that the object is definitely not of a // type that we support. We translate them to S_FALSE. // Other errors could include file-not-found, access-denied, // invalid-arg, etc. We return those errors unmodified. // hr = S_FALSE; } } return hr; }