// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" // sql queries LPCWSTR vcsSqlDatabaseQuery = L"SELECT `SqlDb`, `Server`, `Instance`, `Database`, " L"`Component_`, `User_`, `FileSpec_`, `FileSpec_Log`, `Attributes` " L"FROM `Wix4SqlDatabase`"; enum eSqlDatabaseQuery { sdqSqlDb = 1, sdqServer, sdqInstance, sdqDatabase, sdqComponent, sdqUser, sdqDbFileSpec, sdqLogFileSpec, sdqAttributes }; LPCWSTR vcsSqlFileSpecQuery = L"SELECT `FileSpec`, `Name`, `Filename`, `Size`, " L"`MaxSize`, `GrowthSize` FROM `Wix4SqlFileSpec` WHERE `FileSpec`=?"; enum eSqlFileSpecQuery { sfsqFileSpec = 1, sfsqName, sfsqFilename, sfsqSize, sfsqMaxSize, sfsqGrowth }; // prototypes for private helper functions static HRESULT NewDb( __out SCA_DB** ppsd ); static SCA_DB* AddDbToList( __in SCA_DB* psdList, __in SCA_DB* psd ); static HRESULT SchedCreateDatabase( __in SCA_DB* psd ); static HRESULT SchedDropDatabase( __in LPCWSTR wzKey, LPCWSTR wzServer, __in LPCWSTR wzInstance, __in LPCWSTR wzDatabase, __in int iAttributes, __in BOOL fIntegratedAuth, __in LPCWSTR wzUser, __in LPCWSTR wzPassword ); static HRESULT GetFileSpec( __in MSIHANDLE hViewFileSpec, __in LPCWSTR wzKey, __in SQL_FILESPEC* psf ); HRESULT ScaDbsRead( __inout SCA_DB** ppsdList, __in SCA_ACTION saAction ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; PMSIHANDLE hView; PMSIHANDLE hRec; PMSIHANDLE hViewFileSpec = NULL; LPWSTR pwzData = NULL; LPWSTR pwzId = NULL; LPWSTR pwzComponent = NULL; SCA_DB* psd = NULL; if (S_OK != WcaTableExists(L"Wix4SqlDatabase")) { WcaLog(LOGMSG_VERBOSE, "Skipping ScaCreateDatabase() - Wix4SqlDatabase table not present"); ExitFunction1(hr = S_FALSE); } if (S_OK == WcaTableExists(L"Wix4SqlFileSpec")) { hr = WcaOpenView(vcsSqlFileSpecQuery, &hViewFileSpec); ExitOnFailure(hr, "failed to open view on Wix4SqlFileSpec table"); } // loop through all the sql databases hr = WcaOpenExecuteView(vcsSqlDatabaseQuery, &hView); ExitOnFailure(hr, "Failed to open view on Wix4SqlDatabase table"); while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) { BOOL fHasComponent = FALSE; INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; hr = WcaGetRecordString(hRec, sdqSqlDb, &pwzId); ExitOnFailure(hr, "Failed to get Wix4SqlDatabase.SqlDb"); hr = WcaGetRecordString(hRec, sdqComponent, &pwzComponent); ExitOnFailure(hr, "Failed to get Component for database: '%ls'", psd->wzKey); if (pwzComponent && *pwzComponent) { fHasComponent = TRUE; er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzComponent, &isInstalled, &isAction); hr = HRESULT_FROM_WIN32(er); ExitOnFailure(hr, "Failed to get state for component: %ls", pwzComponent); // If we're doing install but the Component is not being installed or we're doing // uninstall but the Component is not being uninstalled, skip it. if ((WcaIsInstalling(isInstalled, isAction) && SCA_ACTION_INSTALL != saAction) || (WcaIsUninstalling(isInstalled, isAction) && SCA_ACTION_UNINSTALL != saAction)) { continue; } } hr = NewDb(&psd); ExitOnFailure(hr, "Failed to allocate memory for new database: %D", pwzId); hr = ::StringCchCopyW(psd->wzKey, countof(psd->wzKey), pwzId); ExitOnFailure(hr, "Failed to copy Wix4SqlDatabase.SqlDbL: %ls", pwzId); hr = ::StringCchCopyW(psd->wzComponent, countof(psd->wzComponent), pwzComponent); ExitOnFailure(hr, "Failed to copy Wix4SqlDatabase.Component_: %ls", pwzComponent); psd->fHasComponent = fHasComponent; psd->isInstalled = isInstalled; psd->isAction = isAction; hr = WcaGetRecordFormattedString(hRec, sdqServer, &pwzData); ExitOnFailure(hr, "Failed to get Server for database: '%ls'", psd->wzKey); hr = ::StringCchCopyW(psd->wzServer, countof(psd->wzServer), pwzData); ExitOnFailure(hr, "Failed to copy server string to database object:%ls", pwzData); hr = WcaGetRecordFormattedString(hRec, sdqInstance, &pwzData); ExitOnFailure(hr, "Failed to get Instance for database: '%ls'", psd->wzKey); hr = ::StringCchCopyW(psd->wzInstance, countof(psd->wzInstance), pwzData); ExitOnFailure(hr, "Failed to copy instance string to database object:%ls", pwzData); hr = WcaGetRecordFormattedString(hRec, sdqDatabase, &pwzData); ExitOnFailure(hr, "Failed to get Database for database: '%ls'", psd->wzKey); hr = ::StringCchCopyW(psd->wzDatabase, countof(psd->wzDatabase), pwzData); ExitOnFailure(hr, "Failed to copy database string to database object:%ls", pwzData); hr = WcaGetRecordInteger(hRec, sdqAttributes, &psd->iAttributes); ExitOnFailure(hr, "Failed to get Wix4SqlDatabase.Attributes"); hr = WcaGetRecordFormattedString(hRec, sdqUser, &pwzData); ExitOnFailure(hr, "Failed to get User record for database: '%ls'", psd->wzKey); // if a user was specified if (*pwzData) { psd->fUseIntegratedAuth = FALSE; hr = ScaGetUser(pwzData, &psd->scau); ExitOnFailure(hr, "Failed to get user information for database: '%ls'", psd->wzKey); } else { psd->fUseIntegratedAuth = TRUE; // integrated authorization doesn't have a User record } hr = WcaGetRecordString(hRec, sdqDbFileSpec, &pwzData); ExitOnFailure(hr, "Failed to get Database FileSpec for database: '%ls'", psd->wzKey); // if a database filespec was specified if (*pwzData) { hr = GetFileSpec(hViewFileSpec, pwzData, &psd->sfDb); ExitOnFailure(hr, "failed to get FileSpec for: %ls", pwzData); if (S_OK == hr) { psd->fHasDbSpec = TRUE; } } hr = WcaGetRecordString(hRec, sdqLogFileSpec, &pwzData); ExitOnFailure(hr, "Failed to get Log FileSpec for database: '%ls'", psd->wzKey); // if a log filespec was specified if (*pwzData) { hr = GetFileSpec(hViewFileSpec, pwzData, &psd->sfLog); ExitOnFailure(hr, "failed to get FileSpec for: %ls", pwzData); if (S_OK == hr) { psd->fHasLogSpec = TRUE; } } *ppsdList = AddDbToList(*ppsdList, psd); psd = NULL; // set the db NULL so it doesn't accidentally get freed below } if (E_NOMOREITEMS == hr) { hr = S_OK; } ExitOnFailure(hr, "Failure occured while processing Wix4SqlDatabase table"); LExit: if (psd) { ScaDbsFreeList(psd); } ReleaseStr(pwzComponent); ReleaseStr(pwzId); ReleaseStr(pwzData); return hr; } SCA_DB* ScaDbsFindDatabase( __in LPCWSTR wzSqlDb, __in SCA_DB* psdList ) { SCA_DB* psd = NULL; for (psd = psdList; psd; psd = psd->psdNext) { if (0 == lstrcmpW(wzSqlDb, psd->wzKey)) { break; } } return psd; } HRESULT ScaDbsInstall( __in SCA_DB* psdList ) { HRESULT hr = S_FALSE; // assume nothing will be done SCA_DB* psd = NULL; for (psd = psdList; psd; psd = psd->psdNext) { if (psd->fHasComponent) { // if we need to drop, do that first if (((psd->iAttributes & SCADB_DROP_ON_INSTALL) && WcaIsInstalling(psd->isInstalled, psd->isAction) && !WcaIsReInstalling(psd->isInstalled, psd->isAction)) || ((psd->iAttributes & SCADB_DROP_ON_REINSTALL) && WcaIsReInstalling(psd->isInstalled, psd->isAction))) { hr = SchedDropDatabase(psd->wzKey, psd->wzServer, psd->wzInstance, psd->wzDatabase, psd->iAttributes, psd->fUseIntegratedAuth, psd->scau.wzName, psd->scau.wzPassword); ExitOnFailure(hr, "Failed to drop database %ls", psd->wzKey); } // if installing this component if (((psd->iAttributes & SCADB_CREATE_ON_INSTALL) && WcaIsInstalling(psd->isInstalled, psd->isAction) && !WcaIsReInstalling(psd->isInstalled, psd->isAction)) || ((psd->iAttributes & SCADB_CREATE_ON_REINSTALL) && WcaIsReInstalling(psd->isInstalled, psd->isAction))) { hr = SchedCreateDatabase(psd); ExitOnFailure(hr, "Failed to ensure database %ls exists", psd->wzKey); } } } LExit: return hr; } HRESULT ScaDbsUninstall( __in SCA_DB* psdList ) { HRESULT hr = S_FALSE; // assume nothing will be done SCA_DB* psd = NULL; for (psd = psdList; psd; psd = psd->psdNext) { if (psd->fHasComponent) { // if we need to drop do that first if ((psd->iAttributes & SCADB_DROP_ON_UNINSTALL) && WcaIsUninstalling(psd->isInstalled, psd->isAction)) { hr = SchedDropDatabase(psd->wzKey, psd->wzServer, psd->wzInstance, psd->wzDatabase, psd->iAttributes, psd->fUseIntegratedAuth, psd->scau.wzName, psd->scau.wzPassword); ExitOnFailure(hr, "Failed to drop database %ls", psd->wzKey); } // install the db if ((psd->iAttributes & SCADB_CREATE_ON_UNINSTALL) && WcaIsUninstalling(psd->isInstalled, psd->isAction)) { hr = SchedCreateDatabase(psd); ExitOnFailure(hr, "Failed to ensure database %ls exists", psd->wzKey); } } } LExit: return hr; } void ScaDbsFreeList( __in SCA_DB* psdList ) { SCA_DB* psdDelete = psdList; while (psdList) { psdDelete = psdList; psdList = psdList->psdNext; MemFree(psdDelete); } } // private helper functions static HRESULT NewDb( __out SCA_DB** ppsd ) { HRESULT hr = S_OK; SCA_DB* psd = static_cast(MemAlloc(sizeof(SCA_DB), TRUE)); ExitOnNull(psd, hr, E_OUTOFMEMORY, "failed to allocate memory for new database element"); *ppsd = psd; LExit: return hr; } static SCA_DB* AddDbToList( __in SCA_DB* psdList, __in SCA_DB* psd ) { if (psdList) { SCA_DB* psdT = psdList; while (psdT->psdNext) { psdT = psdT->psdNext; } psdT->psdNext = psd; } else { psdList = psd; } return psdList; } static HRESULT SchedCreateDatabase( __in SCA_DB* psd ) { HRESULT hr = S_OK; WCHAR* pwzCustomActionData = NULL; hr = WcaWriteStringToCaData(psd->wzKey, &pwzCustomActionData); ExitOnFailure(hr, "failed to add DBKey to CustomActionData"); hr = WcaWriteStringToCaData(psd->wzServer, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server name to CustomActionData"); hr = WcaWriteStringToCaData(psd->wzInstance, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server instance to CustomActionData"); hr = WcaWriteStringToCaData(psd->wzDatabase, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add database name to CustomActionData"); hr = WcaWriteIntegerToCaData(psd->iAttributes, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add Sql attributes to CustomActionData"); hr = WcaWriteStringToCaData(psd->fUseIntegratedAuth ? L"1" : L"0", &pwzCustomActionData); ExitOnFailure(hr, "Failed to add if integrated connection to CustomActionData"); hr = WcaWriteStringToCaData(psd->scau.wzName, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server user to CustomActionData"); hr = WcaWriteStringToCaData(psd->scau.wzPassword, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add user password to CustomActionData"); // Check to see if the database exists, if it does not then schedule a rollback // so we clean up after ourselves if the creation of the database fails or is // aborted. It is interesting to note that we can do this check here because the // deferred CustomActions are Impersonated. That means this scheduling action and // the execution actions all run with the same user context, so it is safe to // to do the check. hr = SqlDatabaseExists(psd->wzServer, psd->wzInstance, psd->wzDatabase, psd->fUseIntegratedAuth, psd->scau.wzName, psd->scau.wzPassword, NULL); if (S_FALSE == hr) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackCreateDatabase"), pwzCustomActionData, COST_SQL_CREATEDB); ExitOnFailure(hr, "Failed to schedule RollbackCreateDatabase action"); } // database filespec if (psd->fHasDbSpec) { hr = WcaWriteStringToCaData(L"1", &pwzCustomActionData); ExitOnFailure(hr, "failed to specify that do have db.filespec to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfDb.wzName, &pwzCustomActionData); ExitOnFailure(hr, "failed to add FileSpec.Name to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfDb.wzFilename, &pwzCustomActionData); ExitOnFailure(hr, "failed to add FileSpec.Filename to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfDb.wzSize, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.Size to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfDb.wzMaxSize, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.MaxSize to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfDb.wzGrow, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.GrowthSize to CustomActionData"); } else { hr = WcaWriteStringToCaData(L"0", &pwzCustomActionData); ExitOnFailure(hr, "failed to specify that do not have db.filespec to CustomActionData"); } // log filespec if (psd->fHasLogSpec) { hr = WcaWriteStringToCaData(L"1", &pwzCustomActionData); ExitOnFailure(hr, "failed to specify that do have log.filespec to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfLog.wzName, &pwzCustomActionData); ExitOnFailure(hr, "failed to add FileSpec.Name to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfLog.wzFilename, &pwzCustomActionData); ExitOnFailure(hr, "failed to add FileSpec.Filename to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfLog.wzSize, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.Size to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfLog.wzMaxSize, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.MaxSize to CustomActionData"); hr = WcaWriteStringToCaData(psd->sfLog.wzGrow, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add FileSpec.GrowthSize to CustomActionData"); } else { hr = WcaWriteStringToCaData(L"0", &pwzCustomActionData); ExitOnFailure(hr, "failed to specify that do not have log.filespec to CustomActionData"); } // schedule the CreateDatabase action hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateDatabase"), pwzCustomActionData, COST_SQL_CREATEDB); ExitOnFailure(hr, "Failed to schedule CreateDatabase action"); LExit: ReleaseStr(pwzCustomActionData); return hr; } HRESULT SchedDropDatabase( __in LPCWSTR wzKey, __in LPCWSTR wzServer, __in LPCWSTR wzInstance, __in LPCWSTR wzDatabase, __in int iAttributes, __in BOOL fIntegratedAuth, __in LPCWSTR wzUser, __in LPCWSTR wzPassword ) { HRESULT hr = S_OK; WCHAR* pwzCustomActionData = NULL; hr = WcaWriteStringToCaData(wzKey, &pwzCustomActionData); ExitOnFailure(hr, "failed to add DBKey to CustomActionData"); hr = WcaWriteStringToCaData(wzServer, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server name to CustomActionData"); hr = WcaWriteStringToCaData(wzInstance, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server instance to CustomActionData"); hr = WcaWriteStringToCaData(wzDatabase, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add database name to CustomActionData"); hr = WcaWriteIntegerToCaData(iAttributes, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server name to CustomActionData"); hr = WcaWriteStringToCaData(fIntegratedAuth ? L"1" : L"0", &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server name to CustomActionData"); hr = WcaWriteStringToCaData(wzUser, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add server user to CustomActionData"); hr = WcaWriteStringToCaData(wzPassword, &pwzCustomActionData); ExitOnFailure(hr, "Failed to add user password to CustomActionData"); hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"DropDatabase"), pwzCustomActionData, COST_SQL_DROPDB); ExitOnFailure(hr, "Failed to schedule DropDatabase action"); LExit: ReleaseStr(pwzCustomActionData); return hr; } HRESULT GetFileSpec( __in MSIHANDLE hViewFileSpec, __in LPCWSTR wzKey, __in SQL_FILESPEC* psf ) { HRESULT hr = S_OK; PMSIHANDLE hRecFileSpec, hRec; LPWSTR pwzData = NULL; // create a record to do the fetch hRecFileSpec = ::MsiCreateRecord(1); if (!hRecFileSpec) { ExitOnFailure(hr = E_UNEXPECTED, "failed to create record for filespec: %ls", wzKey); } hr = WcaSetRecordString(hRecFileSpec, 1, wzKey); ExitOnFailure(hr, "failed to set record string for filespec: %ls", wzKey); // get the FileSpec record hr = WcaExecuteView(hViewFileSpec, hRecFileSpec); ExitOnFailure(hr, "failed to execute view on Wix4SqlFileSpec table for filespec: %ls", wzKey); hr = WcaFetchSingleRecord(hViewFileSpec, &hRec); ExitOnFailure(hr, "failed to get record for filespec: %ls", wzKey); // read the data out of the filespec record hr = WcaGetRecordFormattedString(hRec, sfsqName, &pwzData); ExitOnFailure(hr, "Failed to get Wix4SqlFileSpec.Name for filespec: %ls", wzKey); hr = ::StringCchCopyW(psf->wzName, countof(psf->wzName), pwzData); ExitOnFailure(hr, "Failed to copy Wix4SqlFileSpec.Name string: %ls", pwzData); hr = WcaGetRecordFormattedString(hRec, sfsqFilename, &pwzData); ExitOnFailure(hr, "Failed to get Wix4SqlFileSpec.Filename for filespec: %ls", wzKey); if (*pwzData) { hr = ::StringCchCopyW(psf->wzFilename, countof(psf->wzFilename), pwzData); ExitOnFailure(hr, "Failed to copy filename to filespec object: %ls", pwzData); } else // if there is no file, skip this FILESPEC { WcaLog(LOGMSG_VERBOSE, "No filename specified, skipping FileSpec: %ls", psf->wzName); ExitFunction1(hr = S_FALSE); } hr = WcaGetRecordFormattedString(hRec, sfsqSize, &pwzData); ExitOnFailure(hr, "Failed to get Wix4SqlFileSpec.Size for filespec: %ls", wzKey); if (*pwzData) { hr = ::StringCchCopyW(psf->wzSize, countof(psf->wzSize), pwzData); ExitOnFailure(hr, "Failed to copy size to filespec object: %ls", pwzData); } else { psf->wzSize[0] = 0; } hr = WcaGetRecordFormattedString(hRec, sfsqMaxSize, &pwzData); ExitOnFailure(hr, "Failed to get Wix4SqlFileSpec.MaxSize for filespec: %ls", wzKey); if (*pwzData) { hr = ::StringCchCopyW(psf->wzMaxSize, countof(psf->wzMaxSize), pwzData); ExitOnFailure(hr, "Failed to copy max size to filespec object: %ls", pwzData); } else { psf->wzMaxSize[0] = 0; } hr = WcaGetRecordFormattedString(hRec, sfsqGrowth, &pwzData); ExitOnFailure(hr, "Failed to get Wix4SqlFileSpec.GrowthSize for filespec: %ls", wzKey); if (*pwzData) { hr = ::StringCchCopyW(psf->wzGrow, countof(psf->wzGrow), pwzData); ExitOnFailure(hr, "Failed to copy growth size to filespec object: %ls", pwzData); } else { psf->wzGrow[0] = 0; } hr = S_OK; LExit: ReleaseStr(pwzData); return hr; }