#include "StdAfx.h" #pragma comment(lib, "msi.lib") #pragma comment(lib, "mstask.lib") //////////////////////////////////////////////////////////////////////////// // Local constants //////////////////////////////////////////////////////////////////////////// #define MSITSCA_TASK_TICK_SIZE (16*1024) //////////////////////////////////////////////////////////////////////////// // Global functions //////////////////////////////////////////////////////////////////////////// extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { UNREFERENCED_PARAMETER(hInstance); UNREFERENCED_PARAMETER(lpReserved); switch (dwReason) { case DLL_PROCESS_ATTACH: // Randomize! srand((unsigned)time(NULL)); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } //////////////////////////////////////////////////////////////////// // Exported functions //////////////////////////////////////////////////////////////////// UINT MSITSCA_API EvaluateScheduledTasks(MSIHANDLE hInstall) { UINT uiResult; HRESULT hr; BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL)); CMSITSCAOpList olExecute; BOOL bRollbackEnabled; PMSIHANDLE hDatabase, hRecordProg = ::MsiCreateRecord(3); CString sValue; assert(hRecordProg); assert(0); // Attach debugger here, or press "Ignore"! // Check and add the rollback enabled state. uiResult = ::MsiGetProperty(hInstall, _T("RollbackDisabled"), sValue); bRollbackEnabled = uiResult == ERROR_SUCCESS ? _wtoi(sValue) || !sValue.IsEmpty() && towlower(sValue.GetAt(0)) == L'y' ? FALSE : TRUE : TRUE; olExecute.AddTail(new CMSITSCAOpEnableRollback(bRollbackEnabled)); // Open MSI database. hDatabase = ::MsiGetActiveDatabase(hInstall); if (hDatabase) { // Check if ScheduledTask table exists. If it doesn't exist, there's nothing to do. MSICONDITION condition = ::MsiDatabaseIsTablePersistent(hDatabase, _T("ScheduledTask")); if (condition == MSICONDITION_FALSE || condition == MSICONDITION_TRUE) { PMSIHANDLE hViewST; // Prepare a query to get a list/view of tasks. uiResult = ::MsiDatabaseOpenView(hDatabase, _T("SELECT Task,DisplayName,Application,Parameters,WorkingDir,Flags,Priority,User,Password,Description,IdleMin,IdleDeadline,MaxRuntime,Condition,Component_ FROM ScheduledTask"), &hViewST); if (uiResult == ERROR_SUCCESS) { // Execute query! uiResult = ::MsiViewExecute(hViewST, NULL); if (uiResult == ERROR_SUCCESS) { //CString sComponent; CStringW sDisplayName; for (;;) { PMSIHANDLE hRecord; INSTALLSTATE iInstalled, iAction; // Fetch one record from the view. uiResult = ::MsiViewFetch(hViewST, &hRecord); if (uiResult == ERROR_NO_MORE_ITEMS) { uiResult = ERROR_SUCCESS; break; } else if (uiResult != ERROR_SUCCESS) break; // Read and evaluate task's condition. uiResult = ::MsiRecordGetString(hRecord, 14, sValue); if (uiResult != ERROR_SUCCESS) break; // TODO: If condition is empty string ommit evaluation. condition = ::MsiEvaluateCondition(hInstall, sValue); if (condition == MSICONDITION_FALSE) continue; else if (condition == MSICONDITION_ERROR) { uiResult = ERROR_INVALID_FIELD; break; } // Read task's Component ID. uiResult = ::MsiRecordGetString(hRecord, 15, sValue); if (uiResult != ERROR_SUCCESS) break; // Get the component state. uiResult = ::MsiGetComponentState(hInstall, sValue, &iInstalled, &iAction); if (uiResult != ERROR_SUCCESS) break; // Get task's DisplayName. uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 2, sDisplayName); if (iAction >= INSTALLSTATE_LOCAL) { // Component is or should be installed. Create the task. PMSIHANDLE hViewTT; CMSITSCAOpCreateTask *opCreateTask = new CMSITSCAOpCreateTask(sDisplayName, MSITSCA_TASK_TICK_SIZE); assert(opCreateTask); // Populate the operation with task's data. opCreateTask->SetFromRecord(hInstall, hRecord); // 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; // 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; olExecute.AddTail(opCreateTask); } else if (iInstalled >= INSTALLSTATE_LOCAL && iAction >= INSTALLSTATE_ADVERTISED) { // Component is installed, but should be degraded to advertised. Delete the task. olExecute.AddTail(new CMSITSCAOpDeleteTask(sDisplayName, MSITSCA_TASK_TICK_SIZE)); } // 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); } } else { verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult) == ERROR_SUCCESS); ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); } } } else { uiResult = ERROR_INSTALL_SCHEDULED_TASKS_DATABASE_OPEN; verify(::MsiRecordSetInteger(hRecordProg, 1, uiResult) == ERROR_SUCCESS); ::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg); } olExecute.Free(); if (bIsCoInitialized) ::CoUninitialize(); return uiResult; } UINT MSITSCA_API InstallScheduledTasks(MSIHANDLE hInstall) { UINT uiResult; HRESULT hr; BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL)); CString sSequenceFilename; assert(0); // Attach debugger here, or press "Ignore"! uiResult = ::MsiGetProperty(hInstall, _T("CustomActionData"), sSequenceFilename); if (uiResult == ERROR_SUCCESS) { CMSITSCAOpList lstOperations; // Load operation sequence. hr = lstOperations.LoadFromFile(sSequenceFilename); if (SUCCEEDED(hr)) { CMSITSCASession session; BOOL bIsCleanup = ::MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || ::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK); session.m_hInstall = hInstall; // In case of commit/rollback, continue sequence on error, to do as much cleanup as possible. session.m_bContinueOnError = bIsCleanup; // 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; sSequenceFilenameRB.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); sSequenceFilenameCM.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension); // 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)); // 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 { // No cleanup support required. uiResult = ERROR_SUCCESS; } } else { // Execution failed. uiResult = HRESULT_CODE(hr); } if (uiResult != ERROR_SUCCESS) { // Perform the cleanup now. The rollback script might not have been written to file yet. // And even if it was, the rollback action might not get invoked at all (if scheduled in InstallExecuteSequence later than this action). session.m_bContinueOnError = TRUE; session.m_bRollbackEnabled = FALSE; verify(SUCCEEDED(session.m_olRollback.Execute(&session))); } } else { // Task scheduler creation failed. uiResult = HRESULT_CODE(hr); } } else { // Sequence loading failed. Don't panic => do nothing. uiResult = ERROR_SUCCESS; } lstOperations.Free(); ::DeleteFile(sSequenceFilename); } else { // Couldn't get CustomActionData property. } if (bIsCoInitialized) ::CoUninitialize(); return uiResult; }