From 7d813eaad8eaca04a687d1bb942316232d1c54fd Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 16 Dec 2018 13:53:48 -0600 Subject: Import implementation of SqlCA from old repo's scasched/scaexec. --- src/ca/CustomMsiErrors.h | 10 + src/ca/precomp.h | 14 + src/ca/sca.h | 33 +++ src/ca/scacost.h | 7 + src/ca/scadb.cpp | 587 ++++++++++++++++++++++++++++++++++++++ src/ca/scadb.h | 55 ++++ src/ca/scaexec.cpp | 393 +++++++++++++++++++++++++ src/ca/scasql.cpp | 113 ++++++++ src/ca/scasqlstr.cpp | 728 +++++++++++++++++++++++++++++++++++++++++++++++ src/ca/scasqlstr.h | 51 ++++ src/ca/scauser.cpp | 82 ++++++ src/ca/scauser.h | 40 +++ src/ca/sqlca.def | 8 +- src/ca/sqlca.vcxproj | 11 + 14 files changed, 2131 insertions(+), 1 deletion(-) create mode 100644 src/ca/CustomMsiErrors.h create mode 100644 src/ca/sca.h create mode 100644 src/ca/scacost.h create mode 100644 src/ca/scadb.cpp create mode 100644 src/ca/scadb.h create mode 100644 src/ca/scaexec.cpp create mode 100644 src/ca/scasql.cpp create mode 100644 src/ca/scasqlstr.cpp create mode 100644 src/ca/scasqlstr.h create mode 100644 src/ca/scauser.cpp create mode 100644 src/ca/scauser.h diff --git a/src/ca/CustomMsiErrors.h b/src/ca/CustomMsiErrors.h new file mode 100644 index 00000000..b568d01c --- /dev/null +++ b/src/ca/CustomMsiErrors.h @@ -0,0 +1,10 @@ +#pragma once +// 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. + +#define msierrSQLFailedCreateDatabase 26201 +#define msierrSQLFailedDropDatabase 26202 +#define msierrSQLFailedConnectDatabase 26203 +#define msierrSQLFailedExecString 26204 +#define msierrSQLDatabaseAlreadyExists 26205 + +//Last available is 26250 \ No newline at end of file diff --git a/src/ca/precomp.h b/src/ca/precomp.h index 3edad7ed..7a5074b3 100644 --- a/src/ca/precomp.h +++ b/src/ca/precomp.h @@ -2,12 +2,26 @@ // 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. +#if _WIN32_MSI < 150 +#define _WIN32_MSI 150 +#endif + #include #include +#include + #define MAXUINT USHRT_MAX #include #include "wcautil.h" #include "fileutil.h" +#include "memutil.h" #include "strutil.h" +#include "wiutil.h" + +#include "CustomMsiErrors.h" + +#include "sca.h" +#include "scacost.h" +#include "scasqlstr.h" diff --git a/src/ca/sca.h b/src/ca/sca.h new file mode 100644 index 00000000..bc36344e --- /dev/null +++ b/src/ca/sca.h @@ -0,0 +1,33 @@ +#pragma once +// 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. + +// Generic action enum. +enum SCA_ACTION +{ + SCA_ACTION_NONE, + SCA_ACTION_INSTALL, + SCA_ACTION_UNINSTALL +}; + +// sql database attributes definitions +enum SCADB_ATTRIBUTES +{ + SCADB_CREATE_ON_INSTALL = 0x00000001, + SCADB_DROP_ON_UNINSTALL = 0x00000002, + SCADB_CONTINUE_ON_ERROR = 0x00000004, + SCADB_DROP_ON_INSTALL = 0x00000008, + SCADB_CREATE_ON_UNINSTALL = 0x00000010, + SCADB_CONFIRM_OVERWRITE = 0x00000020, + SCADB_CREATE_ON_REINSTALL = 0x00000040, + SCADB_DROP_ON_REINSTALL = 0x00000080, +}; + +// sql string/script attributes definitions +enum SCASQL_ATTRIBUTES +{ + SCASQL_EXECUTE_ON_INSTALL = 0x00000001, + SCASQL_EXECUTE_ON_UNINSTALL = 0x00000002, + SCASQL_CONTINUE_ON_ERROR = 0x00000004, + SCASQL_ROLLBACK = 0x00000008, + SCASQL_EXECUTE_ON_REINSTALL = 0x00000010, +}; diff --git a/src/ca/scacost.h b/src/ca/scacost.h new file mode 100644 index 00000000..6ea7e465 --- /dev/null +++ b/src/ca/scacost.h @@ -0,0 +1,7 @@ +#pragma once +// 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. + +const UINT COST_SQL_CREATEDB = 10000; +const UINT COST_SQL_DROPDB = 5000; +const UINT COST_SQL_CONNECTDB = 5000; +const UINT COST_SQL_STRING = 5000; diff --git a/src/ca/scadb.cpp b/src/ca/scadb.cpp new file mode 100644 index 00000000..9f9efca2 --- /dev/null +++ b/src/ca/scadb.cpp @@ -0,0 +1,587 @@ +// 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 `SqlDatabase`"; +enum eSqlDatabaseQuery { sdqSqlDb = 1, sdqServer, sdqInstance, sdqDatabase, + sdqComponent, sdqUser, sdqDbFileSpec, sdqLogFileSpec, sdqAttributes }; + +LPCWSTR vcsSqlFileSpecQuery = L"SELECT `FileSpec`, `Name`, `Filename`, `Size`, " + L"`MaxSize`, `GrowthSize` FROM `SqlFileSpec` 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"SqlDatabase")) + { + WcaLog(LOGMSG_VERBOSE, "Skipping ScaCreateDatabase() - SqlDatabase table not present"); + ExitFunction1(hr = S_FALSE); + } + + if (S_OK == WcaTableExists(L"SqlFileSpec")) + { + hr = WcaOpenView(vcsSqlFileSpecQuery, &hViewFileSpec); + ExitOnFailure(hr, "failed to open view on SqlFileSpec table"); + } + + // loop through all the sql databases + hr = WcaOpenExecuteView(vcsSqlDatabaseQuery, &hView); + ExitOnFailure(hr, "Failed to open view on SqlDatabase 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 SqlDatabase.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 SqlDatabase.SqlDbL: %ls", pwzId); + + hr = ::StringCchCopyW(psd->wzComponent, countof(psd->wzComponent), pwzComponent); + ExitOnFailure(hr, "Failed to copy SqlDatabase.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 SqlDatabase.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 SqlDatabase 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(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(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(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 SqlFileSpec 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 SqlFileSpec.Name for filespec: %ls", wzKey); + hr = ::StringCchCopyW(psf->wzName, countof(psf->wzName), pwzData); + ExitOnFailure(hr, "Failed to copy SqlFileSpec.Name string: %ls", pwzData); + + hr = WcaGetRecordFormattedString(hRec, sfsqFilename, &pwzData); + ExitOnFailure(hr, "Failed to get SqlFileSpec.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 SqlFileSpec.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 SqlFileSpec.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 SqlFileSpec.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; +} diff --git a/src/ca/scadb.h b/src/ca/scadb.h new file mode 100644 index 00000000..885e84c2 --- /dev/null +++ b/src/ca/scadb.h @@ -0,0 +1,55 @@ +#pragma once +// 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 "scauser.h" +#include "sqlutil.h" + +struct SCA_DB +{ + // darwin information + WCHAR wzKey[MAX_DARWIN_KEY + 1]; + BOOL fHasComponent; + WCHAR wzComponent[MAX_DARWIN_KEY + 1]; + INSTALLSTATE isInstalled, isAction; + + WCHAR wzServer[MAX_DARWIN_COLUMN + 1]; + WCHAR wzInstance[MAX_DARWIN_COLUMN + 1]; + WCHAR wzDatabase[MAX_DARWIN_COLUMN + 1]; + + int iAttributes; + + BOOL fUseIntegratedAuth; + SCA_USER scau; + + BOOL fHasDbSpec; + SQL_FILESPEC sfDb; + BOOL fHasLogSpec; + SQL_FILESPEC sfLog; + + SCA_DB* psdNext; +}; + + +// prototypes +HRESULT ScaDbsRead( + __inout SCA_DB** ppsdList, + __in SCA_ACTION saAction + ); + +SCA_DB* ScaDbsFindDatabase( + __in LPCWSTR wzSqlDb, + __in SCA_DB* psdList + ); + +HRESULT ScaDbsInstall( + __in SCA_DB* psdList + ); + +HRESULT ScaDbsUninstall( + __in SCA_DB* psdList + ); + +void ScaDbsFreeList( + __in SCA_DB* psdList + ); diff --git a/src/ca/scaexec.cpp b/src/ca/scaexec.cpp new file mode 100644 index 00000000..7a30f52a --- /dev/null +++ b/src/ca/scaexec.cpp @@ -0,0 +1,393 @@ +// 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" + + +/******************************************************************** + * CreateDatabase - CUSTOM ACTION ENTRY POINT for creating databases + * + * Input: deferred CustomActionData - DbKey\tServer\tInstance\tDatabase\tAttributes\tIntegratedAuth\tUser\tPassword + * ****************************************************************/ +extern "C" UINT __stdcall CreateDatabase(MSIHANDLE hInstall) +{ +//AssertSz(FALSE, "debug CreateDatabase here"); + UINT er = ERROR_SUCCESS; + HRESULT hr = S_OK; + + LPWSTR pwzData = NULL; + IDBCreateSession* pidbSession = NULL; + BSTR bstrErrorDescription = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzDatabaseKey = NULL; + LPWSTR pwzServer = NULL; + LPWSTR pwzInstance = NULL; + LPWSTR pwzDatabase = NULL; + LPWSTR pwzTemp = NULL; + int iAttributes; + BOOL fIntegratedAuth; + LPWSTR pwzUser = NULL; + LPWSTR pwzPassword = NULL; + BOOL fHaveDbFileSpec = FALSE; + SQL_FILESPEC sfDb; + BOOL fHaveLogFileSpec = FALSE; + SQL_FILESPEC sfLog; + BOOL fInitializedCom = FALSE; + + memset(&sfDb, 0, sizeof(sfDb)); + memset(&sfLog, 0, sizeof(sfLog)); + + hr = WcaInitialize(hInstall, "CreateDatabase"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to intialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzDatabaseKey); // SQL Server + ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz); + hr = WcaReadStringFromCaData(&pwz, &pwzServer); // SQL Server + ExitOnFailure(hr, "failed to read server from custom action data: %ls", pwz); + hr = WcaReadStringFromCaData(&pwz, &pwzInstance); // SQL Server Instance + ExitOnFailure(hr, "failed to read server instance from custom action data: %ls", pwz); + hr = WcaReadStringFromCaData(&pwz, &pwzDatabase); // SQL Database + ExitOnFailure(hr, "failed to read server instance from custom action data: %ls", pwz); + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "failed to read attributes from custom action data: %ls", pwz); + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&fIntegratedAuth)); // Integrated Windows Authentication? + ExitOnFailure(hr, "failed to read integrated auth flag from custom action data: %ls", pwz); + hr = WcaReadStringFromCaData(&pwz, &pwzUser); // SQL User + ExitOnFailure(hr, "failed to read user from custom action data: %ls", pwz); + hr = WcaReadStringFromCaData(&pwz, &pwzPassword); // SQL User Password + ExitOnFailure(hr, "failed to read user from custom action data: %ls", pwz); + + // db file spec + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&fHaveDbFileSpec)); + ExitOnFailure(hr, "failed to read db file spec from custom action data: %ls", pwz); + + if (fHaveDbFileSpec) + { + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read db file spec name from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfDb.wzName, countof(sfDb.wzName), pwzTemp); + ExitOnFailure(hr, "failed to copy db file spec name: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read db file spec filename from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfDb.wzFilename, countof(sfDb.wzFilename), pwzTemp); + ExitOnFailure(hr, "failed to copy db file spec filename: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read db file spec size from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfDb.wzSize, countof(sfDb.wzSize), pwzTemp); + ExitOnFailure(hr, "failed to copy db file spec size value: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read db file spec max size from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfDb.wzMaxSize, countof(sfDb.wzMaxSize), pwzTemp); + ExitOnFailure(hr, "failed to copy db file spec max size: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read db file spec grow from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfDb.wzGrow, countof(sfDb.wzGrow), pwzTemp); + ExitOnFailure(hr, "failed to copy db file spec grow value: %ls", pwzTemp); + } + + // log file spec + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&fHaveLogFileSpec)); + ExitOnFailure(hr, "failed to read log file spec from custom action data: %ls", pwz); + if (fHaveLogFileSpec) + { + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read log file spec name from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfLog.wzName, countof(sfDb.wzName), pwzTemp); + ExitOnFailure(hr, "failed to copy log file spec name: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read log file spec filename from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfLog.wzFilename, countof(sfDb.wzFilename), pwzTemp); + ExitOnFailure(hr, "failed to copy log file spec filename: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read log file spec size from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfLog.wzSize, countof(sfDb.wzSize), pwzTemp); + ExitOnFailure(hr, "failed to copy log file spec size value: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read log file spec max size from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfLog.wzMaxSize, countof(sfDb.wzMaxSize), pwzTemp); + ExitOnFailure(hr, "failed to copy log file spec max size: %ls", pwzTemp); + + hr = WcaReadStringFromCaData(&pwz, &pwzTemp); + ExitOnFailure(hr, "failed to read log file spec grow from custom action data: %ls", pwz); + hr = ::StringCchCopyW(sfLog.wzGrow, countof(sfDb.wzGrow), pwzTemp); + ExitOnFailure(hr, "failed to copy log file spec grow value: %ls", pwzTemp); + } + + if (iAttributes & SCADB_CONFIRM_OVERWRITE) + { + // Check if the database already exists + hr = SqlDatabaseExists(pwzServer, pwzInstance, pwzDatabase, fIntegratedAuth, pwzUser, pwzPassword, &bstrErrorDescription); + MessageExitOnFailure(hr, msierrSQLFailedCreateDatabase, "failed to check if database exists: '%ls', error: %ls", pwzDatabase, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription); + + if (S_OK == hr) // found an existing database, confirm that they don't want to stop before it gets trampled, in no UI case just continue anyways + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + if (IDNO == WcaErrorMessage(msierrSQLDatabaseAlreadyExists, hr, MB_YESNO, 1, pwzDatabase)) + ExitOnFailure(hr, "failed to initialize"); + } + } + + hr = SqlDatabaseEnsureExists(pwzServer, pwzInstance, pwzDatabase, fIntegratedAuth, pwzUser, pwzPassword, fHaveDbFileSpec ? &sfDb : NULL, fHaveLogFileSpec ? &sfLog : NULL, &bstrErrorDescription); + if ((iAttributes & SCADB_CONTINUE_ON_ERROR) && FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "Error 0x%x: failed to create SQL database but continuing, error: %ls, Database: %ls", hr, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription, pwzDatabase); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrSQLFailedCreateDatabase, "failed to create to database: '%ls', error: %ls", pwzDatabase, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription); + + hr = WcaProgressMessage(COST_SQL_CONNECTDB, FALSE); +LExit: + ReleaseStr(pwzDatabaseKey); + ReleaseStr(pwzServer); + ReleaseStr(pwzInstance); + ReleaseStr(pwzDatabase); + ReleaseStr(pwzUser); + ReleaseStr(pwzPassword); + ReleaseObject(pidbSession); + ReleaseBSTR(bstrErrorDescription); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} + + +/******************************************************************** + DropDatabase - CUSTOM ACTION ENTRY POINT for removing databases + + Input: deferred CustomActionData - DbKey\tServer\tInstance\tDatabase\tAttributes\tIntegratedAuth\tUser\tPassword + * ****************************************************************/ +extern "C" UINT __stdcall DropDatabase(MSIHANDLE hInstall) +{ +//Assert(FALSE); + UINT er = ERROR_SUCCESS; + HRESULT hr = S_OK; + + LPWSTR pwzData = NULL; + IDBCreateSession* pidbSession = NULL; + BSTR bstrErrorDescription = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzDatabaseKey = NULL; + LPWSTR pwzServer = NULL; + LPWSTR pwzInstance = NULL; + LPWSTR pwzDatabase = NULL; + long lAttributes; + BOOL fIntegratedAuth; + LPWSTR pwzUser = NULL; + LPWSTR pwzPassword = NULL; + BOOL fInitializedCom = TRUE; + + hr = WcaInitialize(hInstall, "DropDatabase"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to intialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzDatabaseKey); + ExitOnFailure(hr, "failed to read database key"); + hr = WcaReadStringFromCaData(&pwz, &pwzServer); + ExitOnFailure(hr, "failed to read server"); + hr = WcaReadStringFromCaData(&pwz, &pwzInstance); + ExitOnFailure(hr, "failed to read instance"); + hr = WcaReadStringFromCaData(&pwz, &pwzDatabase); + ExitOnFailure(hr, "failed to read database"); + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&lAttributes)); + ExitOnFailure(hr, "failed to read attributes"); + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&fIntegratedAuth)); // Integrated Windows Authentication? + ExitOnFailure(hr, "failed to read integrated auth flag"); + hr = WcaReadStringFromCaData(&pwz, &pwzUser); + ExitOnFailure(hr, "failed to read user"); + hr = WcaReadStringFromCaData(&pwz, &pwzPassword); + ExitOnFailure(hr, "failed to read password"); + + hr = SqlDropDatabase(pwzServer, pwzInstance, pwzDatabase, fIntegratedAuth, pwzUser, pwzPassword, &bstrErrorDescription); + if ((lAttributes & SCADB_CONTINUE_ON_ERROR) && FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "Error 0x%x: failed to drop SQL database but continuing, error: %ls, Database: %ls", hr, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription, pwzDatabase); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrSQLFailedDropDatabase, "failed to drop to database: '%ls', error: %ls", pwzDatabase, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription); + + hr = WcaProgressMessage(COST_SQL_CONNECTDB, FALSE); + +LExit: + ReleaseStr(pwzDatabaseKey); + ReleaseStr(pwzServer); + ReleaseStr(pwzInstance); + ReleaseStr(pwzDatabase); + ReleaseStr(pwzUser); + ReleaseStr(pwzPassword); + ReleaseStr(pwzData); + ReleaseObject(pidbSession); + ReleaseBSTR(bstrErrorDescription); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} + + +/******************************************************************** + ExecuteSqlStrings - CUSTOM ACTION ENTRY POINT for running SQL strings + + Input: deferred CustomActionData - DbKey\tServer\tInstance\tDatabase\tAttributes\tIntegratedAuth\tUser\tPassword\tSQLKey1\tSQLString1\tSQLKey2\tSQLString2\tSQLKey3\tSQLString3\t... + rollback CustomActionData - same as above + * ****************************************************************/ +extern "C" UINT __stdcall ExecuteSqlStrings(MSIHANDLE hInstall) +{ +//Assert(FALSE); + UINT er = ERROR_SUCCESS; + HRESULT hr = S_OK; + HRESULT hrDB = S_OK; + + LPWSTR pwzData = NULL; + IDBCreateSession* pidbSession = NULL; + BSTR bstrErrorDescription = NULL; + + LPWSTR pwz = NULL; + LPWSTR pwzDatabaseKey = NULL; + LPWSTR pwzServer = NULL; + LPWSTR pwzInstance = NULL; + LPWSTR pwzDatabase = NULL; + int iAttributesDB; + int iAttributesSQL; + BOOL fIntegratedAuth; + LPWSTR pwzUser = NULL; + LPWSTR pwzPassword = NULL; + LPWSTR pwzSqlKey = NULL; + LPWSTR pwzSql = NULL; + BOOL fInitializedCom = FALSE; + + hr = WcaInitialize(hInstall, "ExecuteSqlStrings"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to intialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzDatabaseKey); + ExitOnFailure(hr, "failed to read database key"); + hr = WcaReadStringFromCaData(&pwz, &pwzServer); + ExitOnFailure(hr, "failed to read server"); + hr = WcaReadStringFromCaData(&pwz, &pwzInstance); + ExitOnFailure(hr, "failed to read instance"); + hr = WcaReadStringFromCaData(&pwz, &pwzDatabase); + ExitOnFailure(hr, "failed to read database"); + hr = WcaReadIntegerFromCaData(&pwz, &iAttributesDB); + ExitOnFailure(hr, "failed to read attributes"); + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast(&fIntegratedAuth)); // Integrated Windows Authentication? + ExitOnFailure(hr, "failed to read integrated auth flag"); + hr = WcaReadStringFromCaData(&pwz, &pwzUser); + ExitOnFailure(hr, "failed to read user"); + hr = WcaReadStringFromCaData(&pwz, &pwzPassword); + ExitOnFailure(hr, "failed to read password"); + + // Store off the result of the connect, only exit if we don't care if the database connection succeeds + // Wait to fail until later to see if we actually have work to do that is not set to continue on error + hrDB = SqlConnectDatabase(pwzServer, pwzInstance, pwzDatabase, fIntegratedAuth, pwzUser, pwzPassword, &pidbSession); + if ((iAttributesDB & SCADB_CONTINUE_ON_ERROR) && FAILED(hrDB)) + { + WcaLog(LOGMSG_STANDARD, "Error 0x%x: continuing after failure to connect to database: %ls", hrDB, pwzDatabase); + ExitFunction1(hr = S_OK); + } + + while (S_OK == hr && S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzSqlKey))) + { + hr = WcaReadIntegerFromCaData(&pwz, &iAttributesSQL); + ExitOnFailure(hr, "failed to read attributes for SQL string: %ls", pwzSqlKey); + + hr = WcaReadStringFromCaData(&pwz, &pwzSql); + ExitOnFailure(hr, "failed to read SQL string for key: %ls", pwzSqlKey); + + // If the SqlString row is set to continue on error and the DB connection failed, skip attempting to execute + if ((iAttributesSQL & SCASQL_CONTINUE_ON_ERROR) && FAILED(hrDB)) + { + WcaLog(LOGMSG_STANDARD, "Error 0x%x: continuing after failure to connect to database: %ls", hrDB, pwzDatabase); + continue; + } + + // Now check if the DB connection succeeded + MessageExitOnFailure(hr = hrDB, msierrSQLFailedConnectDatabase, "failed to connect to database: '%ls'", pwzDatabase); + + WcaLog(LOGMSG_VERBOSE, "Executing SQL string: %ls", pwzSql); + hr = SqlSessionExecuteQuery(pidbSession, pwzSql, NULL, NULL, &bstrErrorDescription); + if ((iAttributesSQL & SCASQL_CONTINUE_ON_ERROR) && FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "Error 0x%x: failed to execute SQL string but continuing, error: %ls, SQL key: %ls SQL string: %ls", hr, NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription, pwzSqlKey, pwzSql); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrSQLFailedExecString, "failed to execute SQL string, error: %ls, SQL key: %ls SQL string: %ls", NULL == bstrErrorDescription ? L"unknown error" : bstrErrorDescription, pwzSqlKey, pwzSql); + + WcaProgressMessage(COST_SQL_STRING, FALSE); + } + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + +LExit: + ReleaseStr(pwzDatabaseKey); + ReleaseStr(pwzServer); + ReleaseStr(pwzInstance); + ReleaseStr(pwzDatabase); + ReleaseStr(pwzUser); + ReleaseStr(pwzPassword); + ReleaseStr(pwzData); + + ReleaseBSTR(bstrErrorDescription); + ReleaseObject(pidbSession); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} diff --git a/src/ca/scasql.cpp b/src/ca/scasql.cpp new file mode 100644 index 00000000..5e3edd1c --- /dev/null +++ b/src/ca/scasql.cpp @@ -0,0 +1,113 @@ +// 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" + +// prototypes +static HRESULT ConfigureSqlData( + __in SCA_ACTION saAction + ); + + +/******************************************************************** +InstallSqlData - CUSTOM ACTION ENTRY POINT for installing + SQL data + +********************************************************************/ +extern "C" UINT __stdcall InstallSqlData( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + // initialize + hr = WcaInitialize(hInstall, "InstallSqlData"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = ConfigureSqlData(SCA_ACTION_INSTALL); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/******************************************************************** +UninstallSqlData - CUSTOM ACTION ENTRY POINT for uninstalling + SQL data + +********************************************************************/ +extern "C" UINT __stdcall UninstallSqlData( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + // initialize + hr = WcaInitialize(hInstall, "UninstallCertificates"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = ConfigureSqlData(SCA_ACTION_UNINSTALL); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +static HRESULT ConfigureSqlData( + __in SCA_ACTION saAction + ) +{ + //AssertSz(FALSE, "debug ConfigureSqlData()"); + HRESULT hr = S_OK; + + SCA_DB* psdList = NULL; + SCA_SQLSTR* psssList = NULL; + + // check for the prerequsite tables + if (S_OK != WcaTableExists(L"SqlDatabase")) + { + WcaLog(LOGMSG_VERBOSE, "skipping SQL CustomAction, no SqlDatabase table"); + ExitFunction1(hr = S_FALSE); + } + + // read tables + hr = ScaDbsRead(&psdList, saAction); + ExitOnFailure(hr, "failed to read SqlDatabase table"); + + hr = ScaSqlStrsRead(&psssList, saAction); + ExitOnFailure(hr, "failed to read SqlStrings table"); + + hr = ScaSqlStrsReadScripts(&psssList, saAction); + ExitOnFailure(hr, "failed to read SqlScripts table"); + + if (SCA_ACTION_UNINSTALL == saAction) + { + // do uninstall actions (order is important!) + hr = ScaSqlStrsUninstall(psdList, psssList); + ExitOnFailure(hr, "failed to execute uninstall SQL strings"); + + hr = ScaDbsUninstall(psdList); + ExitOnFailure(hr, "failed to uninstall databases"); + } + else + { + // do install actions (order is important!) + hr = ScaDbsInstall(psdList); + ExitOnFailure(hr, "failed to install databases"); + + hr = ScaSqlStrsInstall(psdList, psssList); + ExitOnFailure(hr, "failed to execute install SQL strings, length may be too long, try add GO to break up"); + } + +LExit: + if (psssList) + ScaSqlStrsFreeList(psssList); + + if (psdList) + ScaDbsFreeList(psdList); + + return hr; +} diff --git a/src/ca/scasqlstr.cpp b/src/ca/scasqlstr.cpp new file mode 100644 index 00000000..3108e307 --- /dev/null +++ b/src/ca/scasqlstr.cpp @@ -0,0 +1,728 @@ +// 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 vcsSqlStringQuery = L"SELECT `String`, `SqlDb_`, `Component_`,`SQL`,`User_`,`Attributes`,`Sequence` " +L"FROM `SqlString` ORDER BY `SqlDb_`,`Sequence`"; +enum eSqlStringQuery { ssqSqlString = 1, ssqSqlDb, ssqComponent, ssqSQL, ssqUser, ssqAttributes, ssqSequence }; + +LPCWSTR vcsSqlScriptQuery = L"SELECT `ScriptBinary_`,`Script`, `SqlDb_`, `Component_`,`User_`,`Attributes`,`Sequence` " +L"FROM `SqlScript` ORDER BY `SqlDb_`,`Sequence`"; +enum eSqlScriptQuery { sscrqScriptBinary=1, sscrqSqlScript, sscrqSqlDb, sscrqComponent, sscrqUser, sscrqAttributes, sscrqSequence }; + +LPCWSTR vcsSqlBinaryScriptQuery = L"SELECT `Data` FROM `Binary` WHERE `Name`=?"; +enum eSqlBinaryScriptQuery { ssbsqData = 1 }; + + +// prototypes for private helper functions +static HRESULT NewSqlStr( + __out SCA_SQLSTR** ppsss + ); +static SCA_SQLSTR* AddSqlStrToList( + __in SCA_SQLSTR* psssList, + __in SCA_SQLSTR* psss + ); +static HRESULT ExecuteStrings( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList, + __in BOOL fInstall + ); + +HRESULT ScaSqlStrsRead( + __inout SCA_SQLSTR** ppsssList, + __in SCA_ACTION saAction + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + PMSIHANDLE hView, hRec; + PMSIHANDLE hViewUser, hRecUser; + + LPWSTR pwzComponent = NULL; + LPWSTR pwzData = NULL; + + SCA_SQLSTR* psss = NULL; + + if (S_OK != WcaTableExists(L"SqlString") || S_OK != WcaTableExists(L"SqlDatabase")) + { + WcaLog(LOGMSG_VERBOSE, "Skipping ScaSqlStrsRead() - SqlString and/or SqlDatabase table not present"); + ExitFunction1(hr = S_FALSE); + } + + // loop through all the sql strings + hr = WcaOpenExecuteView(vcsSqlStringQuery, &hView); + ExitOnFailure(hr, "Failed to open view on SqlString table"); + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) + { + INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; + INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; + + hr = WcaGetRecordString(hRec, ssqComponent, &pwzComponent); + ExitOnFailure(hr, "Failed to get Component for SQL String."); + + 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 = NewSqlStr(&psss); + ExitOnFailure(hr, "failed to allocation new sql string element"); + + psss->isInstalled = isInstalled; + psss->isAction = isAction; + + hr = WcaGetRecordString(hRec, ssqSqlString, &pwzData); + ExitOnFailure(hr, "Failed to get SqlString.String"); + hr = ::StringCchCopyW(psss->wzKey, countof(psss->wzKey), pwzData); + ExitOnFailure(hr, "Failed to copy SqlString.String: %ls", pwzData); + + // find the database information for this string + hr = WcaGetRecordString(hRec, ssqSqlDb, &pwzData); + ExitOnFailure(hr, "Failed to get SqlString.SqlDb_ for SqlString '%ls'", psss->wzKey); + hr = ::StringCchCopyW(psss->wzSqlDb, countof(psss->wzSqlDb), pwzData); + ExitOnFailure(hr, "Failed to copy SqlString.SqlDb_: %ls", pwzData); + + hr = WcaGetRecordInteger(hRec, ssqAttributes, &psss->iAttributes); + ExitOnFailure(hr, "Failed to get SqlString.Attributes for SqlString '%ls'", psss->wzKey); + + //get the sequence number for the string (note that this will be sequenced with scripts too) + hr = WcaGetRecordInteger(hRec, ssqSequence, &psss->iSequence); + ExitOnFailure(hr, "Failed to get SqlString.Sequence for SqlString '%ls'", psss->wzKey); + + // execute SQL + hr = WcaGetRecordFormattedString(hRec, ssqSQL, &pwzData); + ExitOnFailure(hr, "Failed to get SqlString.SQL for SqlString '%ls'", psss->wzKey); + + Assert(!psss->pwzSql); + hr = StrAllocString(&psss->pwzSql, pwzData, 0); + ExitOnFailure(hr, "Failed to alloc string for SqlString '%ls'", psss->wzKey); + + *ppsssList = AddSqlStrToList(*ppsssList, psss); + psss = NULL; // set the sss to NULL so it doesn't get freed below + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failure occured while reading SqlString table"); + +LExit: + // if anything was left over after an error clean it all up + if (psss) + { + ScaSqlStrsFreeList(psss); + } + + ReleaseStr(pwzData); + ReleaseStr(pwzComponent); + + return hr; +} + + +HRESULT ScaSqlStrsReadScripts( + __inout SCA_SQLSTR** ppsssList, + __in SCA_ACTION saAction + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + PMSIHANDLE hView, hRec; + PMSIHANDLE hViewBinary, hRecBinary; + PMSIHANDLE hViewUser, hRecUser; + + LPWSTR pwzComponent = NULL; + LPWSTR pwzData = NULL; + + BYTE* pbScript = NULL; + DWORD cbRead = 0; + DWORD cbScript = 0; + DWORD cchScript = 0; + + LPWSTR pwzScriptBuffer = NULL; + WCHAR* pwzScript = NULL; + WCHAR* pwz; + DWORD cch = 0; + + SCA_SQLSTR sss; + SCA_SQLSTR* psss = NULL; + + if (S_OK != WcaTableExists(L"SqlScript") || S_OK != WcaTableExists(L"SqlDatabase") || S_OK != WcaTableExists(L"Binary")) + { + WcaLog(LOGMSG_VERBOSE, "Skipping ScaSqlStrsReadScripts() - SqlScripts and/or SqlDatabase table not present"); + ExitFunction1(hr = S_FALSE); + } + + // open a view on the binary table + hr = WcaOpenView(vcsSqlBinaryScriptQuery, &hViewBinary); + ExitOnFailure(hr, "Failed to open view on Binary table for SqlScripts"); + + // loop through all the sql scripts + hr = WcaOpenExecuteView(vcsSqlScriptQuery, &hView); + ExitOnFailure(hr, "Failed to open view on SqlScript table"); + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) + { + INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; + INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; + + hr = WcaGetRecordString(hRec, sscrqComponent, &pwzComponent); + ExitOnFailure(hr, "Failed to get Component for SQL Script."); + + 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; + } + + ::ZeroMemory(&sss, sizeof(sss)); + + sss.isInstalled = isInstalled; + sss.isAction = isAction; + + hr = WcaGetRecordString(hRec, sscrqSqlScript, &pwzData); + ExitOnFailure(hr, "Failed to get SqlScript.Script"); + hr = ::StringCchCopyW(sss.wzKey, countof(sss.wzKey), pwzData); + ExitOnFailure(hr, "Failed to copy SqlScript.Script: %ls", pwzData); + + // find the database information for this string + hr = WcaGetRecordString(hRec, sscrqSqlDb, &pwzData); + ExitOnFailure(hr, "Failed to get SqlScript.SqlDb_ for SqlScript '%ls'", sss.wzKey); + hr = ::StringCchCopyW(sss.wzSqlDb, countof(sss.wzSqlDb), pwzData); + ExitOnFailure(hr, "Failed to copy SqlScritp.SqlDbb: %ls", pwzData); + + hr = WcaGetRecordInteger(hRec, sscrqAttributes, &sss.iAttributes); + ExitOnFailure(hr, "Failed to get SqlScript.Attributes for SqlScript '%ls'", sss.wzKey); + + hr = WcaGetRecordInteger(hRec, sscrqSequence, &sss.iSequence); + ExitOnFailure(hr, "Failed to get SqlScript.Sequence for SqlScript '%ls'", sss.wzKey); + + // get the sql script out of the binary stream + hr = WcaExecuteView(hViewBinary, hRec); + ExitOnFailure(hr, "Failed to open SqlScript.BinaryScript_ for SqlScript '%ls'", sss.wzKey); + hr = WcaFetchSingleRecord(hViewBinary, &hRecBinary); + ExitOnFailure(hr, "Failed to fetch SqlScript.BinaryScript_ for SqlScript '%ls'", sss.wzKey); + + // Note: We need to allocate an extra character on the stream to NULL terminate the SQL script. + // The WcaGetRecordStream() function won't let us add extra space on the end of the stream + // so we'll read the stream "the old fashioned way". + //hr = WcaGetRecordStream(hRecBinary, ssbsqData, (BYTE**)&pbScript, &cbScript); + //ExitOnFailure(hr, "Failed to read SqlScript.BinaryScript_ for SqlScript '%ls'", sss.wzKey); + er = ::MsiRecordReadStream(hRecBinary, ssbsqData, NULL, &cbRead); + hr = HRESULT_FROM_WIN32(er); + ExitOnFailure(hr, "failed to get size of stream"); + + cbScript = cbRead + sizeof(WCHAR); // we may have an ANSI SQL script but leave enough to even NULL terminate a WCHAR string + hr = WcaAllocStream(&pbScript, cbScript); // this will allocate a fully zeroed out buffer so our string will be NULL terminated + ExitOnFailure(hr, "failed to allocate data for stream"); + + er = ::MsiRecordReadStream(hRecBinary, ssbsqData, reinterpret_cast(pbScript), &cbRead); //read the buffer but leave the space for the NULL terminator + hr = HRESULT_FROM_WIN32(er); + ExitOnFailure(hr, "failed to read from stream"); + + Assert(cbRead + sizeof(WCHAR) == cbScript); + + // Check for the UNICODE BOM file marker. + if ((0xFF == *pbScript) && (0xFE == *(pbScript + 1))) + { + // Copy the UNICODE string after the BOM marker (subtract one because we'll skip the BOM marker). + cchScript = (cbScript / sizeof(WCHAR)) - 1; + + hr = StrAllocString(&pwzScriptBuffer, reinterpret_cast(pbScript) + 1, 0); + ExitOnFailure(hr, "Failed to allocate WCHAR string of size '%d'", cchScript); + } + else + { + // We have an ANSI string so convert it to UNICODE. + cchScript = cbScript; + + hr = StrAllocStringAnsi(&pwzScriptBuffer, reinterpret_cast(pbScript), 0, CP_ACP); + ExitOnFailure(hr, "Failed to allocate WCHAR string of size '%d'", cchScript); + } + + // Free the byte buffer since it has been converted to a new UNICODE string, one way or another. + if (pbScript) + { + WcaFreeStream(pbScript); + pbScript = NULL; + } + + // Process the SQL script stripping out unnecessary stuff (like comments) and looking for "GO" statements. + pwzScript = pwzScriptBuffer; + while (cchScript && pwzScript && *pwzScript) + { + // strip off leading whitespace + while (cchScript && *pwzScript && iswspace(*pwzScript)) + { + ++pwzScript; + --cchScript; + } + + Assert(0 <= cchScript); + + // if there is a SQL comment remove it + while (cchScript && L'/' == *pwzScript && L'*' == *(pwzScript + 1)) + { + // go until end of comment + while (cchScript && *pwzScript && *(pwzScript + 1) && !(L'*' == *pwzScript && L'/' == *(pwzScript + 1))) + { + ++pwzScript; + --cchScript; + } + + Assert(2 <= cchScript); + + if (2 <= cchScript) + { + // to account for */ at end + pwzScript+=2; + cchScript-=2; + } + + Assert(0 <= cchScript); + + // strip off any new leading whitespace + while (cchScript && *pwzScript && iswspace(*pwzScript)) + { + ++pwzScript; + --cchScript; + } + } + + while (cchScript && L'-' == *pwzScript && L'-' == *(pwzScript + 1)) + { + // go past the new line character + while (cchScript && *pwzScript && L'\n' != *pwzScript) + { + ++pwzScript; + --cchScript; + } + + Assert(0 <= cchScript); + + if (cchScript && L'\n' == *pwzScript) + { + ++pwzScript; + --cchScript; + } + + Assert(0 <= cchScript); + + // strip off any new leading whitespace + while (cchScript && *pwzScript && iswspace(*pwzScript)) + { + ++pwzScript; + --cchScript; + } + } + + Assert(0 <= cchScript); + + // try to isolate a "GO" keyword and count the characters in the SQL string + pwz = pwzScript; + cch = 0; + while (cchScript && *pwz) + { + //skip past comment lines that might have "go" in them + //note that these comments are "in the middle" of our function, + //or possibly at the end of a line + if (cchScript && L'-' == *pwz && L'-' == *(pwz + 1)) + { + // skip past chars until the new line character + while (cchScript && *pwz && (L'\n' != *pwz)) + { + ++pwz; + ++cch; + --cchScript; + } + } + + //skip past comment lines of form /* to */ that might have "go" in them + //note that these comments are "in the middle" of our function, + //or possibly at the end of a line + if (cchScript && L'/' == *pwz && L'*' == *(pwz + 1)) + { + // skip past chars until the new line character + while (cchScript && *pwz && *(pwz + 1) && !((L'*' == *pwz) && (L'/' == *(pwz + 1)))) + { + ++pwz; + ++cch; + --cchScript; + } + + if (2 <= cchScript) + { + // to account for */ at end + pwz+=2; + cch+=2; + cchScript-=2; + } + } + + // Skip past strings that may be part of the SQL statement that might have a "go" in them + if ( cchScript && L'\'' == *pwz ) + { + ++pwz; + ++cch; + --cchScript; + + // Skip past chars until the end of the string + while ( cchScript && *pwz && !(L'\'' == *pwz) ) + { + ++pwz; + ++cch; + --cchScript; + } + } + + // Skip past strings that may be part of the SQL statement that might have a "go" in them + if ( cchScript && L'\"' == *pwz ) + { + ++pwz; + ++cch; + --cchScript; + + // Skip past chars until the end of the string + while ( cchScript && *pwz && !(L'\"' == *pwz) ) + { + ++pwz; + ++cch; + --cchScript; + } + } + + // if "GO" is isolated + if ((pwzScript == pwz || iswspace(*(pwz - 1))) && + (L'G' == *pwz || L'g' == *pwz) && + (L'O' == *(pwz + 1) || L'o' == *(pwz + 1)) && + (0 == *(pwz + 2) || iswspace(*(pwz + 2)))) + { + *pwz = 0; // null terminate the SQL string on the "G" + pwz += 2; + cchScript -= 2; + break; // found "GO" now add SQL string to list + } + + ++pwz; + ++cch; + --cchScript; + } + + Assert(0 <= cchScript); + + if (0 < cch) //don't process if there's nothing to process + { + // replace tabs with spaces + for (LPWSTR pwzTab = wcsstr(pwzScript, L"\t"); pwzTab; pwzTab = wcsstr(pwzTab, L"\t")) + *pwzTab = ' '; + + // strip off whitespace at the end of the script string + for (LPWSTR pwzErase = pwzScript + cch - 1; pwzScript < pwzErase && iswspace(*pwzErase); pwzErase--) + { + *(pwzErase) = 0; + cch--; + } + } + + if (0 < cch) + { + hr = NewSqlStr(&psss); + ExitOnFailure(hr, "failed to allocate new sql string element"); + + // copy everything over + hr = ::StringCchCopyW(psss->wzKey, countof(psss->wzKey), sss.wzKey); + ExitOnFailure(hr, "Failed to copy key string to sqlstr object"); + hr = ::StringCchCopyW(psss->wzSqlDb, countof(psss->wzSqlDb), sss.wzSqlDb); + ExitOnFailure(hr, "Failed to copy DB string to sqlstr object"); + hr = ::StringCchCopyW(psss->wzComponent, countof(psss->wzComponent), sss.wzComponent); + ExitOnFailure(hr, "Failed to copy component string to sqlstr object"); + psss->isInstalled = sss.isInstalled; + psss->isAction = sss.isAction; + psss->iAttributes = sss.iAttributes; + psss->iSequence = sss.iSequence; + + // cchRequired includes the NULL terminating char + hr = StrAllocString(&psss->pwzSql, pwzScript, 0); + ExitOnFailure(hr, "Failed to allocate string for SQL script: '%ls'", psss->wzKey); + + *ppsssList = AddSqlStrToList(*ppsssList, psss); + psss = NULL; // set the db NULL so it doesn't accidentally get freed below + } + + pwzScript = pwz; + } + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failure occured while reading SqlString table"); + +LExit: + // if anything was left over after an error clean it all up + if (psss) + { + ScaSqlStrsFreeList(psss); + } + + if (pbScript) + { + WcaFreeStream(pbScript); + } + + ReleaseStr(pwzScriptBuffer); + ReleaseStr(pwzData); + ReleaseStr(pwzComponent); + + return hr; +} + + +HRESULT ScaSqlStrsInstall( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList + ) +{ + HRESULT hr = ExecuteStrings(psdList, psssList, TRUE); + + return hr; +} + + +HRESULT ScaSqlStrsUninstall( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList + ) +{ + HRESULT hr = ExecuteStrings(psdList, psssList, FALSE); + + return hr; +} + + +void ScaSqlStrsFreeList( + __in SCA_SQLSTR* psssList + ) +{ + SCA_SQLSTR* psssDelete = psssList; + while (psssList) + { + psssDelete = psssList; + psssList = psssList->psssNext; + + if (psssDelete->pwzSql) + { + ReleaseStr(psssDelete->pwzSql); + } + + MemFree(psssDelete); + } +} + + +// private helper functions + +static HRESULT NewSqlStr( + __out SCA_SQLSTR** ppsss + ) +{ + HRESULT hr = S_OK; + SCA_SQLSTR* psss = static_cast(MemAlloc(sizeof(SCA_SQLSTR), TRUE)); + ExitOnNull(psss, hr, E_OUTOFMEMORY, "failed to allocate memory for new sql string element"); + + *ppsss = psss; + +LExit: + return hr; +} + + +static SCA_SQLSTR* AddSqlStrToList( + __in SCA_SQLSTR* psssList, + __in SCA_SQLSTR* psss + ) +{ + Assert(psss); //just checking + + //make certain we have a valid sequence number; note that negatives are technically valid + if (MSI_NULL_INTEGER == psss->iSequence) + { + psss->iSequence = 0; + } + + if (psssList) + { + //list already exists, so insert psss into the list in Sequence order + + //see if we need to change the head, otherwise figure out where in the order it fits + if (psss->iSequence < psssList->iSequence) + { + psss->psssNext = psssList; + psssList = psss; + } + else + { + SCA_SQLSTR* psssT = psssList; + //note that if Sequence numbers are duplicated, as in the case of a sqlscript, + //we need to insert them "at the end" of the group so the sqlfile stays in order + while (psssT->psssNext && (psssT->psssNext->iSequence <= psss->iSequence)) + { + psssT = psssT->psssNext; + } + + //insert our new psss AFTER psssT + psss->psssNext = psssT->psssNext; + psssT->psssNext = psss; + } + } + else + { + psssList = psss; + } + + return psssList; +} + + +static HRESULT ExecuteStrings( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList, + __in BOOL fInstall + ) +{ + HRESULT hr = S_FALSE; // assume nothing will be done + + int iRollback = -1; + int iOldRollback = iRollback; + + LPCWSTR wzOldDb = NULL; + UINT uiCost = 0; + WCHAR* pwzCustomActionData = NULL; + WCHAR wzNumber[64]; + + // loop through all sql strings + for (SCA_SQLSTR* psss = psssList; psss; psss = psss->psssNext) + { + // if installing this component + if ((fInstall && (psss->iAttributes & SCASQL_EXECUTE_ON_INSTALL) && WcaIsInstalling(psss->isInstalled, psss->isAction) && !WcaIsReInstalling(psss->isInstalled, psss->isAction)) || + (fInstall && (psss->iAttributes & SCASQL_EXECUTE_ON_REINSTALL) && WcaIsReInstalling(psss->isInstalled, psss->isAction)) || + (!fInstall && (psss->iAttributes & SCASQL_EXECUTE_ON_UNINSTALL) && WcaIsUninstalling(psss->isInstalled, psss->isAction))) + { + // determine if this is a rollback scheduling or normal deferred scheduling + if (psss->iAttributes & SCASQL_ROLLBACK) + { + iRollback = 1; + } + else + { + iRollback = 0; + } + + // if we need to create a connection to a new server\database + if (!wzOldDb || 0 != lstrcmpW(wzOldDb, psss->wzSqlDb) || iOldRollback != iRollback) + { + const SCA_DB* psd = ScaDbsFindDatabase(psss->wzSqlDb, psdList); + if (!psd) + { + ExitOnFailure(hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND), "failed to find data for Database: %ls", psss->wzSqlDb); + } + + if (-1 == iOldRollback) + { + iOldRollback = iRollback; + } + Assert(0 == iOldRollback || 1 == iOldRollback); + + // if there was custom action data before, schedule the action to write it + if (pwzCustomActionData && *pwzCustomActionData) + { + Assert(pwzCustomActionData && *pwzCustomActionData && uiCost); + + hr = WcaDoDeferredAction(1 == iOldRollback ? L"RollbackExecuteSqlStrings" : L"ExecuteSqlStrings", pwzCustomActionData, uiCost); + ExitOnFailure(hr, "failed to schedule ExecuteSqlStrings action, rollback: %d", iOldRollback); + iOldRollback = iRollback; + + *pwzCustomActionData = L'\0'; + uiCost = 0; + } + + Assert(!pwzCustomActionData || (pwzCustomActionData && 0 == *pwzCustomActionData) && 0 == uiCost); + + hr = WcaWriteStringToCaData(psd->wzKey, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Server Database String to CustomActionData for Database String: %ls", psd->wzKey); + + hr = WcaWriteStringToCaData(psd->wzServer, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Server to CustomActionData for Database String: %ls", psd->wzKey); + + hr = WcaWriteStringToCaData(psd->wzInstance, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Instance to CustomActionData for Database String: %ls", psd->wzKey); + + hr = WcaWriteStringToCaData(psd->wzDatabase, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Database to CustomActionData for Database String: %ls", psd->wzKey); + + hr = ::StringCchPrintfW(wzNumber, countof(wzNumber), L"%d", psd->iAttributes); + ExitOnFailure(hr, "Failed to format attributes integer value to string"); + hr = WcaWriteStringToCaData(wzNumber, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Attributes to CustomActionData for Database String: %ls", psd->wzKey); + + hr = ::StringCchPrintfW(wzNumber, countof(wzNumber), L"%d", psd->fUseIntegratedAuth); + ExitOnFailure(hr, "Failed to format UseIntegratedAuth integer value to string"); + hr = WcaWriteStringToCaData(wzNumber, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL IntegratedAuth flag to CustomActionData for Database String: %ls", psd->wzKey); + + hr = WcaWriteStringToCaData(psd->scau.wzName, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL UserName to CustomActionData for Database String: %ls", psd->wzKey); + + hr = WcaWriteStringToCaData(psd->scau.wzPassword, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Password to CustomActionData for Database String: %ls", psd->wzKey); + + uiCost += COST_SQL_CONNECTDB; + + wzOldDb = psss->wzSqlDb; + } + + WcaLog(LOGMSG_VERBOSE, "Scheduling SQL string: %ls", psss->pwzSql); + + hr = WcaWriteStringToCaData(psss->wzKey, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to add SQL Key to CustomActionData for SQL string: %ls", psss->wzKey); + + hr = WcaWriteIntegerToCaData(psss->iAttributes, &pwzCustomActionData); + ExitOnFailure(hr, "failed to add attributes to CustomActionData for SQL string: %ls", psss->wzKey); + + hr = WcaWriteStringToCaData(psss->pwzSql, &pwzCustomActionData); + ExitOnFailure(hr, "Failed to to add SQL Query to CustomActionData for SQL string: %ls", psss->wzKey); + uiCost += COST_SQL_STRING; + } + } + + if (pwzCustomActionData && *pwzCustomActionData) + { + Assert(pwzCustomActionData && *pwzCustomActionData && uiCost); + hr = WcaDoDeferredAction(1 == iRollback ? L"RollbackExecuteSqlStrings" : L"ExecuteSqlStrings", pwzCustomActionData, uiCost); + ExitOnFailure(hr, "Failed to schedule ExecuteSqlStrings action"); + + *pwzCustomActionData = L'\0'; + uiCost = 0; + } + +LExit: + ReleaseStr(pwzCustomActionData); + + return hr; +} diff --git a/src/ca/scasqlstr.h b/src/ca/scasqlstr.h new file mode 100644 index 00000000..a6f6df1c --- /dev/null +++ b/src/ca/scasqlstr.h @@ -0,0 +1,51 @@ +#pragma once +// 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 "scadb.h" + +struct SCA_SQLSTR +{ + // darwin information + WCHAR wzKey[MAX_DARWIN_KEY + 1]; + WCHAR wzComponent[MAX_DARWIN_KEY + 1]; + INSTALLSTATE isInstalled, isAction; + + WCHAR wzSqlDb[MAX_DARWIN_COLUMN + 1]; + + BOOL fHasUser; + SCA_USER scau; + + LPWSTR pwzSql; + int iAttributes; + int iSequence; //used to sequence SqlString and SqlScript tables together + + SCA_SQLSTR* psssNext; +}; + + +// prototypes +HRESULT ScaSqlStrsRead( + __inout SCA_SQLSTR** ppsssList, + __in SCA_ACTION saAction + ); + +HRESULT ScaSqlStrsReadScripts( + __inout SCA_SQLSTR** ppsssList, + __in SCA_ACTION saAction + ); + +HRESULT ScaSqlStrsInstall( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList + ); + +HRESULT ScaSqlStrsUninstall( + __in SCA_DB* psdList, + __in SCA_SQLSTR* psssList + ); + +void ScaSqlStrsFreeList( + __in SCA_SQLSTR* psssList + ); + diff --git a/src/ca/scauser.cpp b/src/ca/scauser.cpp new file mode 100644 index 00000000..4d74e4d4 --- /dev/null +++ b/src/ca/scauser.cpp @@ -0,0 +1,82 @@ +// 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" + +LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Password` FROM `User` WHERE `User`=?"; +enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqPassword }; + + +HRESULT __stdcall ScaGetUser( + __in LPCWSTR wzUser, + __out SCA_USER* pscau + ) +{ + if (!wzUser || !pscau) + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + PMSIHANDLE hView, hRec; + + LPWSTR pwzData = NULL; + + // clear struct and bail right away if no user key was passed to search for + ::ZeroMemory(pscau, sizeof(*pscau)); + if (!*wzUser) + { + ExitFunction1(hr = S_OK); + } + + hRec = ::MsiCreateRecord(1); + hr = WcaSetRecordString(hRec, 1, wzUser); + ExitOnFailure(hr, "Failed to look up User"); + + hr = WcaOpenView(vcsUserQuery, &hView); + ExitOnFailure(hr, "Failed to open view on User table"); + hr = WcaExecuteView(hView, hRec); + ExitOnFailure(hr, "Failed to execute view on User table"); + + hr = WcaFetchSingleRecord(hView, &hRec); + if (S_OK == hr) + { + hr = WcaGetRecordString(hRec, vuqUser, &pwzData); + ExitOnFailure(hr, "Failed to get User.User"); + hr = ::StringCchCopyW(pscau->wzKey, countof(pscau->wzKey), pwzData); + ExitOnFailure(hr, "Failed to copy key string to user object"); + + hr = WcaGetRecordString(hRec, vuqComponent, &pwzData); + ExitOnFailure(hr, "Failed to get User.Component_"); + hr = ::StringCchCopyW(pscau->wzComponent, countof(pscau->wzComponent), pwzData); + ExitOnFailure(hr, "Failed to copy component string to user object"); + + hr = WcaGetRecordFormattedString(hRec, vuqName, &pwzData); + ExitOnFailure(hr, "Failed to get User.Name"); + hr = ::StringCchCopyW(pscau->wzName, countof(pscau->wzName), pwzData); + ExitOnFailure(hr, "Failed to copy name string to user object"); + + hr = WcaGetRecordFormattedString(hRec, vuqDomain, &pwzData); + ExitOnFailure(hr, "Failed to get User.Domain"); + hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData); + ExitOnFailure(hr, "Failed to copy domain string to user object"); + + hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData); + ExitOnFailure(hr, "Failed to get User.Password"); + hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData); + ExitOnFailure(hr, "Failed to copy password string to user object"); + } + else if (E_NOMOREITEMS == hr) + { + WcaLog(LOGMSG_STANDARD, "Error: Cannot locate User.User='%ls'", wzUser); + hr = E_FAIL; + } + else + { + ExitOnFailure(hr, "Error or found multiple matching User rows"); + } + +LExit: + ReleaseStr(pwzData); + + return hr; +} diff --git a/src/ca/scauser.h b/src/ca/scauser.h new file mode 100644 index 00000000..20e561f2 --- /dev/null +++ b/src/ca/scauser.h @@ -0,0 +1,40 @@ +#pragma once +// 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. + + + +// structs +struct SCA_GROUP +{ + WCHAR wzKey[MAX_DARWIN_KEY + 1]; + WCHAR wzComponent[MAX_DARWIN_KEY + 1]; + + WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; + WCHAR wzName[MAX_DARWIN_COLUMN + 1]; + + SCA_GROUP *psgNext; +}; + +struct SCA_USER +{ + WCHAR wzKey[MAX_DARWIN_KEY + 1]; + WCHAR wzComponent[MAX_DARWIN_KEY + 1]; + INSTALLSTATE isInstalled; + INSTALLSTATE isAction; + + WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; + WCHAR wzName[MAX_DARWIN_COLUMN + 1]; + WCHAR wzPassword[MAX_DARWIN_COLUMN + 1]; + INT iAttributes; + + SCA_GROUP *psgGroups; + + SCA_USER *psuNext; +}; + + +// prototypes +HRESULT __stdcall ScaGetUser( + __in LPCWSTR wzUser, + __out SCA_USER* pscau + ); diff --git a/src/ca/sqlca.def b/src/ca/sqlca.def index e16626b3..e899d560 100644 --- a/src/ca/sqlca.def +++ b/src/ca/sqlca.def @@ -4,4 +4,10 @@ LIBRARY "sqlca" EXPORTS - +;scaexec.cpp + CreateDatabase + DropDatabase + ExecuteSqlStrings +;scasql.cpp + InstallSqlData + UninstallSqlData diff --git a/src/ca/sqlca.vcxproj b/src/ca/sqlca.vcxproj index 3d638f6e..b1f7dafa 100644 --- a/src/ca/sqlca.vcxproj +++ b/src/ca/sqlca.vcxproj @@ -45,11 +45,22 @@ Create + + + + + + + + + + + -- cgit v1.2.3-55-g6feb