// 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" #define IDNOACTION 0 #define INITIAL_STRINGDICT_SIZE 4 LPCWSTR vcsDependencyProviderQuery = L"SELECT `Wix4DependencyProvider`.`WixDependencyProvider`, `Wix4DependencyProvider`.`Component_`, `Wix4DependencyProvider`.`ProviderKey`, `Wix4DependencyProvider`.`Attributes` " L"FROM `Wix4DependencyProvider`"; enum eDependencyProviderQuery { dpqId = 1, dpqComponent, dpqProviderKey, dpqAttributes }; LPCWSTR vcsDependencyQuery = L"SELECT `Wix4Dependency`.`WixDependency`, `Wix4DependencyProvider`.`Component_`, `Wix4Dependency`.`ProviderKey`, `Wix4Dependency`.`MinVersion`, `Wix4Dependency`.`MaxVersion`, `Wix4Dependency`.`Attributes` " L"FROM `Wix4DependencyProvider`, `Wix4Dependency`, `Wix4DependencyRef` " L"WHERE `Wix4Dependency`.`WixDependency` = `Wix4DependencyRef`.`WixDependency_` AND `Wix4DependencyProvider`.`WixDependencyProvider` = `Wix4DependencyRef`.`WixDependencyProvider_`"; enum eDependencyComponentQuery { dqId = 1, dqComponent, dqProviderKey, dqMinVersion, dqMaxVersion, dqAttributes }; static HRESULT EnsureRequiredDependencies( __in MSIHANDLE hInstall, __in BOOL fMachineContext ); static HRESULT EnsureAbsentDependents( __in MSIHANDLE hInstall, __in BOOL fMachineContext ); static HRESULT SplitIgnoredDependents( __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents ); static HRESULT CreateDependencyRecord( __in int iMessageId, __in_ecount(cDependencies) const DEPENDENCY* rgDependencies, __in UINT cDependencies, __out MSIHANDLE *phRecord ); static LPCWSTR LogDependencyName( __in_z LPCWSTR wzName ); /*************************************************************************** WixDependencyRequire - Checks that all required dependencies are installed. ***************************************************************************/ extern "C" UINT __stdcall WixDependencyRequire( __in MSIHANDLE hInstall ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; BOOL fMachineContext = FALSE; hr = WcaInitialize(hInstall, "WixDependencyRequire"); ExitOnFailure(hr, "Failed to initialize."); hr = RegInitialize(); ExitOnFailure(hr, "Failed to initialize the registry functions."); // Determine whether we're installing per-user or per-machine. fMachineContext = WcaIsPropertySet("ALLUSERS"); // Check for any provider components being (re)installed that their requirements are already installed. hr = EnsureRequiredDependencies(hInstall, fMachineContext); ExitOnFailure(hr, "Failed to ensure required dependencies for (re)installing components."); LExit: RegUninitialize(); er = FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS; return WcaFinalize(er); } /*************************************************************************** WixDependencyCheck - Check dependencies based on component state. Note: may return ERROR_NO_MORE_ITEMS to terminate the session early. ***************************************************************************/ extern "C" UINT __stdcall WixDependencyCheck( __in MSIHANDLE hInstall ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; BOOL fMachineContext = FALSE; hr = WcaInitialize(hInstall, "WixDependencyCheck"); ExitOnFailure(hr, "Failed to initialize."); hr = RegInitialize(); ExitOnFailure(hr, "Failed to initialize the registry functions."); // Determine whether we're installing per-user or per-machine. fMachineContext = WcaIsPropertySet("ALLUSERS"); // Check for any dependents of provider components being uninstalled. hr = EnsureAbsentDependents(hInstall, fMachineContext); ExitOnFailure(hr, "Failed to ensure absent dependents for uninstalling components."); LExit: RegUninitialize(); er = FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS; return WcaFinalize(er); } /*************************************************************************** EnsureRequiredDependencies - Check that dependencies are installed for any provider component that is being installed or reinstalled. Note: Skipped if DISABLEDEPENDENCYCHECK is set. ***************************************************************************/ static HRESULT EnsureRequiredDependencies( __in MSIHANDLE /*hInstall*/, __in BOOL fMachineContext ) { HRESULT hr = S_OK; DWORD er = ERROR_SUCCESS; STRINGDICT_HANDLE sdDependencies = NULL; HKEY hkHive = NULL; PMSIHANDLE hView = NULL; PMSIHANDLE hRec = NULL; LPWSTR sczId = NULL; LPWSTR sczComponent = NULL; LPWSTR sczProviderKey = NULL; LPWSTR sczMinVersion = NULL; LPWSTR sczMaxVersion = NULL; int iAttributes = 0; WCA_TODO tComponentAction = WCA_TODO_UNKNOWN; DEPENDENCY* rgDependencies = NULL; UINT cDependencies = 0; PMSIHANDLE hDependencyRec = NULL; // Skip the dependency check if the Wix4Dependency table is missing (no dependencies to check for). hr = WcaTableExists(L"Wix4Dependency"); if (S_FALSE == hr) { WcaLog(LOGMSG_STANDARD, "Skipping the dependency check since no dependencies are authored."); ExitFunction1(hr = S_OK); } // If the table exists but not the others, the database was not authored correctly. ExitOnFailure(hr, "Failed to check if the Wix4Dependency table exists."); // Initialize the dictionary to keep track of unique dependency keys. hr = DictCreateStringList(&sdDependencies, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); ExitOnFailure(hr, "Failed to initialize the unique dependency string list."); // Set the registry hive to use depending on install context. hkHive = fMachineContext ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; // Loop over the provider components. hr = WcaOpenExecuteView(vcsDependencyQuery, &hView); ExitOnFailure(hr, "Failed to open the query view for dependencies."); while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) { hr = WcaGetRecordString(hRec, dqId, &sczId); ExitOnFailure(hr, "Failed to get Wix4Dependency.WixDependency."); hr = WcaGetRecordString(hRec, dqComponent, &sczComponent); ExitOnFailure(hr, "Failed to get Wix4DependencyProvider.Component_."); // Skip the current component if its not being installed or reinstalled. tComponentAction = WcaGetComponentToDo(sczComponent); if (WCA_TODO_INSTALL != tComponentAction && WCA_TODO_REINSTALL != tComponentAction) { WcaLog(LOGMSG_STANDARD, "Skipping dependency check for %ls because the component %ls is not being (re)installed.", sczId, sczComponent); continue; } hr = WcaGetRecordString(hRec, dqProviderKey, &sczProviderKey); ExitOnFailure(hr, "Failed to get Wix4Dependency.ProviderKey."); hr = WcaGetRecordString(hRec, dqMinVersion, &sczMinVersion); ExitOnFailure(hr, "Failed to get Wix4Dependency.MinVersion."); hr = WcaGetRecordString(hRec, dqMaxVersion, &sczMaxVersion); ExitOnFailure(hr, "Failed to get Wix4Dependency.MaxVersion."); hr = WcaGetRecordInteger(hRec, dqAttributes, &iAttributes); ExitOnFailure(hr, "Failed to get Wix4Dependency.Attributes."); // Check the registry to see if the required providers (dependencies) exist. hr = DepCheckDependency(hkHive, sczProviderKey, sczMinVersion, sczMaxVersion, iAttributes, sdDependencies, &rgDependencies, &cDependencies); if (E_NOTFOUND != hr) { ExitOnFailure(hr, "Failed dependency check for %ls.", sczId); } } if (E_NOMOREITEMS != hr) { ExitOnFailure(hr, "Failed to enumerate all of the rows in the dependency query view."); } else { hr = S_OK; } // If we collected any dependencies in the previous check, pump a message and prompt the user. if (0 < cDependencies) { hr = CreateDependencyRecord(msierrDependencyMissingDependencies, rgDependencies, cDependencies, &hDependencyRec); ExitOnFailure(hr, "Failed to create the dependency record for message %d.", msierrDependencyMissingDependencies); // Send a yes/no message with a warning icon since continuing could be detrimental. // This is sent as a USER message to better detect whether a user or dependency-aware bootstrapper is responding // or if Windows Installer or a dependency-unaware boostrapper is returning a typical default response. er = WcaProcessMessage(static_cast(INSTALLMESSAGE_USER | MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2), hDependencyRec); switch (er) { // Only a user or dependency-aware bootstrapper that prompted the user should return IDYES to continue anyway. case IDYES: ExitFunction1(hr = S_OK); // Only a user or dependency-aware bootstrapper that prompted the user should return IDNO to terminate the operation. case IDNO: WcaSetReturnValue(ERROR_INSTALL_USEREXIT); ExitFunction1(hr = S_OK); // A dependency-aware bootstrapper should return IDCANCEL if running silently and the operation should be canceled. case IDCANCEL: __fallthrough; // Bootstrappers which are not dependency-aware may return IDOK for unhandled messages. case IDOK: __fallthrough; // Windows Installer returns 0 for USER messages when silent or passive, or when a bootstrapper does not handle the message. case IDNOACTION: WcaSetReturnValue(ERROR_INSTALL_FAILURE); ExitFunction1(hr = S_OK); default: ExitOnFailure(hr = E_UNEXPECTED, "Unexpected message response %d from user or bootstrapper application.", er); } } LExit: ReleaseDependencyArray(rgDependencies, cDependencies); ReleaseStr(sczId); ReleaseStr(sczComponent); ReleaseStr(sczProviderKey); ReleaseStr(sczMinVersion); ReleaseStr(sczMaxVersion); ReleaseDict(sdDependencies); return hr; } /*************************************************************************** EnsureAbsentDependents - Checks that there are no dependents registered for providers that are being uninstalled. Note: Skipped if UPGRADINGPRODUCTCODE is set. ***************************************************************************/ static HRESULT EnsureAbsentDependents( __in MSIHANDLE /*hInstall*/, __in BOOL fMachineContext ) { HRESULT hr = S_OK; DWORD er = ERROR_SUCCESS; STRINGDICT_HANDLE sdIgnoredDependents = NULL; HKEY hkHive = NULL; PMSIHANDLE hView = NULL; PMSIHANDLE hRec = NULL; LPWSTR sczId = NULL; LPWSTR sczComponent = NULL; LPWSTR sczProviderKey = NULL; int iAttributes = 0; WCA_TODO tComponentAction = WCA_TODO_UNKNOWN; DEPENDENCY* rgDependents = NULL; UINT cDependents = 0; PMSIHANDLE hDependencyRec = NULL; BOOL fExists = FALSE; // Skip the dependent check if the Wix4DependencyProvider table is missing (no dependency providers). hr = WcaTableExists(L"Wix4DependencyProvider"); if (S_FALSE == hr) { WcaLog(LOGMSG_STANDARD, "Skipping the dependents check since no dependency providers are authored."); ExitFunction1(hr = S_OK); } ExitOnFailure(hr, "Failed to check if the Wix4DependencyProvider table exists."); // Split the IGNOREDEPENDENCIES property for use below if set. If it is "ALL", then quit now. hr = SplitIgnoredDependents(&sdIgnoredDependents); ExitOnFailure(hr, "Failed to get the ignored dependents."); hr = DictKeyExists(sdIgnoredDependents, L"ALL"); if (E_NOTFOUND != hr) { ExitOnFailure(hr, "Failed to check if \"ALL\" was set in IGNOREDEPENDENCIES."); // Otherwise... WcaLog(LOGMSG_STANDARD, "Skipping the dependencies check since IGNOREDEPENDENCIES contains \"ALL\"."); ExitFunction(); } else { // Key was not found, so proceed. hr = S_OK; } // Set the registry hive to use depending on install context. hkHive = fMachineContext ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; // Loop over the provider components. hr = WcaOpenExecuteView(vcsDependencyProviderQuery, &hView); ExitOnFailure(hr, "Failed to open the query view for dependency providers."); while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) { hr = WcaGetRecordString(hRec, dpqId, &sczId); ExitOnFailure(hr, "Failed to get Wix4DependencyProvider.WixDependencyProvider."); hr = WcaGetRecordString(hRec, dpqComponent, &sczComponent); ExitOnFailure(hr, "Failed to get Wix4DependencyProvider.Component."); // Skip the current component if its not being uninstalled. tComponentAction = WcaGetComponentToDo(sczComponent); if (WCA_TODO_UNINSTALL != tComponentAction) { WcaLog(LOGMSG_STANDARD, "Skipping dependents check for %ls because the component %ls is not being uninstalled.", sczId, sczComponent); continue; } hr = WcaGetRecordString(hRec, dpqProviderKey, &sczProviderKey); ExitOnFailure(hr, "Failed to get Wix4DependencyProvider.ProviderKey."); hr = WcaGetRecordInteger(hRec, dpqAttributes, &iAttributes); ExitOnFailure(hr, "Failed to get Wix4DependencyProvider.Attributes."); // Check the registry to see if the provider has any dependents registered. hr = DepCheckDependents(hkHive, sczProviderKey, iAttributes, sdIgnoredDependents, &rgDependents, &cDependents); ExitOnPathFailure(hr, fExists, "Failed dependents check for %ls.", sczId); } if (E_NOMOREITEMS != hr) { ExitOnFailure(hr, "Failed to enumerate all of the rows in the dependency provider query view."); } else { hr = S_OK; } // If we collected any providers with dependents in the previous check, pump a message and prompt the user. if (0 < cDependents) { hr = CreateDependencyRecord(msierrDependencyHasDependents, rgDependents, cDependents, &hDependencyRec); ExitOnFailure(hr, "Failed to create the dependency record for message %d.", msierrDependencyHasDependents); // Send a yes/no message with a warning icon since continuing could be detrimental. // This is sent as a USER message to better detect whether a user or dependency-aware bootstrapper is responding // or if Windows Installer or a dependency-unaware boostrapper is returning a typical default response. er = WcaProcessMessage(static_cast(INSTALLMESSAGE_USER | MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2), hDependencyRec); switch (er) { // Only a user or dependency-aware bootstrapper that prompted the user should return IDYES to continue anyway. case IDYES: ExitFunction1(hr = S_OK); // Only a user or dependency-aware bootstrapper that prompted the user should return IDNO to terminate the operation. case IDNO: __fallthrough; // Bootstrappers which are not dependency-aware may return IDOK for unhandled messages. case IDOK: __fallthrough; // Windows Installer returns 0 for USER messages when silent or passive, or when a bootstrapper does not handle the message. case IDNOACTION: WcaSetReturnValue(ERROR_NO_MORE_ITEMS); ExitFunction1(hr = S_OK); // A dependency-aware bootstrapper should return IDCANCEL if running silently and the operation should be canceled. case IDCANCEL: WcaSetReturnValue(ERROR_INSTALL_FAILURE); ExitFunction1(hr = S_OK); default: hr = E_UNEXPECTED; ExitOnFailure(hr, "Unexpected message response %d from user or bootstrapper application.", er); } } LExit: ReleaseDependencyArray(rgDependents, cDependents); ReleaseStr(sczId); ReleaseStr(sczComponent); ReleaseStr(sczProviderKey); return hr; } /*************************************************************************** SplitIgnoredDependents - Splits the IGNOREDEPENDENCIES property into a map. ***************************************************************************/ static HRESULT SplitIgnoredDependents( __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents ) { HRESULT hr = S_OK; LPWSTR sczIgnoreDependencies = NULL; LPCWSTR wzDelim = L";"; LPWSTR wzContext = NULL; hr = WcaGetProperty(L"IGNOREDEPENDENCIES", &sczIgnoreDependencies); ExitOnFailure(hr, "Failed to get the string value of the IGNOREDEPENDENCIES property."); hr = DictCreateStringList(psdIgnoredDependents, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); ExitOnFailure(hr, "Failed to create the string dictionary."); // Parse through the semicolon-delimited tokens and add to the string dictionary. for (LPCWSTR wzToken = ::wcstok_s(sczIgnoreDependencies, wzDelim, &wzContext); wzToken; wzToken = ::wcstok_s(NULL, wzDelim, &wzContext)) { hr = DictAddKey(*psdIgnoredDependents, wzToken); ExitOnFailure(hr, "Failed to ignored dependency \"%ls\" to the string dictionary.", wzToken); } LExit: ReleaseStr(sczIgnoreDependencies); return hr; } /*************************************************************************** CreateDependencyRecord - Creates a record containing the message template and records to send to the UI handler. Notes: Callers should call WcaProcessMessage and handle return codes. ***************************************************************************/ static HRESULT CreateDependencyRecord( __in int iMessageId, __in_ecount(cDependencies) const DEPENDENCY* rgDependencies, __in UINT cDependencies, __out MSIHANDLE *phRecord ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; UINT cParams = 0; UINT iParam = 0; // Should not be PMSIHANDLE. MSIHANDLE hRec = NULL; // Calculate the number of parameters based on the format: // msgId, count, key1, name1, key2, name2, etc. cParams = 2 + 2 * cDependencies; hRec = ::MsiCreateRecord(cParams); ExitOnNull(hRec, hr, E_OUTOFMEMORY, "Not enough memory to create the message record."); er = ::MsiRecordSetInteger(hRec, ++iParam, iMessageId); ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the message identifier into the message record."); er = ::MsiRecordSetInteger(hRec, ++iParam, cDependencies); ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the number of dependencies into the message record."); // Now loop through each dependency and add the key and name to the record. for (UINT i = 0; i < cDependencies; i++) { const DEPENDENCY* pDependency = &rgDependencies[i]; // Log message type-specific information. switch (iMessageId) { // Send a user message when installing a component that is missing some dependencies. case msierrDependencyMissingDependencies: WcaLog(LOGMSG_VERBOSE, "The dependency \"%ls\" is missing or is not the required version.", pDependency->sczKey); break; // Send a user message when uninstalling a component that still has registered dependents. case msierrDependencyHasDependents: WcaLog(LOGMSG_VERBOSE, "Found dependent \"%ls\", name: \"%ls\".", pDependency->sczKey, LogDependencyName(pDependency->sczName)); break; } er = ::MsiRecordSetStringW(hRec, ++iParam, pDependency->sczKey); ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the dependency key \"%ls\" into the message record.", pDependency->sczKey); er = ::MsiRecordSetStringW(hRec, ++iParam, pDependency->sczName); ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the dependency name \"%ls\" into the message record.", pDependency->sczName); } // Only assign the out parameter if successful to this point. *phRecord = hRec; hRec = NULL; LExit: if (hRec) { ::MsiCloseHandle(hRec); } return hr; } /*************************************************************************** LogDependencyName - Returns the dependency name or "Unknown" if null. ***************************************************************************/ static LPCWSTR LogDependencyName( __in_z LPCWSTR wzName ) { return wzName ? wzName : L"Unknown"; }