Popravil sem napako pri preverjanju statusa, zaradi katere smo tudi v primeru napak med evaluacijo razporejenih opravil vseeno zapisali namestitvena skripta in vrnili uspešen rezultat. Dodal sem funkcijo MsiRecordGetStream() za branje binarnih podatkov iz tabel MSI. Usposobil sem nameščanje certifikatov.
370 lines
14 KiB
C++
370 lines
14 KiB
C++
#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<COperation*>(sizeof(COperation*))
|
|
{
|
|
}
|
|
|
|
|
|
void COpList::Free()
|
|
{
|
|
POSITION pos;
|
|
|
|
for (pos = GetHeadPosition(); pos;) {
|
|
COperation *pOp = GetNext(pos);
|
|
COpList *pOpList = dynamic_cast<COpList*>(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();
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Helper functions
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
UINT SaveSequence(MSIHANDLE hInstall, LPCTSTR szActionExecute, LPCTSTR szActionCommit, LPCTSTR szActionRollback, const COpList &olExecute)
|
|
{
|
|
HRESULT hr;
|
|
UINT uiResult;
|
|
ATL::CAtlString sSequenceFilename;
|
|
ATL::CAtlFile fSequence;
|
|
PMSIHANDLE hRecordProg = ::MsiCreateRecord(3);
|
|
|
|
// Prepare our own sequence script file.
|
|
// The InstallCertificates is a deferred custom action, thus all this information will be unavailable to it.
|
|
// Therefore save all required info to file now.
|
|
{
|
|
LPTSTR szBuffer = sSequenceFilename.GetBuffer(MAX_PATH);
|
|
::GetTempPath(MAX_PATH, szBuffer);
|
|
::GetTempFileName(szBuffer, _T("TS"), 0, szBuffer);
|
|
sSequenceFilename.ReleaseBuffer();
|
|
}
|
|
// Save execute sequence to file.
|
|
hr = olExecute.SaveToFile(sSequenceFilename);
|
|
if (SUCCEEDED(hr)) {
|
|
// Store sequence script file names to properties for deferred custiom actions.
|
|
uiResult = ::MsiSetProperty(hInstall, szActionExecute, sSequenceFilename);
|
|
if (uiResult == ERROR_SUCCESS) {
|
|
LPCTSTR pszExtension = ::PathFindExtension(sSequenceFilename);
|
|
ATL::CAtlString sSequenceFilename2;
|
|
|
|
sSequenceFilename2.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension);
|
|
uiResult = ::MsiSetProperty(hInstall, szActionRollback, sSequenceFilename2);
|
|
if (uiResult == ERROR_SUCCESS) {
|
|
sSequenceFilename2.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension);
|
|
uiResult = ::MsiSetProperty(hInstall, szActionCommit, sSequenceFilename2);
|
|
if (uiResult != ERROR_SUCCESS) {
|
|
::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_PROPERTY_SET);
|
|
::MsiRecordSetString (hRecordProg, 2, szActionCommit );
|
|
::MsiRecordSetInteger(hRecordProg, 3, uiResult );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
} else {
|
|
::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_PROPERTY_SET);
|
|
::MsiRecordSetString (hRecordProg, 2, szActionRollback );
|
|
::MsiRecordSetInteger(hRecordProg, 3, uiResult );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
} else {
|
|
::MsiRecordSetInteger(hRecordProg, 1, ERROR_INSTALL_PROPERTY_SET);
|
|
::MsiRecordSetString (hRecordProg, 2, szActionExecute );
|
|
::MsiRecordSetInteger(hRecordProg, 3, uiResult );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
if (uiResult != ERROR_SUCCESS) ::DeleteFile(sSequenceFilename);
|
|
} else {
|
|
uiResult = ERROR_INSTALL_SCRIPT_WRITE;
|
|
::MsiRecordSetInteger(hRecordProg, 1, uiResult );
|
|
::MsiRecordSetString (hRecordProg, 2, sSequenceFilename);
|
|
::MsiRecordSetInteger(hRecordProg, 3, hr );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
|
|
return uiResult;
|
|
}
|
|
|
|
|
|
UINT ExecuteSequence(MSIHANDLE hInstall)
|
|
{
|
|
UINT uiResult;
|
|
HRESULT hr;
|
|
BOOL bIsCoInitialized = SUCCEEDED(::CoInitialize(NULL));
|
|
ATL::CAtlString sSequenceFilename;
|
|
|
|
uiResult = ::MsiGetProperty(hInstall, _T("CustomActionData"), sSequenceFilename);
|
|
if (uiResult == ERROR_SUCCESS) {
|
|
MSICA::COpList lstOperations;
|
|
BOOL bIsCleanup = ::MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || ::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);
|
|
|
|
// Load operation sequence.
|
|
hr = lstOperations.LoadFromFile(sSequenceFilename);
|
|
if (SUCCEEDED(hr)) {
|
|
MSICA::CSession session;
|
|
|
|
session.m_hInstall = hInstall;
|
|
|
|
// In case of commit/rollback, continue sequence on error, to do as much cleanup as possible.
|
|
session.m_bContinueOnError = bIsCleanup;
|
|
|
|
// Execute the operations.
|
|
hr = lstOperations.Execute(&session);
|
|
if (!bIsCleanup) {
|
|
// Save cleanup scripts of delayed action regardless of action's execution status.
|
|
// Rollback action MUST be scheduled in InstallExecuteSequence before this action! Otherwise cleanup won't be performed in case this action execution failed.
|
|
LPCTSTR pszExtension = ::PathFindExtension(sSequenceFilename);
|
|
ATL::CAtlString sSequenceFilenameCM, sSequenceFilenameRB;
|
|
HRESULT hr;
|
|
|
|
sSequenceFilenameRB.Format(_T("%.*ls-rb%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension);
|
|
sSequenceFilenameCM.Format(_T("%.*ls-cm%ls"), pszExtension - (LPCTSTR)sSequenceFilename, (LPCTSTR)sSequenceFilename, pszExtension);
|
|
|
|
// After commit, delete rollback file. After rollback, delete commit file.
|
|
session.m_olCommit.AddTail(new MSICA::COpFileDelete(
|
|
#ifdef _UNICODE
|
|
sSequenceFilenameRB
|
|
#else
|
|
ATL::CAtlStringW(sSequenceFilenameRB)
|
|
#endif
|
|
));
|
|
session.m_olRollback.AddTail(new MSICA::COpFileDelete(
|
|
#ifdef _UNICODE
|
|
sSequenceFilenameCM
|
|
#else
|
|
ATL::CAtlStringW(sSequenceFilenameCM)
|
|
#endif
|
|
));
|
|
|
|
// Save commit file first.
|
|
hr = session.m_olCommit.SaveToFile(sSequenceFilenameCM);
|
|
if (SUCCEEDED(hr)) {
|
|
// Save rollback file next.
|
|
hr = session.m_olRollback.SaveToFile(sSequenceFilenameRB);
|
|
if (SUCCEEDED(hr)) {
|
|
uiResult = ERROR_SUCCESS;
|
|
} else {
|
|
// Saving rollback file failed.
|
|
PMSIHANDLE hRecordProg = ::MsiCreateRecord(3);
|
|
uiResult = ERROR_INSTALL_SCRIPT_WRITE;
|
|
::MsiRecordSetInteger(hRecordProg, 1, uiResult );
|
|
::MsiRecordSetString (hRecordProg, 2, sSequenceFilenameRB);
|
|
::MsiRecordSetInteger(hRecordProg, 3, hr );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
} else {
|
|
// Saving commit file failed.
|
|
PMSIHANDLE hRecordProg = ::MsiCreateRecord(3);
|
|
uiResult = ERROR_INSTALL_SCRIPT_WRITE;
|
|
::MsiRecordSetInteger(hRecordProg, 1, uiResult );
|
|
::MsiRecordSetString (hRecordProg, 2, sSequenceFilenameCM);
|
|
::MsiRecordSetInteger(hRecordProg, 3, hr );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
|
|
if (uiResult != ERROR_SUCCESS) {
|
|
// The commit and/or rollback scripts were not written to file successfully. Perform the cleanup immediately.
|
|
session.m_bContinueOnError = TRUE;
|
|
session.m_bRollbackEnabled = FALSE;
|
|
session.m_olRollback.Execute(&session);
|
|
::DeleteFile(sSequenceFilenameRB);
|
|
}
|
|
} else {
|
|
// No cleanup after cleanup support.
|
|
uiResult = ERROR_SUCCESS;
|
|
}
|
|
|
|
if (FAILED(hr)) {
|
|
// Execution of the action failed.
|
|
uiResult = HRESULT_CODE(hr);
|
|
}
|
|
|
|
::DeleteFile(sSequenceFilename);
|
|
} else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && bIsCleanup) {
|
|
// Sequence file not found and this is rollback/commit action. Either of the following scenarios are possible:
|
|
// - The delayed action failed to save the rollback/commit file. The delayed action performed cleanup itself. No further action is required.
|
|
// - Somebody removed the rollback/commit file between delayed action and rollback/commit action. No further action is possible.
|
|
uiResult = ERROR_SUCCESS;
|
|
} else {
|
|
// Sequence loading failed. Probably, LOCAL SYSTEM doesn't have read access to user's temp directory.
|
|
PMSIHANDLE hRecordProg = ::MsiCreateRecord(3);
|
|
uiResult = ERROR_INSTALL_SCRIPT_READ;
|
|
::MsiRecordSetInteger(hRecordProg, 1, uiResult );
|
|
::MsiRecordSetString (hRecordProg, 2, sSequenceFilename);
|
|
::MsiRecordSetInteger(hRecordProg, 3, hr );
|
|
::MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecordProg);
|
|
}
|
|
|
|
lstOperations.Free();
|
|
} else {
|
|
// Couldn't get CustomActionData property. uiResult has the error code.
|
|
}
|
|
|
|
if (bIsCoInitialized) ::CoUninitialize();
|
|
return uiResult;
|
|
}
|
|
|
|
} // namespace MSICA
|