From a00c2f3c91a12b42387fa65a9a61c5026f9ed40b Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Wed, 12 Mar 2014 13:00:59 +0000 Subject: [PATCH] AMSICA => MSICALib --- FileOp.cpp | 88 +++ MSICALib.cpp | 184 +++++ MSICALib.h | 1429 ++++++++++++++++++++++++++++++++++++++ MSICALib.vcxproj | 135 ++++ MSICALib.vcxproj.filters | 47 ++ RegOp.cpp | 724 +++++++++++++++++++ TSOp.cpp | 1038 +++++++++++++++++++++++++++ msm/Makefile | 56 ++ res/en_GB.po | 100 +++ stdafx.cpp | 1 + stdafx.h | 35 + 11 files changed, 3837 insertions(+) create mode 100644 FileOp.cpp create mode 100644 MSICALib.cpp create mode 100644 MSICALib.h create mode 100644 MSICALib.vcxproj create mode 100644 MSICALib.vcxproj.filters create mode 100644 RegOp.cpp create mode 100644 TSOp.cpp create mode 100644 msm/Makefile create mode 100644 res/en_GB.po create mode 100644 stdafx.cpp create mode 100644 stdafx.h 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"