| /* |
| * 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 |