diff --git a/MSICALib/MSI/Makefile b/MSICALib/MSI/Makefile index 743bb97..996c473 100644 --- a/MSICALib/MSI/Makefile +++ b/MSICALib/MSI/Makefile @@ -66,7 +66,7 @@ Vse :: "$(JEZIK).$(CFG).$(PLAT).ActionText-2.idt" Action Description Template s$(MSI_TIP_ID) L0 L0 1250 ActionText Action -caInstallScheduledTasks Registracija razporejenih opravil Opravilo: [1] +InstallScheduledTasks Registracija razporejenih opravil Opravilo: [1] <= INSTALLSTATE_LOCAL) { - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 3, fSequence, sValue)); // Application - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 4, fSequence, sValue)); // Parameters - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 5, fSequence, sValue)); // WorkingDir - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger( hRecord, 6, FALSE, fSequence )); // Flags - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger( hRecord, 7, FALSE, fSequence )); // Priority - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 8, fSequence, sValue)); // User - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 9, fSequence, sValue)); // Password - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 10, fSequence, sValue)); // Description - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger( hRecord, 11, TRUE, fSequence )); // IdleMin - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger( hRecord, 12, TRUE, fSequence )); // IdleDeadline - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger( hRecord, 13, FALSE, fSequence )); // MaxRuntime - if (FAILED(hr)) break; + // Get task's DisplayName. + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 2, sDisplayName); - // Write 0 for the number of triggers and store file position to update this count later. - verify(SUCCEEDED(fSequence.GetPosition(posTriggerCount))); - hr = fSequence << (int)nTriggers; - if (FAILED(hr)) break; + if (iAction >= INSTALLSTATE_LOCAL) { + // Installing component. Add the task. + PMSIHANDLE hViewTT; + CMSITSCAOpCreateTask *opCreateTask = new CMSITSCAOpCreateTask(sDisplayName); + assert(opCreateTask); - // Perform another query to get task's triggers. - hr = HRESULT_FROM_WIN32(::MsiDatabaseOpenView(hDatabase, _T("SELECT Trigger,BeginDate,EndDate,StartTime,StartTimeRand,MinutesDuration,MinutesInterval,Flags,Type,DaysInterval,WeeksInterval,DaysOfTheWeek,DaysOfMonth,WeekOfMonth,MonthsOfYear FROM TaskTrigger WHERE Task_=?"), &hViewTT)); - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiViewExecute(hViewTT, hRecord)); - if (FAILED(hr)) break; + // Populate the operation with task's data. + opCreateTask->SetFromRecord(hInstall, hRecord); - for (;; nTriggers++) { - PMSIHANDLE hRecord; - int iStartTime, iStartTimeRand; + // Perform another query to get task's triggers. + uiResult = ::MsiDatabaseOpenView(hDatabase, _T("SELECT Trigger,BeginDate,EndDate,StartTime,StartTimeRand,MinutesDuration,MinutesInterval,Flags,Type,DaysInterval,WeeksInterval,DaysOfTheWeek,DaysOfMonth,WeekOfMonth,MonthsOfYear FROM TaskTrigger WHERE Task_=?"), &hViewTT); + if (uiResult != ERROR_SUCCESS) break; - // Fetch one record from the view. - hr = HRESULT_FROM_WIN32(::MsiViewFetch(hViewTT, &hRecord)); - if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { - hr = S_OK; - break; - } else if (FAILED(hr)) - break; + // Execute query! + uiResult = ::MsiViewExecute(hViewTT, hRecord); + if (uiResult == ERROR_SUCCESS) { + // Populate trigger list. + uiResult = opCreateTask->SetTriggersFromView(hViewTT); + verify(::MsiViewClose(hViewTT) == ERROR_SUCCESS); + if (uiResult != ERROR_SUCCESS) break; + } else + break; - // Read StartTime and StartTimeRand - iStartTime = ::MsiRecordGetInteger(hRecord, 4); - if (iStartTime == MSI_NULL_INTEGER) { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_FIELD); - break; + olExecute.AddTail(opCreateTask); + } else { + // Removing component. Remove the task. + olExecute.AddTail(new CMSITSCAOpDeleteTask(sDisplayName)); + } + + // The amount of tick space to add for each task to progress indicator. + verify(::MsiRecordSetInteger(hRecordProg, 1, 3 ) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 2, MSITSCA_TASK_TICK_SIZE) == ERROR_SUCCESS); + if (::MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) { uiResult = ERROR_INSTALL_USEREXIT; break; } + } + verify(::MsiViewClose(hViewST) == ERROR_SUCCESS); + + if (SUCCEEDED(uiResult)) { + CString sSequenceFilename; + CAtlFile fSequence; + + // Prepare our own sequence script file. + // The InstallScheduledTasks is a deferred custom action, thus all this information will be unavailable to it. + // Therefore save all required info to file now. + { + LPTSTR szBuffer = sSequenceFilename.GetBuffer(MAX_PATH); + assert(szBuffer); + ::GetTempPath(MAX_PATH, szBuffer); + ::GetTempFileName(szBuffer, _T("TS"), 0, szBuffer); + sSequenceFilename.ReleaseBuffer(); + } + // Save execute sequence to file. + hr = olExecute.SaveToFile(sSequenceFilename); + if (SUCCEEDED(hr)) { + // Store sequence script file names to properties for deferred custiom actions. + uiResult = ::MsiSetProperty(hInstall, _T("InstallScheduledTasks"), sSequenceFilename); + if (uiResult == ERROR_SUCCESS) { + LPCTSTR pszExtension = ::PathFindExtension(sSequenceFilename); + CString sSequenceFilename2; + + sSequenceFilename2.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); + uiResult = ::MsiSetProperty(hInstall, _T("RollbackScheduledTasks"), sSequenceFilename2); + if (uiResult == ERROR_SUCCESS) { + sSequenceFilename2.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); + uiResult = ::MsiSetProperty(hInstall, _T("CommitScheduledTasks"), sSequenceFilename2); + if (uiResult != ERROR_SUCCESS) { + verify(::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_SCHEDULED_TASKS_PROPERTY_SET) == ERROR_SUCCESS); + verify(::MsiRecordSetString (hRecordProg, 2, _T("CommitScheduledTasks") ) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 3, uiResult ) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + } else { + verify(::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_SCHEDULED_TASKS_PROPERTY_SET) == ERROR_SUCCESS); + verify(::MsiRecordSetString (hRecordProg, 2, _T("RollbackScheduledTasks") ) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 3, uiResult ) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + } else { + verify(::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_SCHEDULED_TASKS_PROPERTY_SET) == ERROR_SUCCESS); + verify(::MsiRecordSetString (hRecordProg, 2, _T("InstallScheduledTasks") ) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 3, uiResult ) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + if (uiResult != ERROR_SUCCESS) ::DeleteFile(sSequenceFilename); + } else { + uiResult = ERROR_INSTALL_SCHEDULED_TASKS_SCRIPT_WRITE; + verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult ) == ERROR_SUCCESS); + verify(::MsiRecordSetString (hRecordProg, 2, sSequenceFilename) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 3, hr ) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + } else if (uiResult != ERROR_INSTALL_USEREXIT) { + verify(::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_SCHEDULED_TASKS_OPLIST_CREATE) == ERROR_SUCCESS); + verify(::MsiRecordSetInteger(hRecordProg, 2, uiResult ) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + } else { + verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); } - iStartTimeRand = ::MsiRecordGetInteger(hRecord, 5); - if (iStartTimeRand != MSI_NULL_INTEGER) { - // Add random delay to StartTime. - iStartTime += ::MulDiv(rand(), iStartTimeRand, RAND_MAX); - } - - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 2, FALSE, fSequence)); // BeginDate - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 3, TRUE, fSequence)); // EndDate - if (FAILED(hr)) break; - hr = fSequence << iStartTime; // StartTime - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 6, TRUE, fSequence)); // MinutesDuration - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 7, TRUE, fSequence)); // MinutesInterval - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 8, FALSE, fSequence)); // Flags - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 9, FALSE, fSequence)); // Type - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 10, TRUE, fSequence)); // DaysInterval - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 11, TRUE, fSequence)); // WeeksInterval - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 12, TRUE, fSequence)); // DaysOfTheWeek - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 13, TRUE, fSequence)); // DaysOfMonth - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 14, TRUE, fSequence)); // WeekOfMonth - if (FAILED(hr)) break; - hr = HRESULT_FROM_WIN32(::MsiStoreInteger(hRecord, 15, TRUE, fSequence)); // MonthsOfYear - if (FAILED(hr)) break; + } else { + verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); } - - verify(::MsiViewClose(hViewTT) == ERROR_SUCCESS); - if (FAILED(hr)) break; - - // Seek back to update actual trigger count. - verify(SUCCEEDED(fSequence.GetPosition(posEOF))); - verify(SUCCEEDED(fSequence.Seek(posTriggerCount, FILE_BEGIN))); - fSequence << nTriggers; - verify(SUCCEEDED(fSequence.Seek(posEOF, FILE_BEGIN))); } - - if (::MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) { hr = E_ABORT; goto error2; } + } else { + uiResult = ERROR_INSTALL_SCHEDULED_TASKS_DATABASE_OPEN; + verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult) == ERROR_SUCCESS); + ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); } - verify(::MsiViewClose(hViewST) == ERROR_SUCCESS); - if (FAILED(hr)) goto error2; - - // Store sequence script file names for deferred custiom actions. - hr = HRESULT_FROM_WIN32(::MsiSetProperty(hInstall, _T("caInstallScheduledTasks"), sSequenceFilename)); - if (FAILED(hr)) goto error2; - { - LPCTSTR pszExtension = ::PathFindExtension(sSequenceFilename); - CString sSequenceFilename2; - sSequenceFilename2.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); - hr = HRESULT_FROM_WIN32(::MsiSetProperty(hInstall, _T("caRollbackScheduledTasks"), sSequenceFilename2)); - if (FAILED(hr)) goto error2; - sSequenceFilename2.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); - hr = HRESULT_FROM_WIN32(::MsiSetProperty(hInstall, _T("caCommitScheduledTasks"), sSequenceFilename2)); - if (FAILED(hr)) goto error2; - } - - hr = S_OK; - -error2: - fSequence.Close(); -error1: - if (FAILED(hr)) ::DeleteFile(sSequenceFilename); + olExecute.Free(); if (bIsCoInitialized) ::CoUninitialize(); - return SUCCEEDED(hr) ? ERROR_SUCCESS : hr == E_ABORT ? ERROR_INSTALL_USEREXIT : ERROR_INVALID_DATA; + return uiResult; } UINT MSITSCA_API InstallScheduledTasks(MSIHANDLE hInstall) { - assert(::MsiGetMode(hInstall, MSIRUNMODE_SCHEDULED)); - + UINT uiResult; HRESULT hr; BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL)); - CString sSequenceFilename, sSequenceFilenameRB, sSequenceFilenameCM; - LPCTSTR pszExtension; - CStringW sDisplayName; - CAtlFile fSequence, fSequence2; - CComPtr pTaskScheduler; - CMSITSCAOperationList lstOperationsRB, lstOperationsCM; - PMSIHANDLE hRecordAction = ::MsiCreateRecord(3), hRecordProg = ::MsiCreateRecord(3); - BOOL bRollbackEnabled; - - assert(hRecordProg); + CString sSequenceFilename; assert(0); // Attach debugger here, or press "Ignore"! - // Tell the installer to use explicit progress messages. - verify(::MsiRecordSetInteger(hRecordProg, 1, 1) == ERROR_SUCCESS); - verify(::MsiRecordSetInteger(hRecordProg, 2, 1) == ERROR_SUCCESS); - verify(::MsiRecordSetInteger(hRecordProg, 3, 0) == ERROR_SUCCESS); - if (::MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) { hr = E_ABORT; goto error1; } + uiResult = ::MsiGetProperty(hInstall, _T("CustomActionData"), sSequenceFilename); + if (uiResult == ERROR_SUCCESS) { + CMSITSCAOpList lstOperations; - // Specify that an update of the progress bar's position in this case means to move it forward by one increment. - verify(::MsiRecordSetInteger(hRecordProg, 1, 2) == ERROR_SUCCESS); - verify(::MsiRecordSetInteger(hRecordProg, 2, MSITSCA_TASK_TICK_SIZE) == ERROR_SUCCESS); - verify(::MsiRecordSetInteger(hRecordProg, 3, 0) == ERROR_SUCCESS); + // Load operation sequence. + hr = lstOperations.LoadFromFile(sSequenceFilename); + if (SUCCEEDED(hr)) { + CMSITSCASession session; + BOOL bIsCleanup = ::MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || ::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK); - // Determine sequence script file names. - hr = HRESULT_FROM_WIN32(::MsiGetProperty(hInstall, _T("CustomActionData"), sSequenceFilename)); - if (FAILED(hr)) goto error1; - pszExtension = ::PathFindExtension(sSequenceFilename); - assert(pszExtension); - sSequenceFilenameRB.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); - sSequenceFilenameCM.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); + // In case of commit/rollback, continue sequence on error, to do as much cleanup as possible. + session.m_bContinueOnError = bIsCleanup; - // Open sequence script file. - hr = fSequence.Create(sSequenceFilename, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); - if (FAILED(hr)) goto error2; + // Get task scheduler object. + hr = session.m_pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); + if (SUCCEEDED(hr)) { + // Execute the operations. + hr = lstOperations.Execute(&session); + if (SUCCEEDED(hr)) { + if (!bIsCleanup && session.m_bRollbackEnabled) { + // Save cleanup scripts. + LPCTSTR pszExtension = ::PathFindExtension(sSequenceFilename); + CString sSequenceFilenameCM, sSequenceFilenameRB; - // Get rollback-enabled state. - { - int iValue; - hr = fSequence >> iValue; - if (FAILED(hr)) goto error3; - bRollbackEnabled = iValue ? TRUE : FALSE; - } + sSequenceFilenameRB.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); + sSequenceFilenameCM.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); - hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); - if (FAILED(hr)) goto error3; + // After end of commit, delete rollback file too. After end of rollback, delete commit file too. + session.m_olCommit.AddTail(new CMSITSCAOpDeleteFile(sSequenceFilenameRB)); + session.m_olRollback.AddTail(new CMSITSCAOpDeleteFile(sSequenceFilenameCM)); - for (;;) { - INSTALLSTATE iInstalled, iAction; - CComPtr pTaskOrig; - - hr = fSequence >> (int&)iInstalled; - if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) { - hr = S_OK; - break; - } else if (FAILED(hr)) goto error4; - hr = fSequence >> (int&)iAction; - if (FAILED(hr)) goto error4; - hr = fSequence >> sDisplayName; - if (FAILED(hr)) goto error4; - - verify(::MsiRecordSetString(hRecordAction, 1, sDisplayName) == ERROR_SUCCESS); - if (::MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecordAction) == IDCANCEL) { hr = E_ABORT; goto error1; } - - // See if the task with this name already exists. - hr = pTaskScheduler->Activate(sDisplayName, IID_ITask, (IUnknown**)&pTaskOrig); - if (hr != COR_E_FILENOTFOUND && FAILED(hr)) goto error4; - else if (SUCCEEDED(hr)) { - // The task exists. - if (iInstalled < INSTALLSTATE_LOCAL || iAction < INSTALLSTATE_LOCAL) { - // This component either was, or is going to be uninstalled. - // In first case this task shouldn't exist. Perhaps it is from some failed previous install/uninstall attempt. - // In second case we should delete it. - // So delete it anyway! - DWORD dwFlags; - CStringW sDisplayNameOrig; - UINT uiCount = 0; - - if (bRollbackEnabled) { - hr = pTaskOrig->GetFlags(&dwFlags); - if (FAILED(hr)) goto error4; - if ((dwFlags & TASK_FLAG_DISABLED) == 0) { - // The task is enabled. - - // In case the task disabling fails halfway, try to re-enable it anyway on rollback. - lstOperationsRB.AddHead(new CMSITSCAOpEnableTask(sDisplayName, TRUE)); - - // Disable it. - dwFlags |= TASK_FLAG_DISABLED; - hr = pTaskOrig->SetFlags(dwFlags); - if (FAILED(hr)) goto error4; - } - - // Prepare a backup copy of task. - do { - sDisplayNameOrig.Format(L"%ls (orig %u)", (LPCWSTR)sDisplayName, ++uiCount); - hr = pTaskScheduler->AddWorkItem(sDisplayNameOrig, pTaskOrig); - } while (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS)); - // In case the backup copy creation failed halfway, try to delete its remains anyway on rollback. - lstOperationsRB.AddHead(new CMSITSCAOpDeleteTask(sDisplayNameOrig)); - if (FAILED(hr)) goto error4; - - // Save the backup copy. - CComQIPtr pTaskFile(pTaskOrig); - hr = pTaskFile->Save(NULL, TRUE); - if (FAILED(hr)) goto error4; - - // Order rollback action to restore from backup copy. - lstOperationsRB.AddHead(new CMSITSCAOpCopyTask(sDisplayNameOrig, sDisplayName)); + // Save commit file first. + hr = session.m_olCommit.SaveToFile(sSequenceFilenameCM); + if (SUCCEEDED(hr)) { + // Save rollback file next. + hr = session.m_olRollback.SaveToFile(sSequenceFilenameRB); + if (SUCCEEDED(hr)) { + uiResult = ERROR_SUCCESS; + } else { + // Saving rollback file failed. + uiResult = HRESULT_CODE(hr); + } + } else { + // Saving commit file failed. + uiResult = HRESULT_CODE(hr); + } + } else + uiResult = ERROR_SUCCESS; + } else { + // Execution failed. + uiResult = HRESULT_CODE(hr); } - // Delete it. - hr = pTaskScheduler->Delete(sDisplayName); - if (FAILED(hr)) goto error4; - - if (bRollbackEnabled) { - // Order commit action to delete backup copy. - lstOperationsCM.AddTail(new CMSITSCAOpDeleteTask(sDisplayNameOrig)); + if (uiResult != ERROR_SUCCESS && !bIsCleanup) { + // Perform the cleanup now, since rollback action might not get called at all (if scheduled later than this action). + session.m_bContinueOnError = TRUE; + session.m_bRollbackEnabled = FALSE; + verify(SUCCEEDED(session.m_olRollback.Execute(&session))); } - } - } - - if (iAction >= INSTALLSTATE_LOCAL) { - // The component is being installed. - CComPtr pTask; - CComPtr pTaskFile; - - if (bRollbackEnabled) { - // In case the task creation fails halfway, try to delete its remains anyway on rollback. - lstOperationsRB.AddHead(new CMSITSCAOpDeleteTask(sDisplayName)); - } - - // Create the new task. - hr = pTaskScheduler->NewWorkItem(sDisplayName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); - if (FAILED(hr)) goto error4; - - // Load the task from sequence file. - hr = fSequence >> pTask; - if (FAILED(hr)) goto error4; - - // Save the task. - hr = pTask.QueryInterface(&pTaskFile); - if (FAILED(hr)) goto error4; - hr = pTaskFile->Save(NULL, TRUE); - if (FAILED(hr)) goto error4; - } - - if (::MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) { hr = E_ABORT; goto error4; } - } - - if (bRollbackEnabled) { - // Save commit script. - hr = fSequence2.Create(sSequenceFilenameCM, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); - if (SUCCEEDED(hr)) { - // Save rollback filename too, since commit script should delete it. - hr = fSequence2 << sSequenceFilenameRB; - if (FAILED(hr)) goto error4; - // Save commit script. - hr = lstOperationsCM.Save(fSequence2); - if (FAILED(hr)) goto error4; - fSequence2.Close(); - } - } - - hr = S_OK; - -error4: - if (bRollbackEnabled) { - if (SUCCEEDED(hr)) { - // This custom action completed successfully. Save rollback script in case of error. - if (SUCCEEDED(fSequence2.Create(sSequenceFilenameRB, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN))) { - // Save commit filename too, since rollback script should delete it. - if (SUCCEEDED(fSequence2 << sSequenceFilenameRB)) - lstOperationsRB.Save(fSequence2); - fSequence2.Close(); + } else { + // Task scheduler creation failed. + uiResult = HRESULT_CODE(hr); } } else { - // This very custom action encountered an error. Its rollback won't be called later (if scheduled later than this CA) so do the cleanup immediately. - lstOperationsRB.Execute(pTaskScheduler, TRUE); - ::DeleteFile(sSequenceFilenameCM); + // Sequence loading failed. Don't panic => do nothing. + uiResult = ERROR_SUCCESS; } - } - lstOperationsRB.Free(); - lstOperationsCM.Free(); -error3: - fSequence.Close(); -error2: - ::DeleteFile(sSequenceFilename); -error1: - if (bIsCoInitialized) ::CoUninitialize(); - return SUCCEEDED(hr) ? ERROR_SUCCESS : hr == E_ABORT ? ERROR_INSTALL_USEREXIT : ERROR_INVALID_DATA; -} - - -UINT MSITSCA_API FinalizeScheduledTasks(MSIHANDLE hInstall) -{ - HRESULT hr; - BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL)); - CString sSequenceFilename, sSequenceFilename2; - CAtlFile fSequence; - CComPtr pTaskScheduler; - CMSITSCAOperationList lstOperations; - - assert(0); // Attach debugger here, or press "Ignore"! - - hr = HRESULT_FROM_WIN32(::MsiGetProperty(hInstall, _T("CustomActionData"), sSequenceFilename)); - if (SUCCEEDED(hr)) { - // Open sequence script file. - hr = fSequence.Create(sSequenceFilename, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); - if (SUCCEEDED(hr)) { - // Read "the other" script filename and delete it. - hr = fSequence >> sSequenceFilename2; - if (SUCCEEDED(hr)) { - ::DeleteFile(sSequenceFilename2); - - // Get task scheduler object. - hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); - if (SUCCEEDED(hr)) { - // Load operation sequence. - hr = lstOperations.Load(fSequence); - if (SUCCEEDED(hr)) { - // Execute the cleanup operations. - hr = lstOperations.Execute(pTaskScheduler, TRUE); - } - - lstOperations.Free(); - } - } - - fSequence.Close(); - } else - hr = S_OK; + lstOperations.Free(); ::DeleteFile(sSequenceFilename); + } else { + // Couldn't get CustomActionData property. } if (bIsCoInitialized) ::CoUninitialize(); - return SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INVALID_DATA; -} - - -//////////////////////////////////////////////////////////////////////////// -// Local functions -//////////////////////////////////////////////////////////////////////////// - -inline UINT MsiFormatAndStoreString(MSIHANDLE hInstall, MSIHANDLE hRecord, unsigned int iField, CAtlFile &fSequence, CStringW &sValue) -{ - UINT uiResult; - - uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, iField, sValue); - if (uiResult != ERROR_SUCCESS) return uiResult; - if (FAILED(fSequence << sValue)) return ERROR_WRITE_FAULT; - return ERROR_SUCCESS; -} - - -inline UINT MsiStoreInteger(MSIHANDLE hRecord, unsigned int iField, BOOL bIsNullable, CAtlFile &fSequence, int *piValue) -{ - int iValue; - - iValue = ::MsiRecordGetInteger(hRecord, iField); - if (!bIsNullable && iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; - if (FAILED(fSequence << iValue)) return ERROR_WRITE_FAULT; - if (piValue) *piValue = iValue; - return ERROR_SUCCESS; + return uiResult; } diff --git a/MSICALib/MSITSCA.h b/MSICALib/MSITSCA.h index 0b9c149..4a1bea4 100644 --- a/MSICALib/MSITSCA.h +++ b/MSICALib/MSITSCA.h @@ -39,6 +39,11 @@ #define MSITSCA_API #endif +#define ERROR_INSTALL_SCHEDULED_TASKS_DATABASE_OPEN 2550L +#define ERROR_INSTALL_SCHEDULED_TASKS_OPLIST_CREATE 2551L +#define ERROR_INSTALL_SCHEDULED_TASKS_SCRIPT_WRITE 2552L +#define ERROR_INSTALL_SCHEDULED_TASKS_PROPERTY_SET 2553L + //////////////////////////////////////////////////////////////////// // Exported functions @@ -50,7 +55,6 @@ extern "C" { UINT MSITSCA_API EvaluateScheduledTasks(MSIHANDLE hInstall); UINT MSITSCA_API InstallScheduledTasks(MSIHANDLE hInstall); - UINT MSITSCA_API FinalizeScheduledTasks(MSIHANDLE hInstall); #ifdef __cplusplus } @@ -364,172 +368,15 @@ inline HRESULT operator >>(CAtlFile &f, CStringW &str) } -inline HRESULT operator >>(CAtlFile &f, ITask *pTask) +inline HRESULT operator <<(CAtlFile &f, const TASK_TRIGGER &ttData) { - assert(pTask); + return f.Write(&ttData, sizeof(TASK_TRIGGER)); +} - HRESULT hr; - CStringW sValue, sValue2; - int iValue, iValue2; - UINT nTriggers; - hr = f >> sValue; - if (FAILED(hr)) return hr; - hr = pTask->SetApplicationName(sValue); - if (FAILED(hr)) return hr; - - hr = f >> sValue; - if (FAILED(hr)) return hr; - hr = pTask->SetParameters(sValue); - if (FAILED(hr)) return hr; - - hr = f >> sValue; - if (FAILED(hr)) return hr; - hr = pTask->SetWorkingDirectory(sValue); - if (FAILED(hr)) return hr; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - hr = pTask->SetFlags(iValue); - if (FAILED(hr)) return hr; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - hr = pTask->SetPriority(iValue); - if (FAILED(hr)) return hr; - - hr = f >> sValue; - if (FAILED(hr)) return hr; - hr = f >> sValue2; - if (FAILED(hr)) return hr; - hr = !sValue.IsEmpty() ? pTask->SetAccountInformation(sValue, sValue2.IsEmpty() ? NULL : sValue2) : S_OK; - { - // Clear the password in memory before proceeding. - int iLength = sValue2.GetLength(); - LPWSTR pszValue2 = sValue2.GetBuffer(iLength); - ::SecureZeroMemory(pszValue2, sizeof(WCHAR) * iLength); - sValue2.ReleaseBuffer(0); - } - if (FAILED(hr)) return hr; - - hr = f >> sValue; - if (FAILED(hr)) return hr; - hr = pTask->SetComment(sValue); - if (FAILED(hr)) return hr; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - hr = f >> iValue2; - if (FAILED(hr)) return hr; - hr = pTask->SetIdleWait((WORD)iValue, (WORD)iValue2); - if (FAILED(hr)) return hr; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - hr = pTask->SetMaxRunTime(iValue); - if (FAILED(hr)) return hr; - - // Read and add triggers. - hr = f >> (int&)nTriggers; - if (FAILED(hr)) return hr; - - while (nTriggers--) { - CComPtr pTrigger; - TASK_TRIGGER ttData; - WORD wTriggerIdx; - ULONGLONG ullValue; - FILETIME ftValue; - SYSTEMTIME stValue; - int iDaysInterval, iWeeksInterval, iDaysOfTheWeek, iDaysOfMonth, iWeekOfMonth, iMonthsOfYear; - - ZeroMemory(&ttData, sizeof(TASK_TRIGGER)); - ttData.cbTriggerSize = sizeof(TASK_TRIGGER); - - hr = pTask->CreateTrigger(&wTriggerIdx, &pTrigger); - if (FAILED(hr)) return hr; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ullValue = ((ULONGLONG)iValue + 138426) * 864000000000; - ftValue.dwHighDateTime = ullValue >> 32; - ftValue.dwLowDateTime = ullValue & 0xffffffff; - if (!::FileTimeToSystemTime(&ftValue, &stValue)) - return AtlHresultFromLastError(); - ttData.wBeginYear = stValue.wYear; - ttData.wBeginMonth = stValue.wMonth; - ttData.wBeginDay = stValue.wDay; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - if (iValue != MSI_NULL_INTEGER) { - ullValue = ((ULONGLONG)iValue + 138426) * 864000000000; - ftValue.dwHighDateTime = ullValue >> 32; - ftValue.dwLowDateTime = ullValue & 0xffffffff; - if (!::FileTimeToSystemTime(&ftValue, &stValue)) - return AtlHresultFromLastError(); - ttData.wEndYear = stValue.wYear; - ttData.wEndMonth = stValue.wMonth; - ttData.wEndDay = stValue.wDay; - ttData.rgFlags |= TASK_TRIGGER_FLAG_HAS_END_DATE; - } - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ttData.wStartHour = (WORD)(iValue / 60); - ttData.wStartMinute = (WORD)(iValue % 60); - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ttData.MinutesDuration = iValue; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ttData.MinutesInterval = iValue; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ttData.rgFlags |= iValue & ~TASK_TRIGGER_FLAG_HAS_END_DATE; - - hr = f >> iValue; - if (FAILED(hr)) return hr; - ttData.TriggerType = (TASK_TRIGGER_TYPE)iValue; - - hr = f >> iDaysInterval; - if (FAILED(hr)) return hr; - hr = f >> iWeeksInterval; - if (FAILED(hr)) return hr; - hr = f >> iDaysOfTheWeek; - if (FAILED(hr)) return hr; - hr = f >> iDaysOfMonth; - if (FAILED(hr)) return hr; - hr = f >> iWeekOfMonth; - if (FAILED(hr)) return hr; - hr = f >> iMonthsOfYear; - if (FAILED(hr)) return hr; - switch (ttData.TriggerType) { - case TASK_TIME_TRIGGER_DAILY: - ttData.Type.Daily.DaysInterval = (WORD)iDaysInterval; - break; - case TASK_TIME_TRIGGER_WEEKLY: - ttData.Type.Weekly.WeeksInterval = (WORD)iWeeksInterval; - ttData.Type.Weekly.rgfDaysOfTheWeek = (WORD)iDaysOfTheWeek; - break; - case TASK_TIME_TRIGGER_MONTHLYDATE: - ttData.Type.MonthlyDate.rgfDays = iDaysOfMonth; - ttData.Type.MonthlyDate.rgfMonths = (WORD)iMonthsOfYear; - break; - case TASK_TIME_TRIGGER_MONTHLYDOW: - ttData.Type.MonthlyDOW.wWhichWeek = (WORD)iWeekOfMonth; - ttData.Type.MonthlyDOW.rgfDaysOfTheWeek = (WORD)iDaysOfTheWeek; - ttData.Type.MonthlyDOW.rgfMonths = (WORD)iMonthsOfYear; - break; - } - - hr = pTrigger->SetTrigger(&ttData); - if (FAILED(hr)) return hr; - } - - return S_OK; +inline HRESULT operator >>(CAtlFile &f, TASK_TRIGGER &ttData) +{ + return f.Read(&ttData, sizeof(TASK_TRIGGER)); } #endif // !defined(RC_INVOKED) && !defined(MIDL_PASS) diff --git a/MSICALib/MSITSCAOp.cpp b/MSICALib/MSITSCAOp.cpp index 99c6b6c..1c25269 100644 --- a/MSICALib/MSITSCAOp.cpp +++ b/MSICALib/MSITSCAOp.cpp @@ -11,44 +11,466 @@ CMSITSCAOp::CMSITSCAOp() //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOpSingleTaskOperation +// CMSITSCAOpSingleStringOperation //////////////////////////////////////////////////////////////////////////// -CMSITSCAOpSingleTaskOperation::CMSITSCAOpSingleTaskOperation(LPCWSTR pszTaskName) : - m_sTaskName(pszTaskName), +CMSITSCAOpSingleStringOperation::CMSITSCAOpSingleStringOperation(LPCWSTR pszValue) : + m_sValue(pszValue), CMSITSCAOp() { } //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOpsrcDstTaskOperation +// CMSITSCAOpDoubleStringOperation //////////////////////////////////////////////////////////////////////////// -CMSITSCAOpSrcDstTaskOperation::CMSITSCAOpSrcDstTaskOperation(LPCWSTR pszSourceTaskName, LPCWSTR pszDestinationTaskName) : - m_sSourceTaskName(pszSourceTaskName), - m_sDestinationTaskName(pszDestinationTaskName), +CMSITSCAOpSrcDstStringOperation::CMSITSCAOpSrcDstStringOperation(LPCWSTR pszValue1, LPCWSTR pszValue2) : + m_sValue1(pszValue1), + m_sValue2(pszValue2), CMSITSCAOp() { } +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpBooleanOperation +//////////////////////////////////////////////////////////////////////////// + +CMSITSCAOpBooleanOperation::CMSITSCAOpBooleanOperation(BOOL bValue) : + m_bValue(bValue) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpEnableRollback +//////////////////////////////////////////////////////////////////////////// + +CMSITSCAOpEnableRollback::CMSITSCAOpEnableRollback(BOOL bEnable) : + CMSITSCAOpBooleanOperation(bEnable) +{ +} + + +HRESULT CMSITSCAOpEnableRollback::Execute(CMSITSCASession *pSession) +{ + assert(pSession); + + pSession->m_bRollbackEnabled = m_bValue; + return S_OK; +} + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpDeleteFile +//////////////////////////////////////////////////////////////////////////// + +CMSITSCAOpDeleteFile::CMSITSCAOpDeleteFile(LPCWSTR pszFileName) : + CMSITSCAOpSingleStringOperation(pszFileName) +{ +} + + +HRESULT CMSITSCAOpDeleteFile::Execute(CMSITSCASession *pSession) +{ + assert(pSession); + + if (pSession->m_bRollbackEnabled) { + CStringW sBackupName; + UINT uiCount = 0; + DWORD dwError; + + do { + // Rename the file to make a backup. + sBackupName.Format(L"%ls (orig %u)", (LPCWSTR)m_sValue, ++uiCount); + dwError = ::MoveFileW(m_sValue, sBackupName) ? ERROR_SUCCESS : ::GetLastError(); + } while (dwError == ERROR_FILE_EXISTS); + if (dwError != ERROR_SUCCESS) return AtlHresultFromWin32(dwError); + + // Order rollback action to restore from backup copy. + pSession->m_olRollback.AddHead(new CMSITSCAOpMoveFile(sBackupName, m_sValue)); + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new CMSITSCAOpDeleteFile(sBackupName)); + + return S_OK; + } else { + // Delete the file. + return ::DeleteFileW(m_sValue) ? S_OK : AtlHresultFromLastError(); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpMoveFile +//////////////////////////////////////////////////////////////////////////// + +CMSITSCAOpMoveFile::CMSITSCAOpMoveFile(LPCWSTR pszFileSrc, LPCWSTR pszFileDst) : + CMSITSCAOpSrcDstStringOperation(pszFileSrc, pszFileDst) +{ +} + + +HRESULT CMSITSCAOpMoveFile::Execute(CMSITSCASession *pSession) +{ + assert(pSession); + + if (pSession->m_bRollbackEnabled) { + // Move the file. + if (::MoveFileW(m_sValue1, m_sValue2)) { + // Order rollback action to move it back. + pSession->m_olRollback.AddHead(new CMSITSCAOpMoveFile(m_sValue2, m_sValue1)); + return S_OK; + } else + return AtlHresultFromLastError(); + } else { + // Move the file. + return ::MoveFileW(m_sValue1, m_sValue2) ? S_OK : AtlHresultFromLastError(); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpCreateTask +//////////////////////////////////////////////////////////////////////////// + +CMSITSCAOpCreateTask::CMSITSCAOpCreateTask(LPCWSTR pszTaskName) : + m_bForce(FALSE), + m_dwFlags(0), + m_dwPriority(NORMAL_PRIORITY_CLASS), + m_wIdleMinutes(0), + m_wDeadlineMinutes(0), + m_dwMaxRuntimeMS(INFINITE), + CMSITSCAOpSingleStringOperation(pszTaskName) +{ +} + + +CMSITSCAOpCreateTask::~CMSITSCAOpCreateTask() +{ + // Clear the password in memory. + int iLength = m_sPassword.GetLength(); + ::SecureZeroMemory(m_sPassword.GetBuffer(iLength), sizeof(WCHAR) * iLength); + m_sPassword.ReleaseBuffer(0); +} + + +HRESULT CMSITSCAOpCreateTask::Execute(CMSITSCASession *pSession) +{ + HRESULT hr; + CComPtr pTask; + POSITION pos; + + // Load the task to see if it exists. + hr = pSession->m_pTaskScheduler->Activate(m_sValue, IID_ITask, (IUnknown**)&pTask); + if (SUCCEEDED(hr)) { + // The task exists. Release it prematurely before proceeding. + pTask.Detach()->Release(); + if (m_bForce) { + // We're supposed to overwrite it. Delete the existing task first. + // Since task deleting is a complicated job (when rollback/commit support is required), and we do have an operation for just that, we use it. + CMSITSCAOpDeleteTask opDeleteTask(m_sValue); + hr = opDeleteTask.Execute(pSession); + if (FAILED(hr)) return hr; + } else { + // The task exists, and we're happy with that. + return S_OK; + } + } + + // Create the new task. + hr = pSession->m_pTaskScheduler->NewWorkItem(m_sValue, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the task. ITask::NewWorkItem() might made a blank task already. + pSession->m_olRollback.AddHead(new CMSITSCAOpDeleteTask(m_sValue)); + } + if (FAILED(hr)) return hr; + + // Set its properties. + hr = pTask->SetApplicationName (m_sApplicationName ); if (FAILED(hr)) return hr; + hr = pTask->SetComment (m_sComment ); if (FAILED(hr)) return hr; + hr = pTask->SetParameters (m_sParameters ); if (FAILED(hr)) return hr; + hr = pTask->SetWorkingDirectory(m_sWorkingDirectory); if (FAILED(hr)) return hr; + if (pSession->m_bRollbackEnabled && (m_dwFlags & TASK_FLAG_DISABLED) == 0) { + // The task is supposed to be enabled. + // However, we shall enable it at commit to prevent it from accidentally starting in the very installation phase. + pSession->m_olCommit.AddTail(new CMSITSCAOpEnableTask(m_sValue, TRUE)); + m_dwFlags |= TASK_FLAG_DISABLED; + } + hr = pTask->SetFlags (m_dwFlags ); if (FAILED(hr)) return hr; + hr = pTask->SetPriority (m_dwPriority ); if (FAILED(hr)) return hr; + if (!m_sAccountName.IsEmpty()) { + hr = pTask->SetAccountInformation(m_sAccountName, m_sPassword.IsEmpty() ? NULL : m_sPassword); + if (FAILED(hr)) return hr; + } + + hr = pTask->SetIdleWait (m_wIdleMinutes, m_wDeadlineMinutes); if (FAILED(hr)) return hr; + hr = pTask->SetMaxRunTime(m_dwMaxRuntimeMS ); if (FAILED(hr)) return hr; + + // Add triggers. + for (pos = m_lTriggers.GetHeadPosition(); pos;) { + WORD wTriggerIdx; + CComPtr pTrigger; + TASK_TRIGGER &ttData = m_lTriggers.GetNext(pos); + + hr = pTask->CreateTrigger(&wTriggerIdx, &pTrigger); + if (FAILED(hr)) return hr; + + hr = pTrigger->SetTrigger(&ttData); + if (FAILED(hr)) return hr; + } + + // Save the task. + CComQIPtr pTaskFile(pTask); + hr = pTaskFile->Save(NULL, TRUE); + + return S_OK; +} + + +UINT CMSITSCAOpCreateTask::SetFromRecord(MSIHANDLE hInstall, MSIHANDLE hRecord) +{ + UINT uiResult; + int iValue; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 3, m_sApplicationName); + if (uiResult != ERROR_SUCCESS) return uiResult; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 4, m_sParameters); + if (uiResult != ERROR_SUCCESS) return uiResult; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 5, m_sWorkingDirectory); + if (uiResult != ERROR_SUCCESS) return uiResult; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 10, m_sComment); + if (uiResult != ERROR_SUCCESS) return uiResult; + + m_dwFlags = ::MsiRecordGetInteger(hRecord, 6); + if (m_dwFlags == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + + m_dwPriority = ::MsiRecordGetInteger(hRecord, 7); + if (m_dwPriority == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 8, m_sAccountName); + if (uiResult != ERROR_SUCCESS) return uiResult; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 9, m_sPassword); + if (uiResult != ERROR_SUCCESS) return uiResult; + + iValue = ::MsiRecordGetInteger(hRecord, 11); + m_wIdleMinutes = iValue != MSI_NULL_INTEGER ? (WORD)iValue : 0; + + iValue = ::MsiRecordGetInteger(hRecord, 12); + m_wDeadlineMinutes = iValue != MSI_NULL_INTEGER ? (WORD)iValue : 0; + + m_dwMaxRuntimeMS = ::MsiRecordGetInteger(hRecord, 13); + if (m_dwMaxRuntimeMS == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + + return S_OK; +} + + +UINT CMSITSCAOpCreateTask::SetTriggersFromView(MSIHANDLE hView) +{ + for (;;) { + UINT uiResult; + PMSIHANDLE hRecord; + TASK_TRIGGER ttData; + ULONGLONG ullValue; + FILETIME ftValue; + SYSTEMTIME stValue; + int iValue, iStartTime, iStartTimeRand; + + // Fetch one record from the view. + uiResult = ::MsiViewFetch(hView, &hRecord); + if (uiResult == ERROR_NO_MORE_ITEMS) return ERROR_SUCCESS; + else if (uiResult != ERROR_SUCCESS) return uiResult; + + ZeroMemory(&ttData, sizeof(TASK_TRIGGER)); + ttData.cbTriggerSize = sizeof(TASK_TRIGGER); + + // Get StartDate. + iValue = ::MsiRecordGetInteger(hRecord, 2); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ullValue = ((ULONGLONG)iValue + 138426) * 864000000000; + ftValue.dwHighDateTime = ullValue >> 32; + ftValue.dwLowDateTime = ullValue & 0xffffffff; + if (!::FileTimeToSystemTime(&ftValue, &stValue)) + return ::GetLastError(); + ttData.wBeginYear = stValue.wYear; + ttData.wBeginMonth = stValue.wMonth; + ttData.wBeginDay = stValue.wDay; + + // Get EndDate. + iValue = ::MsiRecordGetInteger(hRecord, 3); + if (iValue != MSI_NULL_INTEGER) { + ullValue = ((ULONGLONG)iValue + 138426) * 864000000000; + ftValue.dwHighDateTime = ullValue >> 32; + ftValue.dwLowDateTime = ullValue & 0xffffffff; + if (!::FileTimeToSystemTime(&ftValue, &stValue)) + return ::GetLastError(); + ttData.wEndYear = stValue.wYear; + ttData.wEndMonth = stValue.wMonth; + ttData.wEndDay = stValue.wDay; + ttData.rgFlags |= TASK_TRIGGER_FLAG_HAS_END_DATE; + } + + // Get StartTime and StartTimeRand. + iStartTime = ::MsiRecordGetInteger(hRecord, 4); + if (iStartTime == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + iStartTimeRand = ::MsiRecordGetInteger(hRecord, 5); + if (iStartTimeRand != MSI_NULL_INTEGER) { + // Add random delay to StartTime. + iStartTime += ::MulDiv(rand(), iStartTimeRand, RAND_MAX); + } + ttData.wStartHour = (WORD)(iStartTime / 60); + ttData.wStartMinute = (WORD)(iStartTime % 60); + + // Get MinutesDuration. + iValue = ::MsiRecordGetInteger(hRecord, 6); + ttData.MinutesDuration = iValue != MSI_NULL_INTEGER ? iValue : 0; + + // Get MinutesInterval. + iValue = ::MsiRecordGetInteger(hRecord, 7); + ttData.MinutesInterval = iValue != MSI_NULL_INTEGER ? iValue : 0; + + // Get Flags. + iValue = ::MsiRecordGetInteger(hRecord, 8); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.rgFlags |= iValue & ~TASK_TRIGGER_FLAG_HAS_END_DATE; + + // Get Type. + iValue = ::MsiRecordGetInteger(hRecord, 9); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.TriggerType = (TASK_TRIGGER_TYPE)iValue; + + switch (ttData.TriggerType) { + case TASK_TIME_TRIGGER_DAILY: + // Get DaysInterval. + iValue = ::MsiRecordGetInteger(hRecord, 10); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.Daily.DaysInterval = (WORD)iValue; + break; + + case TASK_TIME_TRIGGER_WEEKLY: + // Get WeeksInterval. + iValue = ::MsiRecordGetInteger(hRecord, 11); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.Weekly.WeeksInterval = (WORD)iValue; + + // Get DaysOfTheWeek. + iValue = ::MsiRecordGetInteger(hRecord, 12); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.Weekly.rgfDaysOfTheWeek = (WORD)iValue; + break; + + case TASK_TIME_TRIGGER_MONTHLYDATE: + // Get DaysOfMonth. + iValue = ::MsiRecordGetInteger(hRecord, 13); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.MonthlyDate.rgfDays = (WORD)iValue; + + // Get MonthsOfYear. + iValue = ::MsiRecordGetInteger(hRecord, 15); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.MonthlyDate.rgfMonths = (WORD)iValue; + break; + + case TASK_TIME_TRIGGER_MONTHLYDOW: + // Get WeekOfMonth. + iValue = ::MsiRecordGetInteger(hRecord, 14); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.MonthlyDOW.wWhichWeek = (WORD)iValue; + + // Get DaysOfTheWeek. + iValue = ::MsiRecordGetInteger(hRecord, 12); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.MonthlyDOW.rgfDaysOfTheWeek = (WORD)iValue; + + // Get MonthsOfYear. + iValue = ::MsiRecordGetInteger(hRecord, 15); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.Type.MonthlyDOW.rgfMonths = (WORD)iValue; + break; + } + + m_lTriggers.AddTail(ttData); + } + + return S_OK; +} + + //////////////////////////////////////////////////////////////////////////// // CMSITSCAOpDeleteTask //////////////////////////////////////////////////////////////////////////// CMSITSCAOpDeleteTask::CMSITSCAOpDeleteTask(LPCWSTR pszTaskName) : - CMSITSCAOpSingleTaskOperation(pszTaskName) + CMSITSCAOpSingleStringOperation(pszTaskName) { } -HRESULT CMSITSCAOpDeleteTask::Execute(ITaskScheduler *pTaskScheduler) +HRESULT CMSITSCAOpDeleteTask::Execute(CMSITSCASession *pSession) { - assert(pTaskScheduler); + assert(pSession); - // Delete the task. - return pTaskScheduler->Delete(m_sTaskName); + if (pSession->m_bRollbackEnabled) { + HRESULT hr; + CComPtr pTask; + DWORD dwFlags; + CString sDisplayNameOrig; + UINT uiCount = 0; + + // Load the task. + hr = pSession->m_pTaskScheduler->Activate(m_sValue, IID_ITask, (IUnknown**)&pTask); + if (FAILED(hr)) return hr; + + // Disable the task. + hr = pTask->GetFlags(&dwFlags); + if (FAILED(hr)) return hr; + if ((dwFlags & TASK_FLAG_DISABLED) == 0) { + // The task is enabled. + + // In case the task disabling fails halfway, try to re-enable it anyway on rollback. + pSession->m_olRollback.AddHead(new CMSITSCAOpEnableTask(m_sValue, TRUE)); + + // Disable it. + dwFlags |= TASK_FLAG_DISABLED; + hr = pTask->SetFlags(dwFlags); + if (FAILED(hr)) return hr; + } + + // Prepare a backup copy of task. + do { + sDisplayNameOrig.Format(L"%ls (orig %u)", (LPCWSTR)m_sValue, ++uiCount); + hr = pSession->m_pTaskScheduler->AddWorkItem(sDisplayNameOrig, pTask); + } while (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS)); + // In case the backup copy creation failed halfway, try to delete its remains anyway on rollback. + pSession->m_olRollback.AddHead(new CMSITSCAOpDeleteTask(sDisplayNameOrig)); + if (FAILED(hr)) return hr; + + // Save the backup copy. + CComQIPtr pTaskFile(pTask); + hr = pTaskFile->Save(NULL, TRUE); + if (FAILED(hr)) return hr; + + // Order rollback action to restore from backup copy. + pSession->m_olRollback.AddHead(new CMSITSCAOpCopyTask(sDisplayNameOrig, m_sValue)); + + // Delete it. + hr = pSession->m_pTaskScheduler->Delete(m_sValue); + if (FAILED(hr)) return hr; + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new CMSITSCAOpDeleteTask(sDisplayNameOrig)); + + return S_OK; + } else { + // Delete the task. + return pSession->m_pTaskScheduler->Delete(m_sValue); + } } @@ -58,19 +480,19 @@ HRESULT CMSITSCAOpDeleteTask::Execute(ITaskScheduler *pTaskScheduler) CMSITSCAOpEnableTask::CMSITSCAOpEnableTask(LPCWSTR pszTaskName, BOOL bEnable) : m_bEnable(bEnable), - CMSITSCAOpSingleTaskOperation(pszTaskName) + CMSITSCAOpSingleStringOperation(pszTaskName) { } -HRESULT CMSITSCAOpEnableTask::Execute(ITaskScheduler *pTaskScheduler) +HRESULT CMSITSCAOpEnableTask::Execute(CMSITSCASession *pSession) { - assert(pTaskScheduler); + assert(pSession); HRESULT hr; CComPtr pTask; // Load the task. - hr = pTaskScheduler->Activate(m_sTaskName, IID_ITask, (IUnknown**)&pTask); + hr = pSession->m_pTaskScheduler->Activate(m_sValue, IID_ITask, (IUnknown**)&pTask); if (SUCCEEDED(hr)) { DWORD dwFlags; @@ -78,10 +500,22 @@ HRESULT CMSITSCAOpEnableTask::Execute(ITaskScheduler *pTaskScheduler) hr = pTask->GetFlags(&dwFlags); if (SUCCEEDED(hr)) { // Modify flags. - if (m_bEnable) - dwFlags &= ~TASK_FLAG_DISABLED; - else + if (m_bEnable) { + if (pSession->m_bRollbackEnabled && (dwFlags & TASK_FLAG_DISABLED)) { + // The task is disabled and supposed to be enabled. + // However, we shall enable it at commit to prevent it from accidentally starting in the very installation phase. + pSession->m_olCommit.AddTail(new CMSITSCAOpEnableTask(m_sValue, TRUE)); + return S_OK; + } else + dwFlags &= ~TASK_FLAG_DISABLED; + } else { + if (pSession->m_bRollbackEnabled && !(dwFlags & TASK_FLAG_DISABLED)) { + // The task is enabled and we will disable it now. + // Order rollback to re-enable it. + pSession->m_olRollback.AddHead(new CMSITSCAOpEnableTask(m_sValue, TRUE)); + } dwFlags |= TASK_FLAG_DISABLED; + } // Set flags. hr = pTask->SetFlags(dwFlags); @@ -101,126 +535,134 @@ HRESULT CMSITSCAOpEnableTask::Execute(ITaskScheduler *pTaskScheduler) // CMSITSCAOpCopyTask //////////////////////////////////////////////////////////////////////////// -CMSITSCAOpCopyTask::CMSITSCAOpCopyTask(LPCWSTR pszSourceTaskName, LPCWSTR pszDestinationTaskName) : - CMSITSCAOpSrcDstTaskOperation(pszSourceTaskName, pszDestinationTaskName) +CMSITSCAOpCopyTask::CMSITSCAOpCopyTask(LPCWSTR pszTaskSrc, LPCWSTR pszTaskDst) : + CMSITSCAOpSrcDstStringOperation(pszTaskSrc, pszTaskDst) { } -HRESULT CMSITSCAOpCopyTask::Execute(ITaskScheduler *pTaskScheduler) +HRESULT CMSITSCAOpCopyTask::Execute(CMSITSCASession *pSession) { - assert(pTaskScheduler); + assert(pSession); HRESULT hr; CComPtr pTask; // Load the source task. - hr = pTaskScheduler->Activate(m_sSourceTaskName, IID_ITask, (IUnknown**)&pTask); - if (SUCCEEDED(hr)) { - // Add with different name. - hr = pTaskScheduler->AddWorkItem(m_sDestinationTaskName, pTask); - if (SUCCEEDED(hr)) { - // Save the task. - CComQIPtr pTaskFile(pTask); - hr = pTaskFile->Save(NULL, TRUE); - } - } + hr = pSession->m_pTaskScheduler->Activate(m_sValue1, IID_ITask, (IUnknown**)&pTask); + if (FAILED(hr)) return hr; - return hr; + // Add with different name. + hr = pSession->m_pTaskScheduler->AddWorkItem(m_sValue2, pTask); + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the new copy. ITask::AddWorkItem() might made a blank task already. + pSession->m_olRollback.AddHead(new CMSITSCAOpDeleteTask(m_sValue2)); + } + if (FAILED(hr)) return hr; + + // Save the task. + CComQIPtr pTaskFile(pTask); + hr = pTaskFile->Save(NULL, TRUE); + if (FAILED(hr)) return hr; + + return S_OK; } //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOperationList +// CMSITSCAOpList //////////////////////////////////////////////////////////////////////////// -CMSITSCAOperationList::CMSITSCAOperationList() : +CMSITSCAOpList::CMSITSCAOpList() : CAtlList(sizeof(CMSITSCAOp*)) { } -HRESULT CMSITSCAOperationList::Save(CAtlFile &f) const +void CMSITSCAOpList::Free() { POSITION pos; - HRESULT hr; for (pos = GetHeadPosition(); pos;) { - const CMSITSCAOp *pOp = GetNext(pos); - if (dynamic_cast(pOp)) - hr = Save(f, pOp); - else if (dynamic_cast(pOp)) - hr = Save(f, pOp); - else if (dynamic_cast(pOp)) - hr = Save(f, pOp); - else { - // Unsupported type of operation. - assert(0); - hr = E_UNEXPECTED; + CMSITSCAOp *pOp = GetNext(pos); + CMSITSCAOpList *pOpList = dynamic_cast(pOp); + + if (pOpList) { + // Recursivelly free sublists. + pOpList->Free(); } - - if (FAILED(hr)) return hr; + delete pOp; } - return S_OK; -} - - -HRESULT CMSITSCAOperationList::Load(CAtlFile &f) -{ - int iTemp; - HRESULT hr; - - for (;;) { - hr = f >> iTemp; - if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) { - hr = S_OK; - break; - } else if (FAILED(hr)) - return hr; - - switch ((OPERATION)iTemp) { - case OPERATION_DELETE_TASK: - hr = LoadAndAddTail(f); - break; - case OPERATION_ENABLE_TASK: - hr = LoadAndAddTail(f); - break; - case OPERATION_COPY_TASK: - hr = LoadAndAddTail(f); - break; - default: - // Unsupported type of operation. - assert(0); - hr = E_UNEXPECTED; - } - - if (FAILED(hr)) return hr; - } - - return S_OK; -} - - -void CMSITSCAOperationList::Free() -{ - POSITION pos; - - for (pos = GetHeadPosition(); pos;) - delete GetNext(pos); - RemoveAll(); } -HRESULT CMSITSCAOperationList::Execute(ITaskScheduler *pTaskScheduler, BOOL bContinueOnError) +HRESULT CMSITSCAOpList::LoadFromFile(LPCTSTR pszFileName) { + assert(pszFileName); + HRESULT hr; + CAtlFile fSequence; + + hr = fSequence.Create(pszFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); + if (FAILED(hr)) return hr; + + // Load operation sequence. + return fSequence >> *this; +} + + +HRESULT CMSITSCAOpList::SaveToFile(LPCTSTR pszFileName) const +{ + assert(pszFileName); + HRESULT hr; + CAtlFile fSequence; + + hr = fSequence.Create(pszFileName, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); + if (FAILED(hr)) return hr; + + // Save execute sequence to file. + hr = fSequence << *this; + fSequence.Close(); + + if (FAILED(hr)) ::DeleteFile(pszFileName); + return hr; +} + + +HRESULT CMSITSCAOpList::Execute(CMSITSCASession *pSession) +{ + assert(pSession); POSITION pos; HRESULT hr; for (pos = GetHeadPosition(); pos;) { - hr = GetNext(pos)->Execute(pTaskScheduler); - if (!bContinueOnError && FAILED(hr)) return hr; + hr = GetNext(pos)->Execute(pSession); + if (!pSession->m_bContinueOnError && FAILED(hr)) return hr; } return S_OK; } + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCASession +//////////////////////////////////////////////////////////////////////////// + +CMSITSCASession::CMSITSCASession() : + m_bContinueOnError(FALSE), + m_bRollbackEnabled(FALSE) +{ +} + + +CMSITSCASession::~CMSITSCASession() +{ +} + + +HRESULT CMSITSCASession::Initialize() +{ + return m_pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); +} + + diff --git a/MSICALib/MSITSCAOp.h b/MSICALib/MSITSCAOp.h index 1dacf51..1c43281 100644 --- a/MSICALib/MSITSCAOp.h +++ b/MSICALib/MSITSCAOp.h @@ -2,12 +2,15 @@ #define __MSITSCAOP_H__ #include "MSITSCA.h" +#include #include #include #include #include #include +class CMSITSCASession; + //////////////////////////////////////////////////////////////////////////// // CMSITSCAOp @@ -18,42 +21,131 @@ class CMSITSCAOp public: CMSITSCAOp(); - virtual HRESULT Execute(ITaskScheduler *pTaskScheduler) = 0; + virtual HRESULT Execute(CMSITSCASession *pSession) = 0; }; //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOpSingleTaskOperation +// CMSITSCAOpSingleStringOperation //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOpSingleTaskOperation : public CMSITSCAOp +class CMSITSCAOpSingleStringOperation : public CMSITSCAOp { public: - CMSITSCAOpSingleTaskOperation(LPCWSTR pszTaskName = L""); + CMSITSCAOpSingleStringOperation(LPCWSTR pszValue = L""); - friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSingleTaskOperation &op); - friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSingleTaskOperation &op); + friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSingleStringOperation &op); + friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSingleStringOperation &op); protected: - CStringW m_sTaskName; + CStringW m_sValue; }; //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOpsrcDstTaskOperation +// CMSITSCAOpDoubleStringOperation //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOpSrcDstTaskOperation : public CMSITSCAOp +class CMSITSCAOpSrcDstStringOperation : public CMSITSCAOp { public: - CMSITSCAOpSrcDstTaskOperation(LPCWSTR pszSourceTaskName = L"", LPCWSTR pszDestinationTaskName = L""); + CMSITSCAOpSrcDstStringOperation(LPCWSTR pszValue1 = L"", LPCWSTR pszValue2 = L""); - friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSrcDstTaskOperation &op); - friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSrcDstTaskOperation &op); + friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSrcDstStringOperation &op); + friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSrcDstStringOperation &op); protected: - CStringW m_sSourceTaskName; - CStringW m_sDestinationTaskName; + CStringW m_sValue1; + CStringW m_sValue2; +}; + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpBooleanOperation +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCAOpBooleanOperation : public CMSITSCAOp +{ +public: + CMSITSCAOpBooleanOperation(BOOL bValue = TRUE); + + friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpBooleanOperation &op); + friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpBooleanOperation &op); + +protected: + BOOL m_bValue; +}; + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpEnableRollback +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCAOpEnableRollback : public CMSITSCAOpBooleanOperation +{ +public: + CMSITSCAOpEnableRollback(BOOL bEnable = TRUE); + virtual HRESULT Execute(CMSITSCASession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpDeleteFile +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCAOpDeleteFile : public CMSITSCAOpSingleStringOperation +{ +public: + CMSITSCAOpDeleteFile(LPCWSTR pszFileName = L""); + virtual HRESULT Execute(CMSITSCASession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpMoveFile +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCAOpMoveFile : public CMSITSCAOpSrcDstStringOperation +{ +public: + CMSITSCAOpMoveFile(LPCWSTR pszFileSrc = L"", LPCWSTR pszFileDst = L""); + virtual HRESULT Execute(CMSITSCASession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// CMSITSCAOpCreateTask +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCAOpCreateTask : public CMSITSCAOpSingleStringOperation +{ +public: + CMSITSCAOpCreateTask(LPCWSTR pszTaskName = L""); + virtual ~CMSITSCAOpCreateTask(); + + virtual HRESULT Execute(CMSITSCASession *pSession); + + UINT SetFromRecord(MSIHANDLE hInstall, MSIHANDLE hRecord); + UINT SetTriggersFromView(MSIHANDLE hView); + + friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpCreateTask &op); + friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpCreateTask &op); + +protected: + BOOL m_bForce; + CStringW m_sApplicationName; + CStringW m_sParameters; + CStringW m_sWorkingDirectory; + CStringW m_sComment; + DWORD m_dwFlags; + DWORD m_dwPriority; + CStringW m_sAccountName; + CStringW m_sPassword; + WORD m_wIdleMinutes; + WORD m_wDeadlineMinutes; + DWORD m_dwMaxRuntimeMS; + + CAtlList m_lTriggers; }; @@ -61,11 +153,11 @@ protected: // CMSITSCAOpDeleteTask //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOpDeleteTask : public CMSITSCAOpSingleTaskOperation +class CMSITSCAOpDeleteTask : public CMSITSCAOpSingleStringOperation { public: CMSITSCAOpDeleteTask(LPCWSTR pszTaskName = L""); - virtual HRESULT Execute(ITaskScheduler *pTaskScheduler); + virtual HRESULT Execute(CMSITSCASession *pSession); }; @@ -73,11 +165,11 @@ public: // CMSITSCAOpEnableTask //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOpEnableTask : public CMSITSCAOpSingleTaskOperation +class CMSITSCAOpEnableTask : public CMSITSCAOpSingleStringOperation { public: CMSITSCAOpEnableTask(LPCWSTR pszTaskName = L"", BOOL bEnable = TRUE); - virtual HRESULT Execute(ITaskScheduler *pTaskScheduler); + virtual HRESULT Execute(CMSITSCASession *pSession); friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpEnableTask &op); friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpEnableTask &op); @@ -91,34 +183,42 @@ protected: // CMSITSCAOpCopyTask //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOpCopyTask : public CMSITSCAOpSrcDstTaskOperation +class CMSITSCAOpCopyTask : public CMSITSCAOpSrcDstStringOperation { public: - CMSITSCAOpCopyTask(LPCWSTR pszSourceTaskName = L"", LPCWSTR pszDestinationTaskName = L""); - virtual HRESULT Execute(ITaskScheduler *pTaskScheduler); + CMSITSCAOpCopyTask(LPCWSTR pszTaskSrc = L"", LPCWSTR pszTaskDst = L""); + virtual HRESULT Execute(CMSITSCASession *pSession); }; //////////////////////////////////////////////////////////////////////////// -// CMSITSCAOperationList +// CMSITSCAOpList //////////////////////////////////////////////////////////////////////////// -class CMSITSCAOperationList : public CAtlList +class CMSITSCAOpList : public CMSITSCAOp, public CAtlList { public: - CMSITSCAOperationList(); + CMSITSCAOpList(); - HRESULT Save(CAtlFile &f) const; - HRESULT Load(CAtlFile &f); void Free(); + HRESULT LoadFromFile(LPCTSTR pszFileName); + HRESULT SaveToFile(LPCTSTR pszFileName) const; - HRESULT Execute(ITaskScheduler *pTaskScheduler, BOOL bContinueOnError); + virtual HRESULT Execute(CMSITSCASession *pSession); + + friend inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpList &op); + friend inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpList &op); protected: enum OPERATION { - OPERATION_DELETE_TASK = 1, + OPERATION_ENABLE_ROLLBACK = 1, + OPERATION_DELETE_FILE, + OPERATION_MOVE_FILE, + OPERATION_CREATE_TASK, + OPERATION_DELETE_TASK, OPERATION_ENABLE_TASK, - OPERATION_COPY_TASK + OPERATION_COPY_TASK, + OPERATION_SUBLIST }; protected: @@ -127,11 +227,262 @@ protected: }; +//////////////////////////////////////////////////////////////////////////// +// CMSITSCASession +//////////////////////////////////////////////////////////////////////////// + +class CMSITSCASession +{ +public: + CMSITSCASession(); + virtual ~CMSITSCASession(); + + HRESULT Initialize(); + + CComPtr m_pTaskScheduler; // Task scheduler interface + BOOL m_bContinueOnError; // Continue execution on operation error? + BOOL m_bRollbackEnabled; // Is rollback enabled? + CMSITSCAOpList m_olRollback; // Rollback operation list + CMSITSCAOpList m_olCommit; // Commit operation list +}; + + +//////////////////////////////////////////////////////////////////////////// +// Inline operators +//////////////////////////////////////////////////////////////////////////// + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSingleStringOperation &op) +{ + return f << op.m_sValue; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSingleStringOperation &op) +{ + return f >> op.m_sValue; +} + + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSrcDstStringOperation &op) +{ + HRESULT hr; + + hr = f << op.m_sValue1; + if (FAILED(hr)) return hr; + + return f << op.m_sValue2; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSrcDstStringOperation &op) +{ + HRESULT hr; + + hr = f >> op.m_sValue1; + if (FAILED(hr)) return hr; + + return f >> op.m_sValue2; +} + + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpBooleanOperation &op) +{ + return f << (int)op.m_bValue; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpBooleanOperation &op) +{ + int iValue; + HRESULT hr; + + hr = f >> iValue; + if (FAILED(hr)) return hr; + op.m_bValue = iValue ? TRUE : FALSE; + + return S_OK; +} + + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpCreateTask &op) +{ + HRESULT hr; + POSITION pos; + + hr = f << (const CMSITSCAOpSingleStringOperation&)op; if (FAILED(hr)) return hr; + hr = f << (int)op.m_bForce; if (FAILED(hr)) return hr; + hr = f << op.m_sApplicationName; if (FAILED(hr)) return hr; + hr = f << op.m_sParameters; if (FAILED(hr)) return hr; + hr = f << op.m_sWorkingDirectory; if (FAILED(hr)) return hr; + hr = f << op.m_sComment; if (FAILED(hr)) return hr; + hr = f << (int)op.m_dwFlags; if (FAILED(hr)) return hr; + hr = f << (int)op.m_dwPriority; if (FAILED(hr)) return hr; + hr = f << op.m_sAccountName; if (FAILED(hr)) return hr; + hr = f << op.m_sPassword; if (FAILED(hr)) return hr; + hr = f << (int)MAKELONG(op.m_wDeadlineMinutes, op.m_wIdleMinutes); if (FAILED(hr)) return hr; + hr = f << (int)op.m_dwMaxRuntimeMS; if (FAILED(hr)) return hr; + hr = f << (int)op.m_lTriggers.GetCount(); if (FAILED(hr)) return hr; + for (pos = op.m_lTriggers.GetHeadPosition(); pos;) { + hr = f << op.m_lTriggers.GetNext(pos); + if (FAILED(hr)) return hr; + } + + return S_OK; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpCreateTask &op) +{ + HRESULT hr; + DWORD dwValue; + + hr = f >> (CMSITSCAOpSingleStringOperation&)op; if (FAILED(hr)) return hr; + hr = f >> (int&)dwValue; if (FAILED(hr)) return hr; op.m_bForce = dwValue ? TRUE : FALSE; + hr = f >> op.m_sApplicationName; if (FAILED(hr)) return hr; + hr = f >> op.m_sParameters; if (FAILED(hr)) return hr; + hr = f >> op.m_sWorkingDirectory; if (FAILED(hr)) return hr; + hr = f >> op.m_sComment; if (FAILED(hr)) return hr; + hr = f >> (int&)op.m_dwFlags; if (FAILED(hr)) return hr; + hr = f >> (int&)op.m_dwPriority; if (FAILED(hr)) return hr; + hr = f >> op.m_sAccountName; if (FAILED(hr)) return hr; + hr = f >> op.m_sPassword; if (FAILED(hr)) return hr; + hr = f >> (int&)dwValue; if (FAILED(hr)) return hr; op.m_wIdleMinutes = HIWORD(dwValue); op.m_wDeadlineMinutes = LOWORD(dwValue); + hr = f >> (int&)op.m_dwMaxRuntimeMS; if (FAILED(hr)) return hr; + hr = f >> (int&)dwValue; if (FAILED(hr)) return hr; + while (dwValue--) { + TASK_TRIGGER ttData; + hr = f >> ttData; + if (FAILED(hr)) return hr; + op.m_lTriggers.AddTail(ttData); + } + + return S_OK; +} + + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpEnableTask &op) +{ + HRESULT hr; + + hr = f << (const CMSITSCAOpSingleStringOperation&)op; + if (FAILED(hr)) return hr; + + return f << (int)op.m_bEnable; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpEnableTask &op) +{ + HRESULT hr; + int iTemp; + + hr = f >> (CMSITSCAOpSingleStringOperation&)op; + if (FAILED(hr)) return hr; + + hr = f >> iTemp; + if (FAILED(hr)) return hr; + op.m_bEnable = iTemp ? TRUE : FALSE; + + return S_OK; +} + + +inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpList &list) +{ + POSITION pos; + HRESULT hr; + + hr = f << (int)list.GetCount(); + if (FAILED(hr)) return hr; + + for (pos = list.GetHeadPosition(); pos;) { + const CMSITSCAOp *pOp = list.GetNext(pos); + if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else if (dynamic_cast(pOp)) + hr = list.Save(f, pOp); + else { + // Unsupported type of operation. + assert(0); + hr = E_UNEXPECTED; + } + + if (FAILED(hr)) return hr; + } + + return S_OK; +} + + +inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpList &list) +{ + HRESULT hr; + DWORD dwCount; + + hr = f >> (int&)dwCount; + if (FAILED(hr)) return hr; + + while (dwCount--) { + int iTemp; + + hr = f >> iTemp; + if (FAILED(hr)) return hr; + + switch ((CMSITSCAOpList::OPERATION)iTemp) { + case CMSITSCAOpList::OPERATION_ENABLE_ROLLBACK: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_DELETE_FILE: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_MOVE_FILE: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_CREATE_TASK: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_DELETE_TASK: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_ENABLE_TASK: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_COPY_TASK: + hr = list.LoadAndAddTail(f); + break; + case CMSITSCAOpList::OPERATION_SUBLIST: + hr = list.LoadAndAddTail(f); + break; + default: + // Unsupported type of operation. + assert(0); + hr = E_UNEXPECTED; + } + + if (FAILED(hr)) return hr; + } + + return S_OK; +} + + //////////////////////////////////////////////////////////////////////////// // Inline methods //////////////////////////////////////////////////////////////////////////// -template inline static HRESULT CMSITSCAOperationList::Save(CAtlFile &f, const CMSITSCAOp *p) +template inline static HRESULT CMSITSCAOpList::Save(CAtlFile &f, const CMSITSCAOp *p) { assert(p); HRESULT hr; @@ -145,7 +496,7 @@ template inline static HRESULT CMSITSCAOperationList::Save(CAt } -template inline HRESULT CMSITSCAOperationList::LoadAndAddTail(CAtlFile &f) +template inline HRESULT CMSITSCAOpList::LoadAndAddTail(CAtlFile &f) { HRESULT hr; @@ -165,69 +516,4 @@ template inline HRESULT CMSITSCAOperationList::LoadAndAddTail(CAtlFile return S_OK; } - -//////////////////////////////////////////////////////////////////////////// -// Inline operators -//////////////////////////////////////////////////////////////////////////// - -inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSingleTaskOperation &op) -{ - return f << op.m_sTaskName; -} - - -inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSingleTaskOperation &op) -{ - return f >> op.m_sTaskName; -} - - -inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpSrcDstTaskOperation &op) -{ - HRESULT hr; - - hr = f << op.m_sSourceTaskName; - if (FAILED(hr)) return hr; - - return f << op.m_sDestinationTaskName; -} - - -inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpSrcDstTaskOperation &op) -{ - HRESULT hr; - - hr = f >> op.m_sSourceTaskName; - if (FAILED(hr)) return hr; - - return f >> op.m_sDestinationTaskName; -} - - -inline HRESULT operator <<(CAtlFile &f, const CMSITSCAOpEnableTask &op) -{ - HRESULT hr; - - hr = f << (const CMSITSCAOpSingleTaskOperation&)op; - if (FAILED(hr)) return hr; - - return f << (int)op.m_bEnable; -} - - -inline HRESULT operator >>(CAtlFile &f, CMSITSCAOpEnableTask &op) -{ - HRESULT hr; - int iTemp; - - hr = f >> (CMSITSCAOpSingleTaskOperation&)op; - if (FAILED(hr)) return hr; - - hr = f >> iTemp; - if (FAILED(hr)) return hr; - op.m_bEnable = iTemp ? TRUE : FALSE; - - return S_OK; -} - #endif // __MSITSCAOP_H__