| /* | |
| * Copyright (c) 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 "MsiCA.h" | |
| #include "MsiDb.h" | |
| #include "MsiUtils.h" | |
| #include "FileUtils.h" | |
| #include "ErrorHandling.h" | |
| #include "Toolbox.h" | |
| // Code in this file requires linking with msi.lib | |
| namespace msi { | |
| tstring CAImpl::getProperty(const tstring& name) const { | |
| return getPropertyFromCustomAction(handle, name); | |
| } | |
| void CAImpl::setProperty(const tstring& name, const tstring& value) { | |
| if (value.empty()) { | |
| JP_THROW(tstrings::any() << "Attempt to assign empty value to '" | |
| << name << "' MSI property"); | |
| } | |
| LOG_TRACE(tstrings::any() << "Setting MSI property '" << name << | |
| "' to '" << value << "'"); | |
| const UINT status = MsiSetProperty(handle, name.c_str(), value.c_str()); | |
| if (status != ERROR_SUCCESS) { | |
| JP_THROW(msi::Error(tstrings::any() << "MsiSetProperty(" << name | |
| << ", " << value << ") failed", status)); | |
| } | |
| } | |
| void CAImpl::removeProperty(const tstring& name) { | |
| LOG_TRACE(tstrings::any() << "Removing MSI property '" << name << "'"); | |
| const UINT status = MsiSetProperty(handle, name.c_str(), NULL); | |
| if (status != ERROR_SUCCESS) { | |
| JP_THROW(msi::Error(tstrings::any() << "MsiSetProperty(" << name | |
| << ", NULL) failed", status)); | |
| } | |
| } | |
| Guid CAFacade::getProductCode() const { | |
| return impl.getProperty(_T("ProductCode")); | |
| } | |
| bool CAFacade::isInMode(MSIRUNMODE v) const { | |
| return MsiGetMode(impl.getHandle(), v) != FALSE; | |
| } | |
| tstring CAFacade::getModes() const { | |
| tstring modes; | |
| // Iterate all modes in the range [MSIRUNMODE_ADMIN, MSIRUNMODE_COMMIT] | |
| for (int mode = MSIRUNMODE_ADMIN; mode != MSIRUNMODE_COMMIT + 1; ++mode) { | |
| modes.insert(modes.end(), isInMode(MSIRUNMODE(mode)) ? | |
| _T('1') : _T('0')); | |
| } | |
| return modes; | |
| } | |
| void CAFacade::doAction(const tstring& name) const { | |
| const UINT status = MsiDoAction(impl.getHandle(), name.c_str()); | |
| if (status != ERROR_SUCCESS) { | |
| JP_THROW(msi::Error(tstrings::any() << "MsiDoAction(" << name | |
| << ") failed", status)); | |
| } | |
| } | |
| tstring CAFacade::normalizeDirectoryPath(tstring v) { | |
| if (v.empty()) { | |
| return v; | |
| } | |
| std::replace(v.begin(), v.end(), '/', '\\'); | |
| return FileUtils::removeTrailingSlash(v) + _T("\\"); | |
| } | |
| CA& CA::setPropertyIfEmpty(const tstring& name, const tstring& v) { | |
| if (getProperty(name).empty()) { | |
| setProperty(name, v); | |
| } | |
| return *this; | |
| } | |
| tstring DeferredCA::getArg() const { | |
| if (isInMode(MSIRUNMODE_SCHEDULED) || caArgPropertyName.empty()) { | |
| // Details on accessing MSI properties from deferred custom actions: | |
| // http://blogs.technet.com/b/alexshev/archive/2008/03/25/property-does-not-exist-or-empty-when-accessed-from-deferred-custom-action.aspx | |
| // http://stackoverflow.com/questions/17988392/unable-to-fetch-the-install-location-property-in-a-deferred-custom-action | |
| // http://stackoverflow.com/questions/11233267/how-to-pass-customactiondata-to-a-customaction-using-wix | |
| return impl.getProperty(_T("CustomActionData")); | |
| } | |
| return impl.getProperty(caArgPropertyName); | |
| } | |
| tstring DeferredCA::getParsedArg(const tstring& name) const { | |
| const auto entry = theParsedArgs.find(name); | |
| if (entry == theParsedArgs.end()) { | |
| JP_THROW(tstrings::any() << "Argument << '" << name | |
| << "' not found."); | |
| } | |
| return entry->second; | |
| } | |
| namespace { | |
| std::pair<tstring, tstring> parseArg(const tstring& v) { | |
| const auto pos = v.find(_T('=')); | |
| if (pos == tstring::npos) { | |
| JP_THROW(tstrings::any() << "Missing expected '=' character in [" | |
| << v << "] string."); | |
| } | |
| return std::pair<tstring, tstring>(v.substr(0, pos), v.substr(pos + 1)); | |
| } | |
| void parseArgsImpl(DeferredCA::ArgsCtnr& dst, const tstring& src) { | |
| const tstring_array pairs = tstrings::split(src, _T("*")); | |
| for(auto it = pairs.begin(), end = pairs.end(); it != end; ++it) { | |
| const auto pair = parseArg(*it); | |
| dst[pair.first] = pair.second; | |
| } | |
| } | |
| } // namespace | |
| void DeferredCA::parseArgs(ArgsCtnr& dst, const tstring& src) { | |
| DeferredCA::ArgsCtnr tmp; | |
| const auto end = src.find(_T("**")); | |
| if (end != tstring::npos) { | |
| parseArgsImpl(tmp, src.substr(0, end)); | |
| tmp[tstring()] = src.substr(end + 2); | |
| } else { | |
| parseArgsImpl(tmp, src); | |
| } | |
| tmp.insert(dst.begin(), dst.end()); | |
| tmp.swap(dst); | |
| } | |
| MsiLogAppender::MsiLogAppender(MSIHANDLE h): handle(h), | |
| ctorThread(GetCurrentThreadId()) { | |
| } | |
| void MsiLogAppender::append(const LogEvent& v) { | |
| const LPCTSTR format = _T("[%02u:%02u:%02u.%03u%s%s:%u (%s)] %s: %s"); | |
| tstring ctxInfo = _T(" "); | |
| if (v.tid != ctorThread) { | |
| ctxInfo = (tstrings::any() << " (TID: " << v.tid << ") ").tstr(); | |
| } | |
| const tstring buf = tstrings::unsafe_format(format, | |
| unsigned(v.ts.wHour), unsigned(v.ts.wMinute), unsigned(v.ts.wSecond), unsigned(v.ts.wMilliseconds), // time | |
| ctxInfo.c_str(), | |
| v.fileName.c_str(), v.lineNum, v.funcName.c_str(), | |
| v.logLevel.c_str(), | |
| v.message.c_str()); | |
| DatabaseRecord r(1); | |
| r.setString(0, _T("Java [1]")); | |
| r.setString(1, buf); | |
| MsiProcessMessage(handle, INSTALLMESSAGE_INFO, r.getHandle()); | |
| } | |
| MsiLogTrigger::MsiLogTrigger(MSIHANDLE h): | |
| msiLogAppender(h), | |
| oldLogAppender(Logger::defaultLogger().getAppender()), | |
| teeLogAppender(&msiLogAppender, &oldLogAppender) { | |
| Logger::defaultLogger().setAppender(teeLogAppender); | |
| } | |
| MsiLogTrigger::~MsiLogTrigger() { | |
| Logger::defaultLogger().setAppender(oldLogAppender); | |
| } | |
| namespace { | |
| MSIHANDLE openDatabase(const CA& ca) { | |
| MSIHANDLE h = MsiGetActiveDatabase(ca.getHandle()); | |
| if (h == NULL) { | |
| JP_THROW(Error(std::string("MsiGetActiveDatabase() failed"), | |
| ERROR_FUNCTION_FAILED)); | |
| } | |
| return h; | |
| } | |
| } // namespace | |
| Database::Database(const CA& ca): msiPath(_T("*CA*")), | |
| dbHandle(openDatabase(ca)) { | |
| } | |
| } // namespace msi |