#include "StdAfx.h" #pragma comment(lib, "msi.lib") #pragma comment(lib, "mstask.lib") //////////////////////////////////////////////////////////////////////////// // Local constants //////////////////////////////////////////////////////////////////////////// #define MSITSCA_TASK_TICK_SIZE 2048 //////////////////////////////////////////////////////////////////////////// // Local function declarations //////////////////////////////////////////////////////////////////////////// inline UINT MsiFormatAndStoreString(MSIHANDLE hInstall, MSIHANDLE hRecord, unsigned int iField, CAtlFile &fSequence, CStringW &sValue); inline UINT MsiStoreInteger(MSIHANDLE hRecord, unsigned int iField, BOOL bIsNullable, CAtlFile &fSequence, int *piValue = NULL); //////////////////////////////////////////////////////////////////////////// // 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) { UNREFERENCED_PARAMETER(hInstall); HRESULT hr; BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL)); CString sSequenceFilename, sComponent; CStringW sValue; CAtlFile fSequence; PMSIHANDLE hDatabase, hViewST; PMSIHANDLE hRecordProg = ::MsiCreateRecord(2); BOOL bRollbackEnabled; assert(hRecordProg); assert(0); // Attach debugger here, or press "Ignore"! // 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); // Prepare our own sequence script. { LPTSTR szBuffer = sSequenceFilename.GetBuffer(MAX_PATH); assert(szBuffer); ::GetTempPath(MAX_PATH, szBuffer); ::GetTempFileName(szBuffer, _T("TS"), 0, szBuffer); sSequenceFilename.ReleaseBuffer(); } hr = fSequence.Create(sSequenceFilename, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN); if (FAILED(hr)) goto error1; // Save the rollback enabled state. hr = HRESULT_FROM_WIN32(::MsiGetProperty(hInstall, _T("RollbackDisabled"), sValue)); bRollbackEnabled = SUCCEEDED(hr) ? _wtoi(sValue) || !sValue.IsEmpty() && towlower(sValue.GetAt(0)) == L'y' ? FALSE : TRUE : TRUE; hr = fSequence << (int)bRollbackEnabled; hDatabase = ::MsiGetActiveDatabase(hInstall); if (!hDatabase) { hr = E_INVALIDARG; goto error2; } // Execute a query to get tasks. hr = HRESULT_FROM_WIN32(::MsiDatabaseOpenView(hDatabase, _T("SELECT Task,DisplayName,Application,Parameters,WorkingDir,Flags,Priority,User,Password,Description,IdleMin,IdleDeadline,MaxRuntime,Condition,Component_ FROM ScheduledTask"), &hViewST)); if (FAILED(hr)) goto error2; hr = HRESULT_FROM_WIN32(::MsiViewExecute(hViewST, NULL)); if (FAILED(hr)) goto error2; for (;;) { PMSIHANDLE hRecord, hViewTT; INSTALLSTATE iInstalled, iAction; MSICONDITION condition; ULONGLONG posTriggerCount, posEOF; UINT nTriggers = 0; // Fetch one record from the view. hr = HRESULT_FROM_WIN32(::MsiViewFetch(hViewST, &hRecord)); if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { hr = S_OK; break; } else if (FAILED(hr)) break; // Read and evaluate condition. hr = HRESULT_FROM_WIN32(::MsiRecordGetString(hRecord, 14, sValue)); if (FAILED(hr)) break; condition = ::MsiEvaluateCondition(hInstall, sValue); if (condition == MSICONDITION_FALSE) continue; else if (condition == MSICONDITION_ERROR) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_FIELD); break; } // Read Component ID. hr = HRESULT_FROM_WIN32(::MsiRecordGetString(hRecord, 15, sComponent)); if (FAILED(hr)) break; // Get component state and save for deferred processing. hr = HRESULT_FROM_WIN32(::MsiGetComponentState(hInstall, sComponent, &iInstalled, &iAction)); if (FAILED(hr)) break; hr = fSequence << (int)iInstalled; if (FAILED(hr)) break; hr = fSequence << (int)iAction; if (FAILED(hr)) break; // Read and store task's data to script file. hr = HRESULT_FROM_WIN32(::MsiFormatAndStoreString(hInstall, hRecord, 2, fSequence, sValue)); // DisplayName if (FAILED(hr)) break; if (iAction >= 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; // 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; // 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; for (;; nTriggers++) { PMSIHANDLE hRecord; int iStartTime, iStartTimeRand; // 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; // Read StartTime and StartTimeRand iStartTime = ::MsiRecordGetInteger(hRecord, 4); if (iStartTime == MSI_NULL_INTEGER) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_FIELD); break; } 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; } 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; } } 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); if (bIsCoInitialized) ::CoUninitialize(); return SUCCEEDED(hr) ? ERROR_SUCCESS : hr == E_ABORT ? ERROR_INSTALL_USEREXIT : ERROR_INVALID_DATA; } UINT MSITSCA_API InstallScheduledTasks(MSIHANDLE hInstall) { assert(::MsiGetMode(hInstall, MSIRUNMODE_SCHEDULED)); 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); 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; } // 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); // 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); // 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 rollback-enabled state. { int iValue; hr = fSequence >> iValue; if (FAILED(hr)) goto error3; bRollbackEnabled = iValue ? TRUE : FALSE; } hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); if (FAILED(hr)) goto error3; 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)); } // 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 (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 { // 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); } } 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; ::DeleteFile(sSequenceFilename); } 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; }