| /* |
| * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| #include "MsiDb.h" |
| #include "MsiCA.h" |
| #include "Version.h" |
| #include "FileUtils.h" |
| #include "WinErrorHandling.h" |
| |
| |
| JP_CA(CheckInstallDir) { |
| const tstring installDir = ca.getProperty(_T("INSTALLDIR")); |
| |
| bool canProceed = !FileUtils::isFileExists(installDir); |
| if (!canProceed && FileUtils::isDirectory(installDir)) { |
| canProceed = !FileUtils::isDirectoryNotEmpty(installDir); |
| } |
| |
| ca.setProperty(_T("INSTALLDIR_VALID"), canProceed ? _T("1") : _T("0")); |
| } |
| |
| |
| namespace { |
| |
| typedef Version< |
| VersionDetails::Base<10, VersionDetails::Parser, 2> |
| > DottedVersion; |
| |
| |
| class ProductInfo { |
| public: |
| explicit ProductInfo(const Guid& pc): productCode(pc), |
| version(msi::getProductInfo(pc, INSTALLPROPERTY_VERSIONSTRING)) { |
| } |
| |
| const DottedVersion& getVersion() const { |
| return version; |
| } |
| |
| const Guid& getProductCode() const { |
| return productCode; |
| } |
| |
| private: |
| Guid productCode; |
| DottedVersion version; |
| }; |
| |
| |
| void findInstalledProducts(const Guid& upgradeCode, |
| std::vector<ProductInfo>& products) { |
| const LPCTSTR upgradeCodeStr = upgradeCode.toMsiString().c_str(); |
| for (DWORD productCodeIdx = 0; true; ++productCodeIdx) { |
| TCHAR productCode[39 /* http://msdn.microsoft.com/en-us/library/aa370101(v=vs.85).aspx */]; |
| const UINT status = MsiEnumRelatedProducts(upgradeCodeStr, 0, |
| productCodeIdx, productCode); |
| if (ERROR_NO_MORE_ITEMS == status) { |
| break; |
| } |
| |
| if (ERROR_SUCCESS == status) { |
| LOG_TRACE(tstrings::any() << "Found " << productCode << " product"); |
| JP_NO_THROW(products.push_back(ProductInfo(Guid(productCode)))); |
| } else { |
| LOG_WARNING(tstrings::any() |
| << "MsiEnumRelatedProducts(" |
| << upgradeCodeStr << ", " |
| << productCodeIdx |
| << ") failed with error=[" << status << "]"); |
| if (ERROR_INVALID_PARAMETER == status) { |
| break; |
| } |
| } |
| } |
| } |
| |
| DottedVersion getDottedVersion(const msi::DatabaseRecord& record, UINT idx) { |
| if (!MsiRecordIsNull(record.getHandle(), idx)) { |
| JP_NO_THROW(return DottedVersion(record.getString(idx))); |
| } |
| |
| return DottedVersion(); |
| } |
| |
| bool dbContainsUpgradeTable(const msi::Database &db) { |
| msi::DatabaseView view(db, |
| _T("SELECT Name FROM _Tables WHERE Name = 'Upgrade'")); |
| msi::DatabaseRecord record; |
| while (!record.tryFetch(view).empty()) { |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| JP_CA(FindRelatedProductsEx) { |
| if (ca.isInMode(MSIRUNMODE_MAINTENANCE)) { |
| // MSI skips standard FindRelatedProducts action in maintenance mode, |
| // so should we do for custom FindRelatedProducts action |
| LOG_TRACE("Not run in maintenance mode"); |
| return; |
| } |
| |
| const msi::Database db(ca); |
| if (!dbContainsUpgradeTable(db)) { |
| LOG_TRACE("The package doesn't contain Upgrade table"); |
| return; |
| } |
| |
| const Guid upgradeCode = Guid(ca.getProperty(_T("UpgradeCode"))); |
| |
| std::vector<ProductInfo> installedProducts; |
| findInstalledProducts(upgradeCode, installedProducts); |
| |
| bool migratePropRemoved = false; |
| |
| // https://docs.microsoft.com/en-us/windows/win32/adsi/sql-dialect |
| msi::DatabaseView view(db, (tstrings::any() |
| << _T("SELECT `VersionMin`,`VersionMax`,`Attributes`,`ActionProperty` FROM Upgrade WHERE `ActionProperty` <> NULL And `UpgradeCode` = '") |
| << upgradeCode.toMsiString() << _T("'")).tstr()); |
| msi::DatabaseRecord record; |
| while (!record.tryFetch(view).empty()) { |
| const tstring actionProperty = record.getString(4); |
| |
| // Clean up properties set by the standard FindRelatedProducts action |
| ca.removeProperty(actionProperty); |
| if (!migratePropRemoved) { |
| ca.removeProperty(_T("MIGRATE")); |
| migratePropRemoved = true; |
| } |
| |
| const DottedVersion versionMin = getDottedVersion(record, 1); |
| const DottedVersion versionMax = getDottedVersion(record, 2); |
| |
| const int attrs = MsiRecordIsNull( |
| record.getHandle(), 3) ? 0 : record.getInteger(3); |
| |
| std::vector<ProductInfo>::const_iterator productIt = |
| installedProducts.begin(); |
| std::vector<ProductInfo>::const_iterator productEnd = |
| installedProducts.end(); |
| for (; productIt != productEnd; ++productIt) { |
| bool minMatch; |
| if (versionMin.source().empty()) { |
| minMatch = true; |
| } else if (attrs & msidbUpgradeAttributesVersionMinInclusive) { |
| minMatch = (versionMin <= productIt->getVersion()); |
| } else { |
| minMatch = (versionMin < productIt->getVersion()); |
| } |
| |
| bool maxMatch; |
| if (versionMax.source().empty()) { |
| maxMatch = true; |
| } else if (attrs & msidbUpgradeAttributesVersionMaxInclusive) { |
| maxMatch = (productIt->getVersion() <= versionMax); |
| } else { |
| maxMatch = (productIt->getVersion() < versionMax); |
| } |
| |
| if (minMatch && maxMatch) { |
| tstring value = productIt->getProductCode().toMsiString(); |
| ca.setProperty(actionProperty, value); |
| ca.setProperty(_T("MIGRATE"), value); |
| // Bail out after the first match as action |
| // property has been set already. |
| // There is no way to communicate multiple product codes |
| // through a single property. |
| break; |
| } |
| } |
| } |
| } |