diff --git a/FileOp.cpp b/FileOp.cpp new file mode 100644 index 0000000..b49de6d --- /dev/null +++ b/FileOp.cpp @@ -0,0 +1,88 @@ +#include "stdafx.h" + + +namespace MSICA { + +//////////////////////////////////////////////////////////////////////////// +// COpFileDelete +//////////////////////////////////////////////////////////////////////////// + +COpFileDelete::COpFileDelete(LPCWSTR pszFileName, int iTicks) : + COpTypeSingleString(pszFileName, iTicks) +{ +} + + +HRESULT COpFileDelete::Execute(CSession *pSession) +{ + DWORD dwError; + + if (pSession->m_bRollbackEnabled) { + ATL::CAtlStringW sBackupName; + UINT uiCount = 0; + + 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_ALREADY_EXISTS); + if (dwError == ERROR_SUCCESS) { + // Order rollback action to restore from backup copy. + pSession->m_olRollback.AddHead(new COpFileMove(sBackupName, m_sValue)); + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new COpFileDelete(sBackupName)); + } + } else { + // Delete the file. + dwError = ::DeleteFileW(m_sValue) ? ERROR_SUCCESS : ::GetLastError(); + } + + if (dwError == ERROR_SUCCESS || dwError == ERROR_FILE_NOT_FOUND) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(3); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_FILE_DELETE_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 3, dwError ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(dwError); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// COpFileMove +//////////////////////////////////////////////////////////////////////////// + +COpFileMove::COpFileMove(LPCWSTR pszFileSrc, LPCWSTR pszFileDst, int iTicks) : + COpTypeSrcDstString(pszFileSrc, pszFileDst, iTicks) +{ +} + + +HRESULT COpFileMove::Execute(CSession *pSession) +{ + DWORD dwError; + + // Move the file. + dwError = ::MoveFileW(m_sValue1, m_sValue2) ? ERROR_SUCCESS : ::GetLastError(); + if (dwError == ERROR_SUCCESS) { + if (pSession->m_bRollbackEnabled) { + // Order rollback action to move it back. + pSession->m_olRollback.AddHead(new COpFileMove(m_sValue2, m_sValue1)); + } + + return S_OK; + } else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(4); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_FILE_MOVE_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue1 ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue2 ); + ::MsiRecordSetInteger(hRecordProg, 4, dwError ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(dwError); + } +} + +} // namespace MSICA diff --git a/MSICALib.cpp b/MSICALib.cpp new file mode 100644 index 0000000..94e4326 --- /dev/null +++ b/MSICALib.cpp @@ -0,0 +1,184 @@ +#include "stdafx.h" + +#pragma comment(lib, "msi.lib") + + +namespace MSICA { + +//////////////////////////////////////////////////////////////////////////// +// COperation +//////////////////////////////////////////////////////////////////////////// + +COperation::COperation(int iTicks) : + m_iTicks(iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeSingleString +//////////////////////////////////////////////////////////////////////////// + +COpTypeSingleString::COpTypeSingleString(LPCWSTR pszValue, int iTicks) : + m_sValue(pszValue), + COperation(iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeSrcDstString +//////////////////////////////////////////////////////////////////////////// + +COpTypeSrcDstString::COpTypeSrcDstString(LPCWSTR pszValue1, LPCWSTR pszValue2, int iTicks) : + m_sValue1(pszValue1), + m_sValue2(pszValue2), + COperation(iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeBoolean +//////////////////////////////////////////////////////////////////////////// + +COpTypeBoolean::COpTypeBoolean(BOOL bValue, int iTicks) : + m_bValue(bValue), + COperation(iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRollbackEnable +//////////////////////////////////////////////////////////////////////////// + +COpRollbackEnable::COpRollbackEnable(BOOL bEnable, int iTicks) : + COpTypeBoolean(bEnable, iTicks) +{ +} + + +HRESULT COpRollbackEnable::Execute(CSession *pSession) +{ + pSession->m_bRollbackEnabled = m_bValue; + return S_OK; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpList +//////////////////////////////////////////////////////////////////////////// + +COpList::COpList(int iTicks) : + COperation(iTicks), + ATL::CAtlList(sizeof(COperation*)) +{ +} + + +void COpList::Free() +{ + POSITION pos; + + for (pos = GetHeadPosition(); pos;) { + COperation *pOp = GetNext(pos); + COpList *pOpList = dynamic_cast(pOp); + + if (pOpList) { + // Recursivelly free sublists. + pOpList->Free(); + } + delete pOp; + } + + RemoveAll(); +} + + +HRESULT COpList::LoadFromFile(LPCTSTR pszFileName) +{ + HRESULT hr; + ATL::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 COpList::SaveToFile(LPCTSTR pszFileName) const +{ + HRESULT hr; + ATL::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 COpList::Execute(CSession *pSession) +{ + POSITION pos; + HRESULT hr; + PMSIHANDLE hRecordProg = ::MsiCreateRecord(3); + + // Tell the installer to use explicit progress messages. + ::MsiRecordSetInteger(hRecordProg, 1, 1); + ::MsiRecordSetInteger(hRecordProg, 2, 1); + ::MsiRecordSetInteger(hRecordProg, 3, 0); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg); + + // Prepare hRecordProg for progress messages. + ::MsiRecordSetInteger(hRecordProg, 1, 2); + ::MsiRecordSetInteger(hRecordProg, 3, 0); + + for (pos = GetHeadPosition(); pos;) { + COperation *pOp = GetNext(pos); + + hr = pOp->Execute(pSession); + if (!pSession->m_bContinueOnError && FAILED(hr)) { + // Operation failed. Its Execute() method should have sent error message to Installer. + // Therefore, just quit here. + return hr; + } + + ::MsiRecordSetInteger(hRecordProg, 2, pOp->m_iTicks); + if (::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) + return AtlHresultFromWin32(ERROR_INSTALL_USEREXIT); + } + + ::MsiRecordSetInteger(hRecordProg, 2, m_iTicks); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg); + + return S_OK; +} + + +//////////////////////////////////////////////////////////////////////////// +// CSession +//////////////////////////////////////////////////////////////////////////// + +CSession::CSession() : + m_bContinueOnError(FALSE), + m_bRollbackEnabled(FALSE) +{ +} + + +CSession::~CSession() +{ + m_olRollback.Free(); + m_olCommit.Free(); +} + +} // namespace MSICA diff --git a/MSICALib.h b/MSICALib.h new file mode 100644 index 0000000..fbcb6a4 --- /dev/null +++ b/MSICALib.h @@ -0,0 +1,1429 @@ +#ifndef __MSICALib_H__ +#define __MSICALib_H__ + +//////////////////////////////////////////////////////////////////////////// +// Version +//////////////////////////////////////////////////////////////////////////// + +#define MSICALib_VERSION 0x02000000 + +#define MSICALib_VERSION_MAJ 2 +#define MSICALib_VERSION_MIN 0 +#define MSICALib_VERSION_REV 0 + +#define MSICALib_VERSION_STR "2.0" + + +#if !defined(RC_INVOKED) && !defined(MIDL_PASS) + +#include +#include +#include +#include +#include +#include +#include + + +//////////////////////////////////////////////////////////////////// +// Error codes (next unused 2569L) +//////////////////////////////////////////////////////////////////// + +#define ERROR_INSTALL_DATABASE_OPEN 2550L +#define ERROR_INSTALL_PROPERTY_SET 2553L +#define ERROR_INSTALL_SCRIPT_WRITE 2552L +#define ERROR_INSTALL_SCRIPT_READ 2560L +#define ERROR_INSTALL_FILE_DELETE_FAILED 2554L +#define ERROR_INSTALL_FILE_MOVE_FAILED 2555L +#define ERROR_INSTALL_REGKEY_CREATE_FAILED 2561L +#define ERROR_INSTALL_REGKEY_COPY_FAILED 2562L +#define ERROR_INSTALL_REGKEY_PROBING_FAILED 2563L +#define ERROR_INSTALL_REGKEY_DELETE_FAILED 2564L +#define ERROR_INSTALL_REGKEY_SETVALUE_FAILED 2565L +#define ERROR_INSTALL_REGKEY_DELETEVALUE_FAILED 2567L +#define ERROR_INSTALL_REGKEY_COPYVALUE_FAILED 2568L +#define ERROR_INSTALL_REGKEY_PROBINGVAL_FAILED 2566L +#define ERROR_INSTALL_TASK_CREATE_FAILED 2556L +#define ERROR_INSTALL_TASK_DELETE_FAILED 2557L +#define ERROR_INSTALL_TASK_ENABLE_FAILED 2558L +#define ERROR_INSTALL_TASK_COPY_FAILED 2559L + +// Errors reported by MSITSCA +#define ERROR_INSTALL_SCHEDULED_TASKS_OPLIST_CREATE 2551L + + +namespace MSICA { + + +//////////////////////////////////////////////////////////////////////////// +// Forward declarations +//////////////////////////////////////////////////////////////////////////// + +class CSession; + + +//////////////////////////////////////////////////////////////////////////// +// COperation +//////////////////////////////////////////////////////////////////////////// + +class COperation +{ +public: + COperation(int iTicks = 0); + + virtual HRESULT Execute(CSession *pSession) = 0; + + friend class COpList; + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COperation &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COperation &op); + +protected: + int m_iTicks; // Number of ticks on a progress bar required for this action execution +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeSingleString +//////////////////////////////////////////////////////////////////////////// + +class COpTypeSingleString : public COperation +{ +public: + COpTypeSingleString(LPCWSTR pszValue = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeSingleString &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeSingleString &op); + +protected: + ATL::CAtlStringW m_sValue; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeSrcDstString +//////////////////////////////////////////////////////////////////////////// + +class COpTypeSrcDstString : public COperation +{ +public: + COpTypeSrcDstString(LPCWSTR pszValue1 = L"", LPCWSTR pszValue2 = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeSrcDstString &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeSrcDstString &op); + +protected: + ATL::CAtlStringW m_sValue1; + ATL::CAtlStringW m_sValue2; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTypeBoolean +//////////////////////////////////////////////////////////////////////////// + +class COpTypeBoolean : public COperation +{ +public: + COpTypeBoolean(BOOL bValue = TRUE, int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeBoolean &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeBoolean &op); + +protected: + BOOL m_bValue; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRollbackEnable +//////////////////////////////////////////////////////////////////////////// + +class COpRollbackEnable : public COpTypeBoolean +{ +public: + COpRollbackEnable(BOOL bEnable = TRUE, int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpFileDelete +//////////////////////////////////////////////////////////////////////////// + +class COpFileDelete : public COpTypeSingleString +{ +public: + COpFileDelete(LPCWSTR pszFileName = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpFileMove +//////////////////////////////////////////////////////////////////////////// + +class COpFileMove : public COpTypeSrcDstString +{ +public: + COpFileMove(LPCWSTR pszFileSrc = L"", LPCWSTR pszFileDst = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeySingle +//////////////////////////////////////////////////////////////////////////// + +class COpRegKeySingle : public COpTypeSingleString +{ +public: + COpRegKeySingle(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegKeySingle &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpRegKeySingle &op); + +protected: + HKEY m_hKeyRoot; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeySrcDst +//////////////////////////////////////////////////////////////////////////// + +class COpRegKeySrcDst : public COpTypeSrcDstString +{ +public: + COpRegKeySrcDst(HKEY hKeyRoot = NULL, LPCWSTR pszKeyNameSrc = L"", LPCWSTR pszKeyNameDst = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegKeySrcDst &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpRegKeySrcDst &op); + +protected: + HKEY m_hKeyRoot; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyCreate +//////////////////////////////////////////////////////////////////////////// + +class COpRegKeyCreate : public COpRegKeySingle +{ +public: + COpRegKeyCreate(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyCopy +//////////////////////////////////////////////////////////////////////////// + +class COpRegKeyCopy : public COpRegKeySrcDst +{ +public: + COpRegKeyCopy(HKEY hKeyRoot = NULL, LPCWSTR pszKeyNameSrc = L"", LPCWSTR pszKeyNameDst = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); + +private: + static LONG CopyKeyRecursively(HKEY hKeyRoot, LPCWSTR pszKeyNameSrc, LPCWSTR pszKeyNameDst, REGSAM samAdditional); + static LONG CopyKeyRecursively(HKEY hKeySrc, HKEY hKeyDst, REGSAM samAdditional); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyDelete +//////////////////////////////////////////////////////////////////////////// + +class COpRegKeyDelete : public COpRegKeySingle +{ +public: + COpRegKeyDelete(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); + +private: + static LONG DeleteKeyRecursively(HKEY hKeyRoot, LPCWSTR pszKeyName, REGSAM samAdditional); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueSingle +//////////////////////////////////////////////////////////////////////////// + +class COpRegValueSingle : public COpRegKeySingle +{ +public: + COpRegValueSingle(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", LPCWSTR pszValueName = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueSingle &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueSingle &op); + +protected: + ATL::CAtlStringW m_sValueName; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueSrcDst +//////////////////////////////////////////////////////////////////////////// + +class COpRegValueSrcDst : public COpRegKeySingle +{ +public: + COpRegValueSrcDst(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", LPCWSTR pszValueNameSrc = L"", LPCWSTR pszValueNameDst = L"", int iTicks = 0); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueSrcDst &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueSrcDst &op); + +protected: + ATL::CAtlStringW m_sValueName1; + ATL::CAtlStringW m_sValueName2; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueCreate +//////////////////////////////////////////////////////////////////////////// + +class COpRegValueCreate : public COpRegValueSingle +{ +public: + COpRegValueCreate(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", LPCWSTR pszValueName = L"", int iTicks = 0); + COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, DWORD dwData, int iTicks = 0); + COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, LPCVOID lpData, SIZE_T nSize, int iTicks = 0); + COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, LPCWSTR pszData, int iTicks = 0); + COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, DWORDLONG qwData, int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueCreate &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueCreate &op); + +protected: + DWORD m_dwType; + ATL::CAtlStringW m_sData; + ATL::CAtlArray m_binData; + DWORD m_dwData; + ATL::CAtlArray m_szData; + DWORDLONG m_qwData; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueCopy +//////////////////////////////////////////////////////////////////////////// + +class COpRegValueCopy : public COpRegValueSrcDst +{ +public: + COpRegValueCopy(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", LPCWSTR pszValueNameSrc = L"", LPCWSTR pszValueNameDst = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueDelete +//////////////////////////////////////////////////////////////////////////// + +class COpRegValueDelete : public COpRegValueSingle +{ +public: + COpRegValueDelete(HKEY hKeyRoot = NULL, LPCWSTR pszKeyName = L"", LPCWSTR pszValueName = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskCreate +//////////////////////////////////////////////////////////////////////////// + +class COpTaskCreate : public COpTypeSingleString +{ +public: + COpTaskCreate(LPCWSTR pszTaskName = L"", int iTicks = 0); + virtual ~COpTaskCreate(); + virtual HRESULT Execute(CSession *pSession); + + UINT SetFromRecord(MSIHANDLE hInstall, MSIHANDLE hRecord); + UINT SetTriggersFromView(MSIHANDLE hView); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpTaskCreate &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpTaskCreate &op); + +protected: + ATL::CAtlStringW m_sApplicationName; + ATL::CAtlStringW m_sParameters; + ATL::CAtlStringW m_sWorkingDirectory; + ATL::CAtlStringW m_sAuthor; + ATL::CAtlStringW m_sComment; + DWORD m_dwFlags; + DWORD m_dwPriority; + ATL::CAtlStringW m_sAccountName; + ATL::CAtlStringW m_sPassword; + WORD m_wIdleMinutes; + WORD m_wDeadlineMinutes; + DWORD m_dwMaxRuntimeMS; + + ATL::CAtlList m_lTriggers; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskDelete +//////////////////////////////////////////////////////////////////////////// + +class COpTaskDelete : public COpTypeSingleString +{ +public: + COpTaskDelete(LPCWSTR pszTaskName = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskEnable +//////////////////////////////////////////////////////////////////////////// + +class COpTaskEnable : public COpTypeSingleString +{ +public: + COpTaskEnable(LPCWSTR pszTaskName = L"", BOOL bEnable = TRUE, int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpTaskEnable &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpTaskEnable &op); + +protected: + BOOL m_bEnable; +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskCopy +//////////////////////////////////////////////////////////////////////////// + +class COpTaskCopy : public COpTypeSrcDstString +{ +public: + COpTaskCopy(LPCWSTR pszTaskSrc = L"", LPCWSTR pszTaskDst = L"", int iTicks = 0); + virtual HRESULT Execute(CSession *pSession); +}; + + +//////////////////////////////////////////////////////////////////////////// +// COpList +//////////////////////////////////////////////////////////////////////////// + +class COpList : public COperation, public ATL::CAtlList +{ +public: + COpList(int iTicks = 0); + + void Free(); + HRESULT LoadFromFile(LPCTSTR pszFileName); + HRESULT SaveToFile(LPCTSTR pszFileName) const; + + virtual HRESULT Execute(CSession *pSession); + + friend inline HRESULT operator <<(ATL::CAtlFile &f, const COpList &op); + friend inline HRESULT operator >>(ATL::CAtlFile &f, COpList &op); + +protected: + enum OPERATION { + OP_ROLLBACK_ENABLE = 1, + OP_FILE_DELETE, + OP_FILE_MOVE, + OP_REG_KEY_CREATE, + OP_REG_KEY_COPY, + OP_REG_KEY_DELETE, + OP_REG_VALUE_CREATE, + OP_REG_VALUE_COPY, + OP_REG_VALUE_DELETE, + OP_TASK_CREATE, + OP_TASK_DELETE, + OP_TASK_ENABLE, + OP_TASK_COPY, + OP_SUBLIST + }; + +protected: + template inline static HRESULT Save(ATL::CAtlFile &f, const COperation *p); + template inline HRESULT LoadAndAddTail(ATL::CAtlFile &f); +}; + + +//////////////////////////////////////////////////////////////////////////// +// CSession +//////////////////////////////////////////////////////////////////////////// + +class CSession +{ +public: + CSession(); + virtual ~CSession(); + + MSIHANDLE m_hInstall; // Installer handle + BOOL m_bContinueOnError; // Continue execution on operation error? + BOOL m_bRollbackEnabled; // Is rollback enabled? + COpList m_olRollback; // Rollback operation list + COpList m_olCommit; // Commit operation list +}; + +} // namespace MSICA + + +//////////////////////////////////////////////////////////////////// +// Local includes +//////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +//////////////////////////////////////////////////////////////////// +// Inline Functions +//////////////////////////////////////////////////////////////////// + +inline UINT MsiGetPropertyA(MSIHANDLE hInstall, LPCSTR szName, ATL::CAtlStringA &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the actual string length first. + uiResult = ::MsiGetPropertyA(hInstall, szName, "", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to read the string data into and read it. + LPSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiGetPropertyA(hInstall, szName, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The string in database is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiGetPropertyW(MSIHANDLE hInstall, LPCWSTR szName, ATL::CAtlStringW &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the actual string length first. + uiResult = ::MsiGetPropertyW(hInstall, szName, L"", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to read the string data into and read it. + LPWSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiGetPropertyW(hInstall, szName, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The string in database is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiRecordGetStringA(MSIHANDLE hRecord, unsigned int iField, ATL::CAtlStringA &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the actual string length first. + uiResult = ::MsiRecordGetStringA(hRecord, iField, "", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to read the string data into and read it. + LPSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiRecordGetStringA(hRecord, iField, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The string in database is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiRecordGetStringW(MSIHANDLE hRecord, unsigned int iField, ATL::CAtlStringW &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the actual string length first. + uiResult = ::MsiRecordGetStringW(hRecord, iField, L"", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to read the string data into and read it. + LPWSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiRecordGetStringW(hRecord, iField, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The string in database is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiFormatRecordA(MSIHANDLE hInstall, MSIHANDLE hRecord, ATL::CAtlStringA &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the final string length first. + uiResult = ::MsiFormatRecordA(hInstall, hRecord, "", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to format the string data into and read it. + LPSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiFormatRecordA(hInstall, hRecord, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The result is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiFormatRecordW(MSIHANDLE hInstall, MSIHANDLE hRecord, ATL::CAtlStringW &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the final string length first. + uiResult = ::MsiFormatRecordW(hInstall, hRecord, L"", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to format the string data into and read it. + LPWSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiFormatRecordW(hInstall, hRecord, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The result is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiRecordFormatStringA(MSIHANDLE hInstall, MSIHANDLE hRecord, unsigned int iField, ATL::CAtlStringA &sValue) +{ + UINT uiResult; + PMSIHANDLE hRecordEx; + + // Read string to format. + uiResult = ::MsiRecordGetStringA(hRecord, iField, sValue); + if (uiResult != ERROR_SUCCESS) return uiResult; + + // If the string is empty, there's nothing left to do. + if (sValue.IsEmpty()) return ERROR_SUCCESS; + + // Create a record. + hRecordEx = ::MsiCreateRecord(1); + if (!hRecordEx) return ERROR_INVALID_HANDLE; + + // Populate record with data. + uiResult = ::MsiRecordSetStringA(hRecordEx, 0, sValue); + if (uiResult != ERROR_SUCCESS) return uiResult; + + // Do the formatting. + return ::MsiFormatRecordA(hInstall, hRecordEx, sValue); +} + + +inline UINT MsiRecordFormatStringW(MSIHANDLE hInstall, MSIHANDLE hRecord, unsigned int iField, ATL::CAtlStringW &sValue) +{ + UINT uiResult; + PMSIHANDLE hRecordEx; + + // Read string to format. + uiResult = ::MsiRecordGetStringW(hRecord, iField, sValue); + if (uiResult != ERROR_SUCCESS) return uiResult; + + // If the string is empty, there's nothing left to do. + if (sValue.IsEmpty()) return ERROR_SUCCESS; + + // Create a record. + hRecordEx = ::MsiCreateRecord(1); + if (!hRecordEx) return ERROR_INVALID_HANDLE; + + // Populate record with data. + uiResult = ::MsiRecordSetStringW(hRecordEx, 0, sValue); + if (uiResult != ERROR_SUCCESS) return uiResult; + + // Do the formatting. + return ::MsiFormatRecordW(hInstall, hRecordEx, sValue); +} + +#ifdef UNICODE +#define MsiRecordFormatString MsiRecordFormatStringW +#else +#define MsiRecordFormatString MsiRecordFormatStringA +#endif // !UNICODE + + +inline UINT MsiGetTargetPathA(MSIHANDLE hInstall, LPCSTR szFolder, ATL::CAtlStringA &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the final string length first. + uiResult = ::MsiGetTargetPathA(hInstall, szFolder, "", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to format the string data into and read it. + LPSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiGetTargetPathA(hInstall, szFolder, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The result is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +inline UINT MsiGetTargetPathW(MSIHANDLE hInstall, LPCWSTR szFolder, ATL::CAtlStringW &sValue) +{ + DWORD dwSize = 0; + UINT uiResult; + + // Query the final string length first. + uiResult = ::MsiGetTargetPathW(hInstall, szFolder, L"", &dwSize); + if (uiResult == ERROR_MORE_DATA) { + // Prepare the buffer to format the string data into and read it. + LPWSTR szBuffer = sValue.GetBuffer(dwSize++); + if (!szBuffer) return ERROR_OUTOFMEMORY; + uiResult = ::MsiGetTargetPathW(hInstall, szFolder, szBuffer, &dwSize); + sValue.ReleaseBuffer(uiResult == ERROR_SUCCESS ? dwSize : 0); + return uiResult; + } else if (uiResult == ERROR_SUCCESS) { + // The result is empty. + sValue.Empty(); + return ERROR_SUCCESS; + } else { + // Return error code. + return uiResult; + } +} + + +namespace MSICA { + +//////////////////////////////////////////////////////////////////////////// +// Inline operators +//////////////////////////////////////////////////////////////////////////// + +inline HRESULT operator <<(ATL::CAtlFile &f, int i) +{ + HRESULT hr; + DWORD dwWritten; + + hr = f.Write(&i, sizeof(int), &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(int) ? hr : E_FAIL : hr; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, int &i) +{ + HRESULT hr; + DWORD dwRead; + + hr = f.Read(&i, sizeof(int), dwRead); + return SUCCEEDED(hr) ? dwRead == sizeof(int) ? hr : E_FAIL : hr; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, DWORDLONG i) +{ + HRESULT hr; + DWORD dwWritten; + + hr = f.Write(&i, sizeof(DWORDLONG), &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(DWORDLONG) ? hr : E_FAIL : hr; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, DWORDLONG &i) +{ + HRESULT hr; + DWORD dwRead; + + hr = f.Read(&i, sizeof(DWORDLONG), dwRead); + return SUCCEEDED(hr) ? dwRead == sizeof(DWORDLONG) ? hr : E_FAIL : hr; +} + + +template +inline HRESULT operator <<(ATL::CAtlFile &f, const ATL::CAtlArray &a) +{ + HRESULT hr; + DWORD dwCount = (DWORD)a.GetCount(), dwWritten; + + // Write element count. + hr = f.Write(&dwCount, sizeof(DWORD), &dwWritten); + if (FAILED(hr)) return hr; + if (dwWritten < sizeof(DWORD)) return E_FAIL; + + // Write data. + hr = f.Write(a.GetData(), sizeof(E) * dwCount, &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(E) * dwCount ? hr : E_FAIL : hr; +} + + +template +inline HRESULT operator >>(ATL::CAtlFile &f, ATL::CAtlArray &a) +{ + HRESULT hr; + DWORD dwCount, dwRead; + + // Read element count as 32-bit integer. + hr = f.Read(&dwCount, sizeof(DWORD), dwRead); + if (FAILED(hr)) return hr; + if (dwRead < sizeof(DWORD)) return E_FAIL; + + // Allocate the buffer. + if (!a.SetCount(dwCount)) return E_OUTOFMEMORY; + + // Read data. + hr = f.Read(a.GetData(), sizeof(E) * dwCount, dwRead); + if (SUCCEEDED(hr)) a.SetCount(dwRead / sizeof(E)); + return hr; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const ATL::CAtlStringA &str) +{ + HRESULT hr; + int iLength = str.GetLength(); + DWORD dwWritten; + + // Write string length (in characters) as 32-bit integer. + hr = f.Write(&iLength, sizeof(int), &dwWritten); + if (FAILED(hr)) return hr; + if (dwWritten < sizeof(int)) return E_FAIL; + + // Write string data (without terminator). + hr = f.Write((LPCSTR)str, sizeof(CHAR) * iLength, &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(CHAR) * iLength ? hr : E_FAIL : hr; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, ATL::CAtlStringA &str) +{ + HRESULT hr; + int iLength; + LPSTR buf; + DWORD dwRead; + + // Read string length (in characters) as 32-bit integer. + hr = f.Read(&iLength, sizeof(int), dwRead); + if (FAILED(hr)) return hr; + if (dwRead < sizeof(int)) return E_FAIL; + + // Allocate the buffer. + buf = str.GetBuffer(iLength); + if (!buf) return E_OUTOFMEMORY; + + // Read string data (without terminator). + hr = f.Read(buf, sizeof(CHAR) * iLength, dwRead); + str.ReleaseBuffer(SUCCEEDED(hr) ? dwRead / sizeof(CHAR) : 0); + return hr; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const ATL::CAtlStringW &str) +{ + HRESULT hr; + int iLength = str.GetLength(); + DWORD dwWritten; + + // Write string length (in characters) as 32-bit integer. + hr = f.Write(&iLength, sizeof(int), &dwWritten); + if (FAILED(hr)) return hr; + if (dwWritten < sizeof(int)) return E_FAIL; + + // Write string data (without terminator). + hr = f.Write((LPCWSTR)str, sizeof(WCHAR) * iLength, &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(WCHAR) * iLength ? hr : E_FAIL : hr; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, ATL::CAtlStringW &str) +{ + HRESULT hr; + int iLength; + LPWSTR buf; + DWORD dwRead; + + // Read string length (in characters) as 32-bit integer. + hr = f.Read(&iLength, sizeof(int), dwRead); + if (FAILED(hr)) return hr; + if (dwRead < sizeof(int)) return E_FAIL; + + // Allocate the buffer. + buf = str.GetBuffer(iLength); + if (!buf) return E_OUTOFMEMORY; + + // Read string data (without terminator). + hr = f.Read(buf, sizeof(WCHAR) * iLength, dwRead); + str.ReleaseBuffer(SUCCEEDED(hr) ? dwRead / sizeof(WCHAR) : 0); + return hr; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const TASK_TRIGGER &ttData) +{ + HRESULT hr; + DWORD dwWritten; + + hr = f.Write(&ttData, sizeof(TASK_TRIGGER), &dwWritten); + return SUCCEEDED(hr) ? dwWritten == sizeof(TASK_TRIGGER) ? hr : E_FAIL : hr; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, TASK_TRIGGER &ttData) +{ + HRESULT hr; + DWORD dwRead; + + hr = f.Read(&ttData, sizeof(TASK_TRIGGER), dwRead); + return SUCCEEDED(hr) ? dwRead == sizeof(TASK_TRIGGER) ? hr : E_FAIL : hr; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COperation &op) +{ + return f << op.m_iTicks; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COperation &op) +{ + return f >> op.m_iTicks; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeSingleString &op) +{ + HRESULT hr; + + hr = f << (const COperation &)op; if (FAILED(hr)) return hr; + hr = f << op.m_sValue; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeSingleString &op) +{ + HRESULT hr; + + hr = f >> (COperation &)op; if (FAILED(hr)) return hr; + hr = f >> op.m_sValue; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeSrcDstString &op) +{ + HRESULT hr; + + hr = f << (const COperation &)op; if (FAILED(hr)) return hr; + hr = f << op.m_sValue1; if (FAILED(hr)) return hr; + hr = f << op.m_sValue2; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeSrcDstString &op) +{ + HRESULT hr; + + hr = f >> (COperation &)op; if (FAILED(hr)) return hr; + hr = f >> op.m_sValue1; if (FAILED(hr)) return hr; + hr = f >> op.m_sValue2; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpTypeBoolean &op) +{ + HRESULT hr; + + hr = f << (const COperation &)op; if (FAILED(hr)) return hr; + hr = f << (int)(op.m_bValue); if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpTypeBoolean &op) +{ + HRESULT hr; + int iValue; + + hr = f >> (COperation &)op; if (FAILED(hr)) return hr; + hr = f >> iValue; if (FAILED(hr)) return hr; op.m_bValue = iValue ? TRUE : FALSE; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegKeySingle &op) +{ + HRESULT hr; + + hr = f << (const COpTypeSingleString &)op; if (FAILED(hr)) return hr; + hr = f << (int)(op.m_hKeyRoot); if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpRegKeySingle &op) +{ + HRESULT hr; + int iValue; + + hr = f >> (COpTypeSingleString &)op; if (FAILED(hr)) return hr; + hr = f >> iValue; if (FAILED(hr)) return hr; op.m_hKeyRoot = (HKEY)iValue; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegKeySrcDst &op) +{ + HRESULT hr; + + hr = f << (const COpTypeSrcDstString &)op; if (FAILED(hr)) return hr; + hr = f << (int)(op.m_hKeyRoot); if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpRegKeySrcDst &op) +{ + HRESULT hr; + int iValue; + + hr = f >> (COpTypeSrcDstString &)op; if (FAILED(hr)) return hr; + hr = f >> iValue; if (FAILED(hr)) return hr; op.m_hKeyRoot = (HKEY)iValue; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueSingle &op) +{ + HRESULT hr; + + hr = f << (const COpRegKeySingle &)op; if (FAILED(hr)) return hr; + hr = f << op.m_sValueName; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueSingle &op) +{ + HRESULT hr; + + hr = f >> (COpRegKeySingle &)op; if (FAILED(hr)) return hr; + hr = f >> op.m_sValueName; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueSrcDst &op) +{ + HRESULT hr; + + hr = f << (const COpRegKeySingle &)op; if (FAILED(hr)) return hr; + hr = f << op.m_sValueName1; if (FAILED(hr)) return hr; + hr = f << op.m_sValueName2; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueSrcDst &op) +{ + HRESULT hr; + + hr = f >> (COpRegKeySingle &)op; if (FAILED(hr)) return hr; + hr = f >> op.m_sValueName1; if (FAILED(hr)) return hr; + hr = f >> op.m_sValueName2; if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpRegValueCreate &op) +{ + HRESULT hr; + + hr = f << (const COpRegValueSingle &)op; if (FAILED(hr)) return hr; + hr = f << (int)(op.m_dwType); if (FAILED(hr)) return hr; + switch (op.m_dwType) { + case REG_SZ: + case REG_EXPAND_SZ: + case REG_LINK: hr = f << op.m_sData; if (FAILED(hr)) return hr; break; + case REG_BINARY: hr = f << op.m_binData; if (FAILED(hr)) return hr; break; + case REG_DWORD_LITTLE_ENDIAN: + case REG_DWORD_BIG_ENDIAN: hr = f << (int)(op.m_dwData); if (FAILED(hr)) return hr; break; + case REG_MULTI_SZ: hr = f << op.m_szData; if (FAILED(hr)) return hr; break; + case REG_QWORD_LITTLE_ENDIAN: hr = f << op.m_qwData; if (FAILED(hr)) return hr; break; + default: return E_UNEXPECTED; + } + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpRegValueCreate &op) +{ + HRESULT hr; + + hr = f >> (COpRegValueSingle &)op; if (FAILED(hr)) return hr; + hr = f >> (int&)(op.m_dwType); if (FAILED(hr)) return hr; + switch (op.m_dwType) { + case REG_SZ: + case REG_EXPAND_SZ: + case REG_LINK: hr = f >> op.m_sData; if (FAILED(hr)) return hr; break; + case REG_BINARY: hr = f >> op.m_binData; if (FAILED(hr)) return hr; break; + case REG_DWORD_LITTLE_ENDIAN: + case REG_DWORD_BIG_ENDIAN: hr = f >> (int&)(op.m_dwData); if (FAILED(hr)) return hr; break; + case REG_MULTI_SZ: hr = f >> op.m_szData; if (FAILED(hr)) return hr; break; + case REG_QWORD_LITTLE_ENDIAN: hr = f >> op.m_qwData; if (FAILED(hr)) return hr; break; + default: return E_UNEXPECTED; + } + + return S_OK; +} + + +inline HRESULT operator <<(ATL::CAtlFile &f, const COpTaskCreate &op) +{ + HRESULT hr; + POSITION pos; + + hr = f << (const COpTypeSingleString&)op; 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_sAuthor; 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 >>(ATL::CAtlFile &f, COpTaskCreate &op) +{ + HRESULT hr; + DWORD dwValue; + + hr = f >> (COpTypeSingleString&)op; 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_sAuthor; 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 <<(ATL::CAtlFile &f, const COpTaskEnable &op) +{ + HRESULT hr; + + hr = f << (const COpTypeSingleString&)op; if (FAILED(hr)) return hr; + hr = f << (int)(op.m_bEnable); if (FAILED(hr)) return hr; + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpTaskEnable &op) +{ + HRESULT hr; + int iTemp; + + hr = f >> (COpTypeSingleString&)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 <<(ATL::CAtlFile &f, const COpList &list) +{ + POSITION pos; + HRESULT hr; + + hr = f << (const COperation &)list; + if (FAILED(hr)) return hr; + + hr = f << (int)(list.GetCount()); + if (FAILED(hr)) return hr; + + for (pos = list.GetHeadPosition(); pos;) { + const COperation *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 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. + hr = E_UNEXPECTED; + } + + if (FAILED(hr)) return hr; + } + + return S_OK; +} + + +inline HRESULT operator >>(ATL::CAtlFile &f, COpList &list) +{ + HRESULT hr; + int iCount; + + hr = f >> (COperation &)list; + if (FAILED(hr)) return hr; + + hr = f >> iCount; + if (FAILED(hr)) return hr; + + while (iCount--) { + int iTemp; + + hr = f >> iTemp; + if (FAILED(hr)) return hr; + + switch ((COpList::OPERATION)iTemp) { + case COpList::OP_ROLLBACK_ENABLE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_FILE_DELETE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_FILE_MOVE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_KEY_CREATE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_KEY_COPY: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_KEY_DELETE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_VALUE_CREATE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_VALUE_COPY: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_REG_VALUE_DELETE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_TASK_CREATE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_TASK_DELETE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_TASK_ENABLE: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_TASK_COPY: + hr = list.LoadAndAddTail(f); + break; + case COpList::OP_SUBLIST: + hr = list.LoadAndAddTail(f); + break; + default: + // Unsupported type of operation. + hr = E_UNEXPECTED; + } + + if (FAILED(hr)) return hr; + } + + return S_OK; +} + + +//////////////////////////////////////////////////////////////////// +// Inline Functions +//////////////////////////////////////////////////////////////////// + + +inline BOOL IsWow64Process() +{ +#ifndef _WIN64 + // Find IsWow64Process() address in KERNEL32.DLL. + BOOL (WINAPI *_IsWow64Process)(__in HANDLE hProcess, __out PBOOL Wow64Process) = (BOOL(WINAPI*)(__in HANDLE, __out PBOOL))::GetProcAddress(::GetModuleHandle(_T("KERNEL32.DLL")), "IsWow64Process"); + + // See if our 32-bit process is running in 64-bit environment. + if (_IsWow64Process) { + BOOL bResult; + + // See, what IsWow64Process() says about current process. + if (_IsWow64Process(::GetCurrentProcess(), &bResult)) { + // Return result. + return bResult; + } else { + // IsWow64Process() returned an error. Assume, the process is not WOW64. + return FALSE; + } + } else { + // This KERNEL32.DLL doesn't know IsWow64Process().Definitely not a WOW64 process. + return FALSE; + } +#else + // 64-bit processes are never run as WOW64. + return FALSE; +#endif +} + +#ifndef _WIN64 +#endif + + + +//////////////////////////////////////////////////////////////////////////// +// Inline methods +//////////////////////////////////////////////////////////////////////////// + +template inline static HRESULT COpList::Save(ATL::CAtlFile &f, const COperation *p) +{ + HRESULT hr; + const T *pp = dynamic_cast(p); + if (!pp) return E_UNEXPECTED; + + hr = f << (int)ID; + if (FAILED(hr)) return hr; + + return f << *pp; +} + + +template inline HRESULT COpList::LoadAndAddTail(ATL::CAtlFile &f) +{ + HRESULT hr; + + // Create element. + T *p = new T(); + if (!p) return E_OUTOFMEMORY; + + // Load element from file. + hr = f >> *p; + if (FAILED(hr)) { + delete p; + return hr; + } + + // Add element. + AddTail(p); + return S_OK; +} + +} // namespace MSICA + + +#endif // RC_INVOKED +#endif // __MSICALib_H__ diff --git a/MSICALib.vcxproj b/MSICALib.vcxproj new file mode 100644 index 0000000..b7b730a --- /dev/null +++ b/MSICALib.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + {8552EE55-177E-4F51-B51B-BAF7D6462CDE} + Win32Proj + MSICALib + MSICALib + + + + StaticLibrary + Unicode + Static + + + StaticLibrary + Unicode + Static + + + StaticLibrary + Unicode + Static + + + StaticLibrary + Unicode + Static + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Windows + true + + + + + + Windows + true + + + + + + Windows + true + true + true + + + + + + + + Windows + true + true + true + + + + + + + + \ No newline at end of file diff --git a/MSICALib.vcxproj.filters b/MSICALib.vcxproj.filters new file mode 100644 index 0000000..ffdd07b --- /dev/null +++ b/MSICALib.vcxproj.filters @@ -0,0 +1,47 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/RegOp.cpp b/RegOp.cpp new file mode 100644 index 0000000..c872a7a --- /dev/null +++ b/RegOp.cpp @@ -0,0 +1,724 @@ +#include "stdafx.h" + + +namespace MSICA { + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeySingle +//////////////////////////////////////////////////////////////////////////// + +COpRegKeySingle::COpRegKeySingle(HKEY hKeyRoot, LPCWSTR pszKeyName, int iTicks) : + m_hKeyRoot(hKeyRoot), + COpTypeSingleString(pszKeyName, iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeySrcDst +//////////////////////////////////////////////////////////////////////////// + +COpRegKeySrcDst::COpRegKeySrcDst(HKEY hKeyRoot, LPCWSTR pszKeyNameSrc, LPCWSTR pszKeyNameDst, int iTicks) : + m_hKeyRoot(hKeyRoot), + COpTypeSrcDstString(pszKeyNameSrc, pszKeyNameDst, iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyCreate +//////////////////////////////////////////////////////////////////////////// + +COpRegKeyCreate::COpRegKeyCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, int iTicks) : COpRegKeySingle(hKeyRoot, pszKeyName, iTicks) +{ +} + + +HRESULT COpRegKeyCreate::Execute(CSession *pSession) +{ + LONG lResult; + REGSAM samAdditional = 0; + ATL::CAtlStringW sPartialName; + int iStart = 0; + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + samAdditional |= KEY_WOW64_64KEY; + } +#endif + + for (;;) { + HKEY hKey; + + int iStartNext = m_sValue.Find(L'\\', iStart); + if (iStartNext >= 0) + sPartialName.SetString(m_sValue, iStartNext); + else + sPartialName = m_sValue; + + // Try to open the key, to see if it exists. + lResult = ::RegOpenKeyExW(m_hKeyRoot, sPartialName, 0, KEY_ENUMERATE_SUB_KEYS | samAdditional, &hKey); + if (lResult == ERROR_FILE_NOT_FOUND) { + // The key doesn't exist yet. Create it. + + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the key. ::RegCreateEx() might create a key but return failure. + pSession->m_olRollback.AddHead(new COpRegKeyDelete(m_hKeyRoot, sPartialName)); + } + + // Create the key. + lResult = ::RegCreateKeyExW(m_hKeyRoot, sPartialName, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ENUMERATE_SUB_KEYS | samAdditional, NULL, &hKey, NULL); + if (lResult != ERROR_SUCCESS) break; + ::RegCloseKey(hKey); + } else if (lResult == ERROR_SUCCESS) { + // This key already exists. Release its handle and continue. + ::RegCloseKey(hKey); + } else + break; + + if (iStartNext < 0) break; + iStart = iStartNext + 1; + } + + if (lResult == ERROR_SUCCESS) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(4); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_CREATE_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 4, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyCopy +//////////////////////////////////////////////////////////////////////////// + +COpRegKeyCopy::COpRegKeyCopy(HKEY hKeyRoot, LPCWSTR pszKeyNameSrc, LPCWSTR pszKeyNameDst, int iTicks) : COpRegKeySrcDst(hKeyRoot, pszKeyNameSrc, pszKeyNameDst, iTicks) +{ +} + + +HRESULT COpRegKeyCopy::Execute(CSession *pSession) +{ + LONG lResult; + REGSAM samAdditional = 0; + + { + // Delete existing destination key first. + // Since deleting registry key is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + // Don't worry, COpRegKeyDelete::Execute() returns S_OK if key doesn't exist. + COpRegKeyDelete opDelete(m_hKeyRoot, m_sValue2); + HRESULT hr = opDelete.Execute(pSession); + if (FAILED(hr)) return hr; + } + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + samAdditional |= KEY_WOW64_64KEY; + } +#endif + + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the destination key. + pSession->m_olRollback.AddHead(new COpRegKeyDelete(m_hKeyRoot, m_sValue2)); + } + + // Copy the registry key. + lResult = CopyKeyRecursively(m_hKeyRoot, m_sValue1, m_sValue2, samAdditional); + if (lResult == ERROR_SUCCESS) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(5); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_COPY_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue1 ); + ::MsiRecordSetStringW(hRecordProg, 4, m_sValue2 ); + ::MsiRecordSetInteger(hRecordProg, 5, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + + +LONG COpRegKeyCopy::CopyKeyRecursively(HKEY hKeyRoot, LPCWSTR pszKeyNameSrc, LPCWSTR pszKeyNameDst, REGSAM samAdditional) +{ + LONG lResult; + HKEY hKeySrc, hKeyDst; + + // Open source key. + lResult = ::RegOpenKeyExW(hKeyRoot, pszKeyNameSrc, 0, READ_CONTROL | KEY_READ | samAdditional, &hKeySrc); + if (lResult != ERROR_SUCCESS) return lResult; + + { + DWORD dwSecurityDescriptorSize, dwClassLen = MAX_PATH; + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; + LPWSTR pszClass = new WCHAR[dwClassLen]; + + // Get source key class length and security descriptor size. + lResult = ::RegQueryInfoKeyW(hKeySrc, pszClass, &dwClassLen, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwSecurityDescriptorSize, NULL); + if (lResult != ERROR_SUCCESS) { + delete [] pszClass; + return lResult; + } + pszClass[dwClassLen] = 0; + + // Get source key security descriptor. + sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(new BYTE[dwSecurityDescriptorSize]); + lResult = ::RegGetKeySecurity(hKeySrc, DACL_SECURITY_INFORMATION, sa.lpSecurityDescriptor, &dwSecurityDescriptorSize); + if (lResult != ERROR_SUCCESS) { + delete [] (LPBYTE)(sa.lpSecurityDescriptor); + delete [] pszClass; + return lResult; + } + + // Create new destination key of the same class and security. + lResult = ::RegCreateKeyExW(hKeyRoot, pszKeyNameDst, 0, pszClass, REG_OPTION_NON_VOLATILE, KEY_WRITE | samAdditional, &sa, &hKeyDst, NULL); + delete [] (LPBYTE)(sa.lpSecurityDescriptor); + delete [] pszClass; + if (lResult != ERROR_SUCCESS) return lResult; + } + + // Copy subkey recursively. + return CopyKeyRecursively(hKeySrc, hKeyDst, samAdditional); +} + + +LONG COpRegKeyCopy::CopyKeyRecursively(HKEY hKeySrc, HKEY hKeyDst, REGSAM samAdditional) +{ + LONG lResult; + DWORD dwMaxSubKeyLen, dwMaxValueNameLen, dwMaxClassLen, dwMaxDataSize, dwIndex; + LPWSTR pszName, pszClass; + LPBYTE lpData; + + // Query the source key. + lResult = ::RegQueryInfoKeyW(hKeySrc, NULL, NULL, NULL, NULL, &dwMaxSubKeyLen, &dwMaxClassLen, NULL, &dwMaxValueNameLen, &dwMaxDataSize, NULL, NULL); + if (lResult != ERROR_SUCCESS) return lResult; + + // Copy values first. + dwMaxValueNameLen++; + pszName = new WCHAR[dwMaxValueNameLen]; + lpData = new BYTE[dwMaxDataSize]; + for (dwIndex = 0; ; dwIndex++) { + DWORD dwNameLen = dwMaxValueNameLen, dwType, dwValueSize = dwMaxDataSize; + + // Read value. + lResult = ::RegEnumValueW(hKeySrc, dwIndex, pszName, &dwNameLen, NULL, &dwType, lpData, &dwValueSize); + if (lResult == ERROR_NO_MORE_ITEMS) { + lResult = ERROR_SUCCESS; + break; + } else if (lResult != ERROR_SUCCESS) + break; + + // Save value. + lResult = ::RegSetValueExW(hKeyDst, pszName, 0, dwType, lpData, dwValueSize); + if (lResult != ERROR_SUCCESS) + break; + } + delete [] lpData; + delete [] pszName; + if (lResult != ERROR_SUCCESS) return lResult; + + // Iterate over all subkeys and copy them. + dwMaxSubKeyLen++; + pszName = new WCHAR[dwMaxSubKeyLen]; + dwMaxClassLen++; + pszClass = new WCHAR[dwMaxClassLen]; + for (dwIndex = 0; ; dwIndex++) { + DWORD dwNameLen = dwMaxSubKeyLen, dwClassLen = dwMaxClassLen; + HKEY hKeySrcSub, hKeyDstSub; + + // Read subkey. + lResult = ::RegEnumKeyExW(hKeySrc, dwIndex, pszName, &dwNameLen, NULL, pszClass, &dwClassLen, NULL); + if (lResult == ERROR_NO_MORE_ITEMS) { + lResult = ERROR_SUCCESS; + break; + } else if (lResult != ERROR_SUCCESS) + break; + + // Open source subkey. + lResult = ::RegOpenKeyExW(hKeySrc, pszName, 0, READ_CONTROL | KEY_READ | samAdditional, &hKeySrcSub); + if (lResult != ERROR_SUCCESS) break; + + { + DWORD dwSecurityDescriptorSize; + SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; + + // Get source subkey security descriptor size. + lResult = ::RegQueryInfoKeyW(hKeySrcSub, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwSecurityDescriptorSize, NULL); + if (lResult != ERROR_SUCCESS) break; + + // Get source subkey security descriptor. + sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(new BYTE[dwSecurityDescriptorSize]); + lResult = ::RegGetKeySecurity(hKeySrc, DACL_SECURITY_INFORMATION, sa.lpSecurityDescriptor, &dwSecurityDescriptorSize); + if (lResult != ERROR_SUCCESS) { + delete [] (LPBYTE)(sa.lpSecurityDescriptor); + break; + } + + // Create new destination subkey of the same class and security. + lResult = ::RegCreateKeyExW(hKeyDst, pszName, 0, pszClass, REG_OPTION_NON_VOLATILE, KEY_WRITE | samAdditional, &sa, &hKeyDstSub, NULL); + delete [] (LPBYTE)(sa.lpSecurityDescriptor); + if (lResult != ERROR_SUCCESS) break; + } + + // Copy subkey recursively. + lResult = CopyKeyRecursively(hKeySrcSub, hKeyDstSub, samAdditional); + if (lResult != ERROR_SUCCESS) break; + } + delete [] pszClass; + delete [] pszName; + + return lResult; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegKeyDelete +//////////////////////////////////////////////////////////////////////////// + +COpRegKeyDelete::COpRegKeyDelete(HKEY hKey, LPCWSTR pszKeyName, int iTicks) : COpRegKeySingle(hKey, pszKeyName, iTicks) +{ +} + + +HRESULT COpRegKeyDelete::Execute(CSession *pSession) +{ + LONG lResult; + HKEY hKey; + REGSAM samAdditional = 0; + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + samAdditional |= KEY_WOW64_64KEY; + } +#endif + + // Probe to see if the key exists. + lResult = ::RegOpenKeyExW(m_hKeyRoot, m_sValue, 0, DELETE | samAdditional, &hKey); + if (lResult == ERROR_SUCCESS) { + ::RegCloseKey(hKey); + + if (pSession->m_bRollbackEnabled) { + // Make a backup of the key first. + ATL::CAtlStringW sBackupName; + UINT uiCount = 0; + int iLength = m_sValue.GetLength(); + + // Trim trailing backslashes. + while (iLength && m_sValue.GetAt(iLength - 1) == L'\\') iLength--; + + for (;;) { + HKEY hKey; + sBackupName.Format(L"%.*ls (orig %u)", iLength, (LPCWSTR)m_sValue, ++uiCount); + lResult = ::RegOpenKeyExW(m_hKeyRoot, sBackupName, 0, KEY_ENUMERATE_SUB_KEYS | samAdditional, &hKey); + if (lResult != ERROR_SUCCESS) break; + ::RegCloseKey(hKey); + } + if (lResult == ERROR_FILE_NOT_FOUND) { + // Since copying registry key is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + COpRegKeyCopy opCopy(m_hKeyRoot, m_sValue, sBackupName); + HRESULT hr = opCopy.Execute(pSession); + if (FAILED(hr)) return hr; + + // Order rollback action to restore the key from backup copy. + pSession->m_olRollback.AddHead(new COpRegKeyCopy(m_hKeyRoot, sBackupName, m_sValue)); + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new COpRegKeyDelete(m_hKeyRoot, sBackupName)); + } else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(4); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_PROBING_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, sBackupName ); + ::MsiRecordSetInteger(hRecordProg, 4, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } + } + + // Delete the registry key. + lResult = DeleteKeyRecursively(m_hKeyRoot, m_sValue, samAdditional); + } + + if (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(4); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_DELETE_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 4, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + + +LONG COpRegKeyDelete::DeleteKeyRecursively(HKEY hKeyRoot, LPCWSTR pszKeyName, REGSAM samAdditional) +{ + HKEY hKey; + LONG lResult; + + // Open the key. + lResult = ::RegOpenKeyExW(hKeyRoot, pszKeyName, 0, DELETE | KEY_READ | samAdditional, &hKey); + if (lResult == ERROR_SUCCESS) { + DWORD dwMaxSubKeyLen; + + // Determine the largest subkey name. + lResult = ::RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, &dwMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL); + if (lResult == ERROR_SUCCESS) { + LPWSTR pszSubKeyName; + + // Prepare buffer to hold the subkey names (including zero terminator). + dwMaxSubKeyLen++; + pszSubKeyName = new WCHAR[dwMaxSubKeyLen]; + if (pszSubKeyName) { + DWORD dwIndex; + + // Iterate over all subkeys and delete them. Skip failed. + for (dwIndex = 0; ;) { + DWORD dwNameLen = dwMaxSubKeyLen; + lResult = ::RegEnumKeyExW(hKey, dwIndex, pszSubKeyName, &dwNameLen, NULL, NULL, NULL, NULL); + if (lResult == ERROR_SUCCESS) { + lResult = DeleteKeyRecursively(hKey, pszSubKeyName, samAdditional); + if (lResult != ERROR_SUCCESS) + dwIndex++; + } else if (lResult == ERROR_NO_MORE_ITEMS) { + lResult = ERROR_SUCCESS; + break; + } else + dwIndex++; + } + + delete [] pszSubKeyName; + } else + lResult = ERROR_OUTOFMEMORY; + } + + ::RegCloseKey(hKey); + + // Finally try to delete the key. + lResult = ::RegDeleteKeyW(hKeyRoot, pszKeyName); + } else if (lResult == ERROR_FILE_NOT_FOUND) { + // The key doesn't exist. Not really an error in this case. + lResult = ERROR_SUCCESS; + } + + return lResult; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueSingle +//////////////////////////////////////////////////////////////////////////// + +COpRegValueSingle::COpRegValueSingle(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, int iTicks) : + m_sValueName(pszValueName), + COpRegKeySingle(hKeyRoot, pszKeyName, iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueSrcDst +//////////////////////////////////////////////////////////////////////////// + +COpRegValueSrcDst::COpRegValueSrcDst(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueNameSrc, LPCWSTR pszValueNameDst, int iTicks) : + m_sValueName1(pszValueNameSrc), + m_sValueName2(pszValueNameDst), + COpRegKeySingle(hKeyRoot, pszKeyName, iTicks) +{ +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueCreate +//////////////////////////////////////////////////////////////////////////// + +COpRegValueCreate::COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, int iTicks) : + m_dwType(REG_NONE), + COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ +} + + +COpRegValueCreate::COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, DWORD dwData, int iTicks) : + m_dwType(REG_DWORD), + m_dwData(dwData), + COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ +} + + +COpRegValueCreate::COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, LPCVOID lpData, SIZE_T nSize, int iTicks) : + m_dwType(REG_BINARY), + COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ + m_binData.SetCount(nSize); + memcpy(m_binData.GetData(), lpData, nSize); +} + + +COpRegValueCreate::COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, LPCWSTR pszData, int iTicks) : + m_dwType(REG_SZ), + m_sData(pszData), + COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ +} + + +COpRegValueCreate::COpRegValueCreate(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, DWORDLONG qwData, int iTicks) : + m_dwType(REG_QWORD), + m_qwData(qwData), + COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ +} + + +HRESULT COpRegValueCreate::Execute(CSession *pSession) +{ + LONG lResult; + REGSAM sam = KEY_QUERY_VALUE | STANDARD_RIGHTS_WRITE | KEY_SET_VALUE; + HKEY hKey; + + { + // Delete existing value first. + // Since deleting registry value is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + // Don't worry, COpRegValueDelete::Execute() returns S_OK if key doesn't exist. + COpRegValueDelete opDelete(m_hKeyRoot, m_sValue, m_sValueName); + HRESULT hr = opDelete.Execute(pSession); + if (FAILED(hr)) return hr; + } + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + sam |= KEY_WOW64_64KEY; + } +#endif + + // Open the key. + lResult = ::RegOpenKeyExW(m_hKeyRoot, m_sValue, 0, sam, &hKey); + if (lResult == ERROR_SUCCESS) { + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the value. + pSession->m_olRollback.AddHead(new COpRegValueDelete(m_hKeyRoot, m_sValue, m_sValueName)); + } + + // Set the registry value. + switch (m_dwType) { + case REG_SZ: + case REG_EXPAND_SZ: + case REG_LINK: + lResult = ::RegSetValueExW(hKey, m_sValueName, 0, m_dwType, (const BYTE*)(LPCWSTR)m_sData, (m_sData.GetLength() + 1) * sizeof(WCHAR)); break; + break; + + case REG_BINARY: + lResult = ::RegSetValueExW(hKey, m_sValueName, 0, m_dwType, m_binData.GetData(), (DWORD)m_binData.GetCount() * sizeof(BYTE)); break; + + case REG_DWORD_LITTLE_ENDIAN: + case REG_DWORD_BIG_ENDIAN: + lResult = ::RegSetValueExW(hKey, m_sValueName, 0, m_dwType, (const BYTE*)&m_dwData, sizeof(DWORD)); break; + break; + + case REG_MULTI_SZ: + lResult = ::RegSetValueExW(hKey, m_sValueName, 0, m_dwType, (const BYTE*)m_szData.GetData(), (DWORD)m_szData.GetCount() * sizeof(WCHAR)); break; + break; + + case REG_QWORD_LITTLE_ENDIAN: + lResult = ::RegSetValueExW(hKey, m_sValueName, 0, m_dwType, (const BYTE*)&m_qwData, sizeof(DWORDLONG)); break; + break; + + default: + lResult = ERROR_UNSUPPORTED_TYPE; + } + + ::RegCloseKey(hKey); + } + + if (lResult == ERROR_SUCCESS) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(5); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_SETVALUE_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetStringW(hRecordProg, 4, m_sValueName ); + ::MsiRecordSetInteger(hRecordProg, 5, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueCopy +//////////////////////////////////////////////////////////////////////////// + +COpRegValueCopy::COpRegValueCopy(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueNameSrc, LPCWSTR pszValueNameDst, int iTicks) : COpRegValueSrcDst(hKeyRoot, pszKeyName, pszValueNameSrc, pszValueNameDst, iTicks) +{ +} + + +HRESULT COpRegValueCopy::Execute(CSession *pSession) +{ + LONG lResult; + REGSAM sam = KEY_QUERY_VALUE | KEY_SET_VALUE; + HKEY hKey; + + { + // Delete existing destination value first. + // Since deleting registry value is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + // Don't worry, COpRegValueDelete::Execute() returns S_OK if key doesn't exist. + COpRegValueDelete opDelete(m_hKeyRoot, m_sValue, m_sValueName2); + HRESULT hr = opDelete.Execute(pSession); + if (FAILED(hr)) return hr; + } + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + sam |= KEY_WOW64_64KEY; + } +#endif + + // Open the key. + lResult = ::RegOpenKeyExW(m_hKeyRoot, m_sValue, 0, sam, &hKey); + if (lResult == ERROR_SUCCESS) { + DWORD dwType, dwSize; + + // Query the source registry value size. + lResult = ::RegQueryValueExW(hKey, m_sValueName1, 0, NULL, NULL, &dwSize); + if (lResult == ERROR_SUCCESS) { + LPBYTE lpData = new BYTE[dwSize]; + // Read the source registry value. + lResult = ::RegQueryValueExW(hKey, m_sValueName1, 0, &dwType, lpData, &dwSize); + if (lResult == ERROR_SUCCESS) { + if (pSession->m_bRollbackEnabled) { + // Order rollback action to delete the destination copy. + pSession->m_olRollback.AddHead(new COpRegValueDelete(m_hKeyRoot, m_sValue, m_sValueName2)); + } + + // Store the value to destination. + lResult = ::RegSetValueExW(hKey, m_sValueName2, 0, dwType, lpData, dwSize); + } + delete [] lpData; + } + + ::RegCloseKey(hKey); + } + + if (lResult == ERROR_SUCCESS) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(6); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_COPYVALUE_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetStringW(hRecordProg, 4, m_sValueName1 ); + ::MsiRecordSetStringW(hRecordProg, 5, m_sValueName2 ); + ::MsiRecordSetInteger(hRecordProg, 6, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// COpRegValueDelete +//////////////////////////////////////////////////////////////////////////// + +COpRegValueDelete::COpRegValueDelete(HKEY hKeyRoot, LPCWSTR pszKeyName, LPCWSTR pszValueName, int iTicks) : COpRegValueSingle(hKeyRoot, pszKeyName, pszValueName, iTicks) +{ +} + + +HRESULT COpRegValueDelete::Execute(CSession *pSession) +{ + LONG lResult; + REGSAM sam = KEY_QUERY_VALUE | KEY_SET_VALUE; + HKEY hKey; + +#ifndef _WIN64 + if (IsWow64Process()) { + // 32-bit processes run as WOW64 should use 64-bit registry too. + sam |= KEY_WOW64_64KEY; + } +#endif + + // Open the key. + lResult = ::RegOpenKeyExW(m_hKeyRoot, m_sValue, 0, sam, &hKey); + if (lResult == ERROR_SUCCESS) { + DWORD dwType; + + // See if the value exists at all. + lResult = ::RegQueryValueExW(hKey, m_sValueName, 0, &dwType, NULL, NULL); + if (lResult == ERROR_SUCCESS) { + if (pSession->m_bRollbackEnabled) { + // Make a backup of the value first. + ATL::CAtlStringW sBackupName; + UINT uiCount = 0; + + for (;;) { + sBackupName.Format(L"%ls (orig %u)", (LPCWSTR)m_sValueName, ++uiCount); + lResult = ::RegQueryValueExW(hKey, sBackupName, 0, &dwType, NULL, NULL); + if (lResult != ERROR_SUCCESS) break; + } + if (lResult == ERROR_FILE_NOT_FOUND) { + // Since copying registry value is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + COpRegValueCopy opCopy(m_hKeyRoot, m_sValue, m_sValueName, sBackupName); + HRESULT hr = opCopy.Execute(pSession); + if (FAILED(hr)) { + ::RegCloseKey(hKey); + return hr; + } + + // Order rollback action to restore the key from backup copy. + pSession->m_olRollback.AddHead(new COpRegValueCopy(m_hKeyRoot, m_sValue, sBackupName, m_sValueName)); + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new COpRegValueDelete(m_hKeyRoot, m_sValue, sBackupName)); + } else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(5); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_PROBINGVAL_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetStringW(hRecordProg, 3, sBackupName ); + ::MsiRecordSetInteger(hRecordProg, 4, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + ::RegCloseKey(hKey); + return AtlHresultFromWin32(lResult); + } + } + + // Delete the registry value. + lResult = ::RegDeleteValueW(hKey, m_sValueName); + } + + ::RegCloseKey(hKey); + } + + if (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND) + return S_OK; + else { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(5); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_REGKEY_DELETEVALUE_FAILED); + ::MsiRecordSetInteger(hRecordProg, 2, (UINT)m_hKeyRoot & 0x7fffffff ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue ); + ::MsiRecordSetStringW(hRecordProg, 4, m_sValueName ); + ::MsiRecordSetInteger(hRecordProg, 5, lResult ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + return AtlHresultFromWin32(lResult); + } +} + +} // namespace MSICA diff --git a/TSOp.cpp b/TSOp.cpp new file mode 100644 index 0000000..089c3d4 --- /dev/null +++ b/TSOp.cpp @@ -0,0 +1,1038 @@ +#include "stdafx.h" + +#pragma comment(lib, "mstask.lib") +#pragma comment(lib, "taskschd.lib") + + +namespace MSICA { + +//////////////////////////////////////////////////////////////////////////// +// COpTaskCreate +//////////////////////////////////////////////////////////////////////////// + +COpTaskCreate::COpTaskCreate(LPCWSTR pszTaskName, int iTicks) : + m_dwFlags(0), + m_dwPriority(NORMAL_PRIORITY_CLASS), + m_wIdleMinutes(0), + m_wDeadlineMinutes(0), + m_dwMaxRuntimeMS(INFINITE), + COpTypeSingleString(pszTaskName, iTicks) +{ +} + + +COpTaskCreate::~COpTaskCreate() +{ + // Clear the password in memory. + int iLength = m_sPassword.GetLength(); + ::SecureZeroMemory(m_sPassword.GetBuffer(iLength), sizeof(WCHAR) * iLength); + m_sPassword.ReleaseBuffer(0); +} + + +HRESULT COpTaskCreate::Execute(CSession *pSession) +{ + HRESULT hr; + PMSIHANDLE hRecordMsg = ::MsiCreateRecord(1); + CComPtr pService; + POSITION pos; + + // Display our custom message in the progress bar. + ::MsiRecordSetStringW(hRecordMsg, 1, m_sValue); + if (MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ACTIONDATA, hRecordMsg) == IDCANCEL) + return AtlHresultFromWin32(ERROR_INSTALL_USEREXIT); + + { + // Delete existing task first. + // Since task deleting is a complicated job (when rollback/commit support is required), and we do have an operation just for that, we use it. + // Don't worry, COpTaskDelete::Execute() returns S_OK if task doesn't exist. + COpTaskDelete opDelete(m_sValue); + hr = opDelete.Execute(pSession); + if (FAILED(hr)) goto finish; + } + + hr = pService.CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER); + if (SUCCEEDED(hr)) { + // Windows Vista or newer. + CComVariant vEmpty; + CComPtr pTaskDefinition; + CComPtr pTaskSettings; + CComPtr pPrincipal; + CComPtr pActionCollection; + CComPtr pAction; + CComPtr pIdleSettings; + CComPtr pExecAction; + CComPtr pRegististrationInfo; + CComPtr pTriggerCollection; + CComPtr pTaskFolder; + CComPtr pTask; + ATL::CAtlStringW str; + UINT iTrigger; + TASK_LOGON_TYPE logonType; + CComBSTR bstrContext(L"Author"); + + // Connect to local task service. + hr = pService->Connect(vEmpty, vEmpty, vEmpty, vEmpty); + if (FAILED(hr)) goto finish; + + // Prepare the definition for a new task. + hr = pService->NewTask(0, &pTaskDefinition); + if (FAILED(hr)) goto finish; + + // Get the task's settings. + hr = pTaskDefinition->get_Settings(&pTaskSettings); + if (FAILED(hr)) goto finish; + + // Enable/disable task. + 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 COpTaskEnable(m_sValue, TRUE)); + m_dwFlags |= TASK_FLAG_DISABLED; + } + hr = pTaskSettings->put_Enabled(m_dwFlags & TASK_FLAG_DISABLED ? VARIANT_FALSE : VARIANT_TRUE); if (FAILED(hr)) goto finish; + + // Get task actions. + hr = pTaskDefinition->get_Actions(&pActionCollection); + if (FAILED(hr)) goto finish; + + // Add execute action. + hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction); + if (FAILED(hr)) goto finish; + hr = pAction.QueryInterface(&pExecAction); + if (FAILED(hr)) goto finish; + + // Configure the action. + hr = pExecAction->put_Path (CComBSTR(m_sApplicationName )); if (FAILED(hr)) goto finish; + hr = pExecAction->put_Arguments (CComBSTR(m_sParameters )); if (FAILED(hr)) goto finish; + hr = pExecAction->put_WorkingDirectory(CComBSTR(m_sWorkingDirectory)); if (FAILED(hr)) goto finish; + + // Set task description. + hr = pTaskDefinition->get_RegistrationInfo(&pRegististrationInfo); + if (FAILED(hr)) goto finish; + hr = pRegististrationInfo->put_Author(CComBSTR(m_sAuthor)); + if (FAILED(hr)) goto finish; + hr = pRegististrationInfo->put_Description(CComBSTR(m_sComment)); + if (FAILED(hr)) goto finish; + + // Configure task "flags". + if (m_dwFlags & TASK_FLAG_DELETE_WHEN_DONE) { + hr = pTaskSettings->put_DeleteExpiredTaskAfter(CComBSTR(L"PT24H")); + if (FAILED(hr)) goto finish; + } + hr = pTaskSettings->put_Hidden(m_dwFlags & TASK_FLAG_HIDDEN ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + hr = pTaskSettings->put_WakeToRun(m_dwFlags & TASK_FLAG_SYSTEM_REQUIRED ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + hr = pTaskSettings->put_DisallowStartIfOnBatteries(m_dwFlags & TASK_FLAG_DONT_START_IF_ON_BATTERIES ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + hr = pTaskSettings->put_StopIfGoingOnBatteries(m_dwFlags & TASK_FLAG_KILL_IF_GOING_ON_BATTERIES ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + + // Configure task priority. + hr = pTaskSettings->put_Priority( + m_dwPriority == REALTIME_PRIORITY_CLASS ? 0 : + m_dwPriority == HIGH_PRIORITY_CLASS ? 1 : + m_dwPriority == ABOVE_NORMAL_PRIORITY_CLASS ? 2 : + m_dwPriority == NORMAL_PRIORITY_CLASS ? 4 : + m_dwPriority == BELOW_NORMAL_PRIORITY_CLASS ? 7 : + m_dwPriority == IDLE_PRIORITY_CLASS ? 9 : 7); + if (FAILED(hr)) goto finish; + + // Get task principal. + hr = pTaskDefinition->get_Principal(&pPrincipal); + if (FAILED(hr)) goto finish; + + if (m_sAccountName.IsEmpty()) { + logonType = TASK_LOGON_SERVICE_ACCOUNT; + hr = pPrincipal->put_LogonType(logonType); if (FAILED(hr)) goto finish; + hr = pPrincipal->put_UserId(CComBSTR(L"S-1-5-18")); if (FAILED(hr)) goto finish; + hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); if (FAILED(hr)) goto finish; + } else if (m_dwFlags & TASK_FLAG_RUN_ONLY_IF_LOGGED_ON) { + logonType = TASK_LOGON_INTERACTIVE_TOKEN; + hr = pPrincipal->put_LogonType(logonType); if (FAILED(hr)) goto finish; + hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_LUA); if (FAILED(hr)) goto finish; + } else { + logonType = TASK_LOGON_PASSWORD; + hr = pPrincipal->put_LogonType(logonType); if (FAILED(hr)) goto finish; + hr = pPrincipal->put_UserId(CComBSTR(m_sAccountName)); if (FAILED(hr)) goto finish; + hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); if (FAILED(hr)) goto finish; + } + + // Connect principal and action collection. + hr = pPrincipal->put_Id(bstrContext); + if (FAILED(hr)) goto finish; + hr = pActionCollection->put_Context(bstrContext); + if (FAILED(hr)) goto finish; + + // Configure idle settings. + hr = pTaskSettings->put_RunOnlyIfIdle(m_dwFlags & TASK_FLAG_START_ONLY_IF_IDLE ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + hr = pTaskSettings->get_IdleSettings(&pIdleSettings); + if (FAILED(hr)) goto finish; + str.Format(L"PT%uS", m_wIdleMinutes*60); + hr = pIdleSettings->put_IdleDuration(CComBSTR(str)); + if (FAILED(hr)) goto finish; + str.Format(L"PT%uS", m_wDeadlineMinutes*60); + hr = pIdleSettings->put_WaitTimeout(CComBSTR(str)); + if (FAILED(hr)) goto finish; + hr = pIdleSettings->put_RestartOnIdle(m_dwFlags & TASK_FLAG_RESTART_ON_IDLE_RESUME ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + hr = pIdleSettings->put_StopOnIdleEnd(m_dwFlags & TASK_FLAG_KILL_ON_IDLE_END ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + + // Configure task runtime limit. + str.Format(L"PT%uS", m_dwMaxRuntimeMS != INFINITE ? (m_dwMaxRuntimeMS + 500) / 1000 : 0); + hr = pTaskSettings->put_ExecutionTimeLimit(CComBSTR(str)); + if (FAILED(hr)) goto finish; + + // Get task trigger colection. + hr = pTaskDefinition->get_Triggers(&pTriggerCollection); + if (FAILED(hr)) goto finish; + + // Add triggers. + for (pos = m_lTriggers.GetHeadPosition(), iTrigger = 0; pos; iTrigger++) { + CComPtr pTrigger; + TASK_TRIGGER &ttData = m_lTriggers.GetNext(pos); + + switch (ttData.TriggerType) { + case TASK_TIME_TRIGGER_ONCE: { + CComPtr pTriggerTime; + hr = pTriggerCollection->Create(TASK_TRIGGER_TIME, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerTime); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerTime->put_RandomDelay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_TIME_TRIGGER_DAILY: { + CComPtr pTriggerDaily; + hr = pTriggerCollection->Create(TASK_TRIGGER_DAILY, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerDaily); if (FAILED(hr)) goto finish; + hr = pTriggerDaily->put_DaysInterval(ttData.Type.Daily.DaysInterval); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerDaily->put_RandomDelay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_TIME_TRIGGER_WEEKLY: { + CComPtr pTriggerWeekly; + hr = pTriggerCollection->Create(TASK_TRIGGER_WEEKLY, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerWeekly); if (FAILED(hr)) goto finish; + hr = pTriggerWeekly->put_WeeksInterval(ttData.Type.Weekly.WeeksInterval); if (FAILED(hr)) goto finish; + hr = pTriggerWeekly->put_DaysOfWeek(ttData.Type.Weekly.rgfDaysOfTheWeek); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerWeekly->put_RandomDelay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_TIME_TRIGGER_MONTHLYDATE: { + CComPtr pTriggerMonthly; + hr = pTriggerCollection->Create(TASK_TRIGGER_MONTHLY, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerMonthly); if (FAILED(hr)) goto finish; + hr = pTriggerMonthly->put_DaysOfMonth(ttData.Type.MonthlyDate.rgfDays); if (FAILED(hr)) goto finish; + hr = pTriggerMonthly->put_MonthsOfYear(ttData.Type.MonthlyDate.rgfMonths); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerMonthly->put_RandomDelay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_TIME_TRIGGER_MONTHLYDOW: { + CComPtr pTriggerMonthlyDOW; + hr = pTriggerCollection->Create(TASK_TRIGGER_MONTHLYDOW, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerMonthlyDOW); if (FAILED(hr)) goto finish; + hr = pTriggerMonthlyDOW->put_WeeksOfMonth( + ttData.Type.MonthlyDOW.wWhichWeek == TASK_FIRST_WEEK ? 0x01 : + ttData.Type.MonthlyDOW.wWhichWeek == TASK_SECOND_WEEK ? 0x02 : + ttData.Type.MonthlyDOW.wWhichWeek == TASK_THIRD_WEEK ? 0x04 : + ttData.Type.MonthlyDOW.wWhichWeek == TASK_FOURTH_WEEK ? 0x08 : + ttData.Type.MonthlyDOW.wWhichWeek == TASK_LAST_WEEK ? 0x10 : 0x00); if (FAILED(hr)) goto finish; + hr = pTriggerMonthlyDOW->put_DaysOfWeek(ttData.Type.MonthlyDOW.rgfDaysOfTheWeek); if (FAILED(hr)) goto finish; + hr = pTriggerMonthlyDOW->put_MonthsOfYear(ttData.Type.MonthlyDOW.rgfMonths); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerMonthlyDOW->put_RandomDelay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_EVENT_TRIGGER_ON_IDLE: { + hr = pTriggerCollection->Create(TASK_TRIGGER_IDLE, &pTrigger); if (FAILED(hr)) goto finish; + break; + } + + case TASK_EVENT_TRIGGER_AT_SYSTEMSTART: { + CComPtr pTriggerBoot; + hr = pTriggerCollection->Create(TASK_TRIGGER_BOOT, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerBoot); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerBoot->put_Delay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + + case TASK_EVENT_TRIGGER_AT_LOGON: { + CComPtr pTriggerLogon; + hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger); if (FAILED(hr)) goto finish; + hr = pTrigger.QueryInterface(&pTriggerLogon); if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.wRandomMinutesInterval); + hr = pTriggerLogon->put_Delay(CComBSTR(str)); if (FAILED(hr)) goto finish; + break; + } + } + + // Set trigger ID. + str.Format(L"%u", iTrigger); + hr = pTrigger->put_Id(CComBSTR(str)); + if (FAILED(hr)) goto finish; + + // Set trigger start date. + str.Format(L"%04u-%02u-%02uT%02u:%02u:00", ttData.wBeginYear, ttData.wBeginMonth, ttData.wBeginDay, ttData.wStartHour, ttData.wStartMinute); + hr = pTrigger->put_StartBoundary(CComBSTR(str)); + if (FAILED(hr)) goto finish; + + if (ttData.rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE) { + // Set trigger end date. + str.Format(L"%04u-%02u-%02u", ttData.wEndYear, ttData.wEndMonth, ttData.wEndDay); + hr = pTrigger->put_EndBoundary(CComBSTR(str)); + if (FAILED(hr)) goto finish; + } + + // Set trigger repetition duration and interval. + if (ttData.MinutesDuration || ttData.MinutesInterval) { + CComPtr pRepetitionPattern; + + hr = pTrigger->get_Repetition(&pRepetitionPattern); + if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.MinutesDuration); + hr = pRepetitionPattern->put_Duration(CComBSTR(str)); + if (FAILED(hr)) goto finish; + str.Format(L"PT%uM", ttData.MinutesInterval); + hr = pRepetitionPattern->put_Interval(CComBSTR(str)); + if (FAILED(hr)) goto finish; + hr = pRepetitionPattern->put_StopAtDurationEnd(ttData.rgFlags & TASK_TRIGGER_FLAG_KILL_AT_DURATION_END ? VARIANT_TRUE : VARIANT_FALSE); + if (FAILED(hr)) goto finish; + } + + // Enable/disable trigger. + hr = pTrigger->put_Enabled(ttData.rgFlags & TASK_TRIGGER_FLAG_DISABLED ? VARIANT_FALSE : VARIANT_TRUE); + if (FAILED(hr)) goto finish; + } + + // Get the task folder. + hr = pService->GetFolder(CComBSTR(L"\\"), &pTaskFolder); + if (FAILED(hr)) goto finish; + +#ifdef _DEBUG + CComBSTR xml; + hr = pTaskDefinition->get_XmlText(&xml); +#endif + + // Register the task. + hr = pTaskFolder->RegisterTaskDefinition( + CComBSTR(m_sValue), // path + pTaskDefinition, // pDefinition + TASK_CREATE, // flags + vEmpty, // userId + logonType != TASK_LOGON_SERVICE_ACCOUNT && !m_sPassword.IsEmpty() ? CComVariant(m_sPassword) : vEmpty, // password + logonType, // logonType + vEmpty, // sddl + &pTask); // ppTask + } else { + // Windows XP or older. + CComPtr pTaskScheduler; + CComPtr pTask; + + // Get task scheduler object. + hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); + if (FAILED(hr)) goto finish; + + // Create the new task. + hr = 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 COpTaskDelete(m_sValue)); + } + if (FAILED(hr)) goto finish; + + // Set its properties. + hr = pTask->SetApplicationName (m_sApplicationName ); if (FAILED(hr)) goto finish; + hr = pTask->SetParameters (m_sParameters ); if (FAILED(hr)) goto finish; + hr = pTask->SetWorkingDirectory(m_sWorkingDirectory); if (FAILED(hr)) goto finish; + hr = pTask->SetComment (m_sComment ); if (FAILED(hr)) goto finish; + 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 COpTaskEnable(m_sValue, TRUE)); + m_dwFlags |= TASK_FLAG_DISABLED; + } + hr = pTask->SetFlags (m_dwFlags ); if (FAILED(hr)) goto finish; + hr = pTask->SetPriority (m_dwPriority ); if (FAILED(hr)) goto finish; + + // Set task credentials. + hr = m_sAccountName.IsEmpty() ? + pTask->SetAccountInformation(L"", NULL ) : + pTask->SetAccountInformation(m_sAccountName, m_sPassword); + if (FAILED(hr)) goto finish; + + hr = pTask->SetIdleWait (m_wIdleMinutes, m_wDeadlineMinutes); if (FAILED(hr)) goto finish; + hr = pTask->SetMaxRunTime(m_dwMaxRuntimeMS ); if (FAILED(hr)) goto finish; + + // Add triggers. + for (pos = m_lTriggers.GetHeadPosition(); pos;) { + WORD wTriggerIdx; + CComPtr pTrigger; + TASK_TRIGGER ttData = m_lTriggers.GetNext(pos); // Don't use reference! We don't want to modify original trigger data by adding random startup delay. + + hr = pTask->CreateTrigger(&wTriggerIdx, &pTrigger); + if (FAILED(hr)) goto finish; + + if (ttData.wRandomMinutesInterval) { + // Windows XP doesn't support random startup delay. However, we can add fixed "random" delay when creating the trigger. + WORD wStartTime = ttData.wStartHour * 60 + ttData.wStartMinute + (WORD)::MulDiv(rand(), ttData.wRandomMinutesInterval, RAND_MAX); + FILETIME ftValue; + SYSTEMTIME stValue; + ULONGLONG ullValue; + + // Convert MDY date to numerical date (SYSTEMTIME -> FILETIME -> ULONGLONG). + memset(&stValue, 0, sizeof(SYSTEMTIME)); + stValue.wYear = ttData.wBeginYear; + stValue.wMonth = ttData.wBeginMonth; + stValue.wDay = ttData.wBeginDay; + ::SystemTimeToFileTime(&stValue, &ftValue); + ullValue = ((ULONGLONG)(ftValue.dwHighDateTime) << 32) | ftValue.dwLowDateTime; + + // Wrap days. + while (wStartTime >= 1440) { + ullValue += (ULONGLONG)864000000000; + wStartTime -= 1440; + } + + // Convert numerical date to DMY (ULONGLONG -> FILETIME -> SYSTEMTIME). + ftValue.dwHighDateTime = ullValue >> 32; + ftValue.dwLowDateTime = ullValue & 0xffffffff; + ::FileTimeToSystemTime(&ftValue, &stValue); + + // Set new trigger date and time. + ttData.wBeginYear = stValue.wYear; + ttData.wBeginMonth = stValue.wMonth; + ttData.wBeginDay = stValue.wDay; + ttData.wStartHour = wStartTime / 60; + ttData.wStartMinute = wStartTime % 60; + } + + hr = pTrigger->SetTrigger(&ttData); + if (FAILED(hr)) goto finish; + } + + // Save the task. + CComQIPtr pTaskFile(pTask); + if (!pTaskFile) { hr = E_NOINTERFACE; goto finish; } + hr = pTaskFile->Save(NULL, TRUE); + } + +finish: + if (FAILED(hr)) { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(3); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_TASK_CREATE_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 3, hr ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + return hr; +} + + +UINT COpTaskCreate::SetFromRecord(MSIHANDLE hInstall, MSIHANDLE hRecord) +{ + UINT uiResult; + int iValue; + ATL::CAtlStringW sFolder; + + 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 = ::MsiRecordGetStringW(hRecord, 5, sFolder); + if (uiResult != ERROR_SUCCESS) return uiResult; + uiResult = ::MsiGetTargetPathW(hInstall, sFolder, m_sWorkingDirectory); + if (uiResult != ERROR_SUCCESS) return uiResult; + if (!m_sWorkingDirectory.IsEmpty() && m_sWorkingDirectory.GetAt(m_sWorkingDirectory.GetLength() - 1) == L'\\') { + // Trim trailing backslash. + m_sWorkingDirectory.Truncate(m_sWorkingDirectory.GetLength() - 1); + } + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 10, m_sAuthor); + if (uiResult != ERROR_SUCCESS) return uiResult; + + uiResult = ::MsiRecordFormatStringW(hInstall, hRecord, 11, 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, 12); + m_wIdleMinutes = iValue != MSI_NULL_INTEGER ? (WORD)iValue : 0; + + iValue = ::MsiRecordGetInteger(hRecord, 13); + m_wDeadlineMinutes = iValue != MSI_NULL_INTEGER ? (WORD)iValue : 0; + + m_dwMaxRuntimeMS = ::MsiRecordGetInteger(hRecord, 14); + if (m_dwMaxRuntimeMS == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + + return ERROR_SUCCESS; +} + + +UINT COpTaskCreate::SetTriggersFromView(MSIHANDLE hView) +{ + for (;;) { + UINT uiResult; + PMSIHANDLE hRecord; + TASK_TRIGGER ttData; + ULONGLONG ullValue; + FILETIME ftValue; + SYSTEMTIME stValue; + int iValue; + + // 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. + iValue = ::MsiRecordGetInteger(hRecord, 4); + if (iValue == MSI_NULL_INTEGER) return ERROR_INVALID_FIELD; + ttData.wStartHour = (WORD)(iValue / 60); + ttData.wStartMinute = (WORD)(iValue % 60); + + // Get StartTimeRand. + iValue = ::MsiRecordGetInteger(hRecord, 5); + ttData.wRandomMinutesInterval = iValue != MSI_NULL_INTEGER ? (WORD)iValue : 0; + + // 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 ERROR_SUCCESS; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskDelete +//////////////////////////////////////////////////////////////////////////// + +COpTaskDelete::COpTaskDelete(LPCWSTR pszTaskName, int iTicks) : + COpTypeSingleString(pszTaskName, iTicks) +{ +} + + +HRESULT COpTaskDelete::Execute(CSession *pSession) +{ + HRESULT hr; + CComPtr pService; + + hr = pService.CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER); + if (SUCCEEDED(hr)) { + // Windows Vista or newer. + CComVariant vEmpty; + CComPtr pTaskFolder; + + // Connect to local task service. + hr = pService->Connect(vEmpty, vEmpty, vEmpty, vEmpty); + if (FAILED(hr)) goto finish; + + // Get task folder. + hr = pService->GetFolder(CComBSTR(L"\\"), &pTaskFolder); + if (FAILED(hr)) goto finish; + + if (pSession->m_bRollbackEnabled) { + CComPtr pTask, pTaskOrig; + CComPtr pTaskDefinition; + CComPtr pPrincipal; + VARIANT_BOOL bEnabled; + TASK_LOGON_TYPE logonType; + CComBSTR sSSDL; + CComVariant vSSDL; + ATL::CAtlStringW sDisplayNameOrig; + UINT uiCount = 0; + + // Get the source task. + hr = pTaskFolder->GetTask(CComBSTR(m_sValue), &pTask); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { hr = S_OK; goto finish; } + else if (FAILED(hr)) goto finish; + + // Disable the task. + hr = pTask->get_Enabled(&bEnabled); + if (FAILED(hr)) goto finish; + if (bEnabled) { + // The task is enabled. + + // In case the task disabling fails halfway, try to re-enable it anyway on rollback. + pSession->m_olRollback.AddHead(new COpTaskEnable(m_sValue, TRUE)); + + // Disable it. + hr = pTask->put_Enabled(VARIANT_FALSE); + if (FAILED(hr)) goto finish; + } + + // Get task's definition. + hr = pTask->get_Definition(&pTaskDefinition); + if (FAILED(hr)) goto finish; + + // Get task principal. + hr = pTaskDefinition->get_Principal(&pPrincipal); + if (FAILED(hr)) goto finish; + + // Get task logon type. + hr = pPrincipal->get_LogonType(&logonType); + if (FAILED(hr)) goto finish; + + // Get task security descriptor. + hr = pTask->GetSecurityDescriptor(DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, &sSSDL); + if (hr == HRESULT_FROM_WIN32(ERROR_PRIVILEGE_NOT_HELD)) vSSDL.Clear(); + else if (FAILED(hr)) goto finish; + else { + V_VT (&vSSDL) = VT_BSTR; + V_BSTR(&vSSDL) = sSSDL.Detach(); + } + + // Register a backup copy of task. + do { + sDisplayNameOrig.Format(L"%ls (orig %u)", (LPCWSTR)m_sValue, ++uiCount); + hr = pTaskFolder->RegisterTaskDefinition( + CComBSTR(sDisplayNameOrig), // path + pTaskDefinition, // pDefinition + TASK_CREATE, // flags + vEmpty, // userId + vEmpty, // password + logonType, // logonType + vSSDL, // sddl + &pTaskOrig); // ppTask + } 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 COpTaskDelete(sDisplayNameOrig)); + if (FAILED(hr)) goto finish; + + // Order rollback action to restore from backup copy. + pSession->m_olRollback.AddHead(new COpTaskCopy(sDisplayNameOrig, m_sValue)); + + // Delete it. + hr = pTaskFolder->DeleteTask(CComBSTR(m_sValue), 0); + if (FAILED(hr)) goto finish; + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new COpTaskDelete(sDisplayNameOrig)); + } else { + // Delete the task. + hr = pTaskFolder->DeleteTask(CComBSTR(m_sValue), 0); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) hr = S_OK; + } + } else { + // Windows XP or older. + CComPtr pTaskScheduler; + + // Get task scheduler object. + hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); + if (FAILED(hr)) goto finish; + + if (pSession->m_bRollbackEnabled) { + CComPtr pTask; + DWORD dwFlags; + ATL::CAtlStringW sDisplayNameOrig; + UINT uiCount = 0; + + // Load the task. + hr = pTaskScheduler->Activate(m_sValue, IID_ITask, (IUnknown**)&pTask); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { hr = S_OK; goto finish; } + else if (FAILED(hr)) goto finish; + + // Disable the task. + hr = pTask->GetFlags(&dwFlags); + if (FAILED(hr)) goto finish; + 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 COpTaskEnable(m_sValue, TRUE)); + + // Disable it. + dwFlags |= TASK_FLAG_DISABLED; + hr = pTask->SetFlags(dwFlags); + if (FAILED(hr)) goto finish; + } + + // Prepare a backup copy of task. + do { + sDisplayNameOrig.Format(L"%ls (orig %u)", (LPCWSTR)m_sValue, ++uiCount); + hr = 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 COpTaskDelete(sDisplayNameOrig)); + if (FAILED(hr)) goto finish; + + // Save the backup copy. + CComQIPtr pTaskFile(pTask); + if (!pTaskFile) { hr = E_NOINTERFACE; goto finish; } + hr = pTaskFile->Save(NULL, TRUE); + if (FAILED(hr)) goto finish; + + // Order rollback action to restore from backup copy. + pSession->m_olRollback.AddHead(new COpTaskCopy(sDisplayNameOrig, m_sValue)); + + // Delete it. + hr = pTaskScheduler->Delete(m_sValue); + if (FAILED(hr)) goto finish; + + // Order commit action to delete backup copy. + pSession->m_olCommit.AddTail(new COpTaskDelete(sDisplayNameOrig)); + } else { + // Delete the task. + hr = pTaskScheduler->Delete(m_sValue); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) hr = S_OK; + } + } + +finish: + if (FAILED(hr)) { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(3); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_TASK_DELETE_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 3, hr ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + return hr; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskEnable +//////////////////////////////////////////////////////////////////////////// + +COpTaskEnable::COpTaskEnable(LPCWSTR pszTaskName, BOOL bEnable, int iTicks) : + m_bEnable(bEnable), + COpTypeSingleString(pszTaskName, iTicks) +{ +} + + +HRESULT COpTaskEnable::Execute(CSession *pSession) +{ + HRESULT hr; + CComPtr pService; + + hr = pService.CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER); + if (SUCCEEDED(hr)) { + // Windows Vista or newer. + CComVariant vEmpty; + CComPtr pTaskFolder; + CComPtr pTask; + VARIANT_BOOL bEnabled; + + // Connect to local task service. + hr = pService->Connect(vEmpty, vEmpty, vEmpty, vEmpty); + if (FAILED(hr)) goto finish; + + // Get task folder. + hr = pService->GetFolder(CComBSTR(L"\\"), &pTaskFolder); + if (FAILED(hr)) goto finish; + + // Get task. + hr = pTaskFolder->GetTask(CComBSTR(m_sValue), &pTask); + if (FAILED(hr)) goto finish; + + // Get currently enabled state. + hr = pTask->get_Enabled(&bEnabled); + if (FAILED(hr)) goto finish; + + // Modify enable state. + if (m_bEnable) { + if (pSession->m_bRollbackEnabled && !bEnabled) { + // 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 COpTaskEnable(m_sValue, TRUE)); + hr = S_OK; goto finish; + } else + bEnabled = VARIANT_TRUE; + } else { + if (pSession->m_bRollbackEnabled && bEnabled) { + // The task is enabled and we will disable it now. + // Order rollback to re-enable it. + pSession->m_olRollback.AddHead(new COpTaskEnable(m_sValue, TRUE)); + } + bEnabled = VARIANT_FALSE; + } + + // Set enable/disable. + hr = pTask->put_Enabled(bEnabled); + if (FAILED(hr)) goto finish; + } else { + // Windows XP or older. + CComPtr pTaskScheduler; + CComPtr pTask; + DWORD dwFlags; + + // Get task scheduler object. + hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); + if (FAILED(hr)) goto finish; + + // Load the task. + hr = pTaskScheduler->Activate(m_sValue, IID_ITask, (IUnknown**)&pTask); + if (FAILED(hr)) goto finish; + + // Get task's current flags. + hr = pTask->GetFlags(&dwFlags); + if (FAILED(hr)) goto finish; + + // Modify flags. + 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 COpTaskEnable(m_sValue, TRUE)); + hr = S_OK; goto finish; + } 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 COpTaskEnable(m_sValue, TRUE)); + } + dwFlags |= TASK_FLAG_DISABLED; + } + + // Set flags. + hr = pTask->SetFlags(dwFlags); + if (FAILED(hr)) goto finish; + + // Save the task. + CComQIPtr pTaskFile(pTask); + if (!pTaskFile) { hr = E_NOINTERFACE; goto finish; } + hr = pTaskFile->Save(NULL, TRUE); + if (FAILED(hr)) goto finish; + } + +finish: + if (FAILED(hr)) { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(3); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_TASK_ENABLE_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue ); + ::MsiRecordSetInteger(hRecordProg, 3, hr ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + return hr; +} + + +//////////////////////////////////////////////////////////////////////////// +// COpTaskCopy +//////////////////////////////////////////////////////////////////////////// + +COpTaskCopy::COpTaskCopy(LPCWSTR pszTaskSrc, LPCWSTR pszTaskDst, int iTicks) : + COpTypeSrcDstString(pszTaskSrc, pszTaskDst, iTicks) +{ +} + + +HRESULT COpTaskCopy::Execute(CSession *pSession) +{ + HRESULT hr; + CComPtr pService; + + hr = pService.CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER); + if (SUCCEEDED(hr)) { + // Windows Vista or newer. + CComVariant vEmpty; + CComPtr pTaskFolder; + CComPtr pTask, pTaskOrig; + CComPtr pTaskDefinition; + CComPtr pPrincipal; + TASK_LOGON_TYPE logonType; + CComBSTR sSSDL; + + // Connect to local task service. + hr = pService->Connect(vEmpty, vEmpty, vEmpty, vEmpty); + if (FAILED(hr)) goto finish; + + // Get task folder. + hr = pService->GetFolder(CComBSTR(L"\\"), &pTaskFolder); + if (FAILED(hr)) goto finish; + + // Get the source task. + hr = pTaskFolder->GetTask(CComBSTR(m_sValue1), &pTask); + if (FAILED(hr)) goto finish; + + // Get task's definition. + hr = pTask->get_Definition(&pTaskDefinition); + if (FAILED(hr)) goto finish; + + // Get task principal. + hr = pTaskDefinition->get_Principal(&pPrincipal); + if (FAILED(hr)) goto finish; + + // Get task logon type. + hr = pPrincipal->get_LogonType(&logonType); + if (FAILED(hr)) goto finish; + + // Get task security descriptor. + hr = pTask->GetSecurityDescriptor(DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, &sSSDL); + if (FAILED(hr)) goto finish; + + // Register a new task. + hr = pTaskFolder->RegisterTaskDefinition( + CComBSTR(m_sValue2), // path + pTaskDefinition, // pDefinition + TASK_CREATE, // flags + vEmpty, // userId + vEmpty, // password + logonType, // logonType + CComVariant(sSSDL), // sddl + &pTaskOrig); // ppTask + if (FAILED(hr)) goto finish; + } else { + // Windows XP or older. + CComPtr pTaskScheduler; + CComPtr pTask; + + // Get task scheduler object. + hr = pTaskScheduler.CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_ALL); + if (FAILED(hr)) goto finish; + + // Load the source task. + hr = pTaskScheduler->Activate(m_sValue1, IID_ITask, (IUnknown**)&pTask); + if (FAILED(hr)) goto finish; + + // Add with different name. + hr = 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 COpTaskDelete(m_sValue2)); + } + if (FAILED(hr)) goto finish; + + // Save the task. + CComQIPtr pTaskFile(pTask); + if (!pTaskFile) { hr = E_NOINTERFACE; goto finish; } + hr = pTaskFile->Save(NULL, TRUE); + if (FAILED(hr)) goto finish; + } + +finish: + if (FAILED(hr)) { + PMSIHANDLE hRecordProg = ::MsiCreateRecord(4); + ::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_TASK_COPY_FAILED); + ::MsiRecordSetStringW(hRecordProg, 2, m_sValue1 ); + ::MsiRecordSetStringW(hRecordProg, 3, m_sValue2 ); + ::MsiRecordSetInteger(hRecordProg, 4, hr ); + ::MsiProcessMessage(pSession->m_hInstall, INSTALLMESSAGE_ERROR, hRecordProg); + } + return hr; +} + +} // namespace MSICA diff --git a/msm/Makefile b/msm/Makefile new file mode 100644 index 0000000..065734f --- /dev/null +++ b/msm/Makefile @@ -0,0 +1,56 @@ +!INCLUDE "..\..\include\MSINast.mak" + +MSM_IMA_LOKALIZACIJO=1 + + +###################################################################### +# Error + +Vse :: \ + "$(JEZIK).$(CFG).$(PLAT).Error-1.idt" \ + "$(JEZIK).$(CFG).$(PLAT).Error-2.idt" + +"$(JEZIK).$(CFG).$(PLAT).Error-1.idt" : "Makefile" "..\..\include\MSINast.mak" + -if exist $@ del /f /q $@ + move /y << $@ > NUL +Error Message +i2 L0 +Error Error +< NUL +Error Message +i2 L0 +1250 Error Error +2550 Pri odpiranju namestitvenega paketa je prilo do napake. Obrnite se na svojo tehnino slubo. +2552 Pri pisanju v datoteko seznama opravil [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2560 Pri branju iz datoteke seznama opravil [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2553 Pri nastavljanju parametra [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2554 Pri brisanju datoteke [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2555 Pri premikanju datoteke [2] v [3] je prilo do napake [4]. Obrnite se na svojo tehnino slubo. +2556 Pri ustvarjanju razporejenega opravila [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2557 Pri brisanju razporejenega opravila [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2558 Pri o(ne)mogoanju razporejenega opravila [2] je prilo do napake [3]. Obrnite se na svojo tehnino slubo. +2559 Pri kopiranju razporejenega opravila [2] v [3] je prilo do napake [4]. Obrnite se na svojo tehnino slubo. +< NUL + + +###################################################################### +# Izdelava modula MSM +###################################################################### + +!INCLUDE "..\..\AOsn\msm\MSM.mak" diff --git a/res/en_GB.po b/res/en_GB.po new file mode 100644 index 0000000..98f1172 --- /dev/null +++ b/res/en_GB.po @@ -0,0 +1,100 @@ +msgid "" +msgstr "" +"Project-Id-Version: MSITSCA\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-06-07 21:16+0100\n" +"PO-Revision-Date: \n" +"Last-Translator: Simon Rozman \n" +"Language-Team: Amebis, d. o. o., Kamnik \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-KeywordsList: __\n" +"X-Poedit-Basepath: .\n" +"X-Generator: Poedit 1.5.5\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPath-1: ..\\msm\n" + +# Privzeta kodna stran ANSI za ta jezik (desetiško) +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:3 ..\msm/Sl.DebugU.x64.Error-2.idtx:3 +msgid "1250" +msgstr "1252" + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:6 ..\msm/Sl.DebugU.x64.Error-2.idtx:6 +msgid "" +"Pri branju iz datoteke seznama opravil »[2]« je prišlo do napake [3]. " +"Obrnite se na svojo tehnično službo." +msgstr "" +"Error [3] reading from \"[2]\" task list file. Please, contact your support " +"personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:8 ..\msm/Sl.DebugU.x64.Error-2.idtx:8 +msgid "" +"Pri brisanju datoteke »[2]« je prišlo do napake [3]. Obrnite se na svojo " +"tehnično službo." +msgstr "" +"Error [3] deleting \"[2]\" file. Please, contact your support personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:11 ..\msm/Sl.DebugU.x64.Error-2.idtx:11 +msgid "" +"Pri brisanju razporejenega opravila »[2]« je prišlo do napake [3]. Obrnite " +"se na svojo tehnično službo." +msgstr "" +"Error [3] deleting \"[2]\" scheduled task. Please, contact your support " +"personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:13 ..\msm/Sl.DebugU.x64.Error-2.idtx:13 +msgid "" +"Pri kopiranju razporejenega opravila »[2]« v »[3]« je prišlo do napake [4]. " +"Obrnite se na svojo tehnično službo." +msgstr "" +"Error [4] copying \"[2]\" scheduled task to \"[3]\". Please, contact your " +"support personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:7 ..\msm/Sl.DebugU.x64.Error-2.idtx:7 +msgid "" +"Pri nastavljanju parametra »[2]« je prišlo do napake [3]. Obrnite se na " +"svojo tehnično službo." +msgstr "" +"Error [3] setting \"[2]\" parameter. Please, contact your support personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:12 ..\msm/Sl.DebugU.x64.Error-2.idtx:12 +msgid "" +"Pri o(ne)mogočanju razporejenega opravila »[2]« je prišlo do napake [3]. " +"Obrnite se na svojo tehnično službo." +msgstr "" +"Error [3] enabling/disabling \"[2]\" scheduled task. Please, contact your " +"support personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:4 ..\msm/Sl.DebugU.x64.Error-2.idtx:4 +msgid "" +"Pri odpiranju namestitvenega paketa je prišlo do napake. Obrnite se na svojo " +"tehnično službo." +msgstr "" +"Error opening installation package. Please, contact your support personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:5 ..\msm/Sl.DebugU.x64.Error-2.idtx:5 +msgid "" +"Pri pisanju v datoteko seznama opravil »[2]« je prišlo do napake [3]. " +"Obrnite se na svojo tehnično službo." +msgstr "" +"Error [3] writing to \"[2]\" task list file. Please, contact your support " +"personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:9 ..\msm/Sl.DebugU.x64.Error-2.idtx:9 +msgid "" +"Pri premikanju datoteke »[2]« v »[3]« je prišlo do napake [4]. Obrnite se na " +"svojo tehnično službo." +msgstr "" +"Error [4] moving \"[2]\" file to \"[3]\". Please, contact your support " +"personnel." + +#: ..\msm/Sl.DebugU.Win32.Error-2.idtx:10 ..\msm/Sl.DebugU.x64.Error-2.idtx:10 +msgid "" +"Pri ustvarjanju razporejenega opravila »[2]« je prišlo do napake [3]. " +"Obrnite se na svojo tehnično službo." +msgstr "" +"Error [3] creating \"[2]\" scheduled task. Please, contact your support " +"personnel." diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..a27b824 --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..9e433b4 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,35 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#pragma once + +#if defined(_UNICODE) && !defined(UNICODE) +#define UNICODE +#endif + +#if defined(_WIN32) && !defined(WIN32) +#define WIN32 +#endif + +#ifndef STRICT +#define STRICT +#endif + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#define _WIN32_WINNT 0x0501 // Include Windows XP symbols +#define _WINSOCKAPI_ // Prevent inclusion of winsock.h in windows.h +//#define _ATL_APARTMENT_THREADED +//#define _ATL_NO_AUTOMATIC_NAMESPACE +//#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // Some CString constructors will be explicit + +#include +#include +#include + +#include +#include +#include +#include + +#include "MSICALib.h"