blob: 59a4330252b1fd85f05a489d33edbb3ececed2a2 [file] [log] [blame]
/*
* 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;
}
}
}
}