| /* |
| * 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. |
| */ |
| |
| #ifndef MsiCA_h |
| #define MsiCA_h |
| |
| #include <windows.h> |
| #include <Msi.h> |
| #include <msidefs.h> |
| #include <msiquery.h> |
| #include <map> |
| |
| #include "Log.h" |
| #include "Guid.h" |
| |
| |
| /** |
| * Helpers to implement custom actions (CA). |
| */ |
| namespace msi { |
| |
| /** |
| * Return values from CA functions. |
| */ |
| struct CAStatus { |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072(v=vs.85).aspx |
| enum Values { |
| Success = ERROR_SUCCESS, |
| |
| // Abort installation session. |
| UserExit = ERROR_INSTALL_USEREXIT, |
| |
| // Unexpected error interrupted installation session. |
| FatalError = ERROR_INSTALL_FAILURE, |
| |
| // Complete installation session without running further actions. |
| ExitNoError = ERROR_NO_MORE_ITEMS |
| }; |
| }; |
| |
| |
| /** |
| * Wrapper around MSIHANDLE passed in CA function by MSI service. |
| * Provides basic functionality to read/write property into the current MSI |
| * session. |
| */ |
| class CAImpl { |
| public: |
| explicit CAImpl(MSIHANDLE h): handle(h) { |
| } |
| |
| /** |
| * Returns value of a property with the given name. |
| * Returns empty string if property with the given name doesn't exist. |
| * Throws exception if error occurs. |
| */ |
| tstring getProperty(const tstring& name) const; |
| |
| /** |
| * Sets property value. |
| * Throws exception if error occurs. |
| * Throws exception if value is empty string. |
| */ |
| void setProperty(const tstring& name, const tstring& v); |
| |
| /** |
| * Removes property. |
| * Throws exception if error occurs. |
| */ |
| void removeProperty(const tstring& name); |
| |
| MSIHANDLE getHandle() const { |
| return handle; |
| } |
| |
| private: |
| CAImpl(const CAImpl&); |
| CAImpl& operator=(const CAImpl&); |
| private: |
| MSIHANDLE handle; |
| }; |
| |
| |
| /** |
| * Provides common functionality for deferred and immediate CAs. |
| */ |
| class CAFacade: public CAStatus { |
| public: |
| explicit CAFacade(MSIHANDLE h, UINT* status=NULL): impl(h), status(status) { |
| } |
| |
| Guid getProductCode() const; |
| |
| bool isInMode(MSIRUNMODE v) const; |
| |
| // Debug |
| tstring getModes() const; |
| |
| void exitStatus(CAStatus::Values v) { |
| if (status) { |
| *status = v; |
| } |
| } |
| |
| void doAction(const tstring& name) const; |
| |
| // Replaces all forward slashes with back slashes and ensures |
| // the last character is a backslash. |
| // Terminating directory paths with backslash is standard for MSI. |
| // Do nothing if 'path' is empty string. |
| static tstring normalizeDirectoryPath(tstring path); |
| |
| protected: |
| CAImpl impl; |
| UINT* status; |
| }; |
| |
| |
| /** |
| * Immediate CA. |
| */ |
| class CA: public CAFacade { |
| public: |
| CA(MSIHANDLE h, const tstring& /* name */, |
| UINT* status=NULL): CAFacade(h, status) { |
| } |
| |
| tstring getProperty(const tstring& name) const { |
| return impl.getProperty(name); |
| } |
| |
| CA& setProperty(const tstring& name, const tstring& v) { |
| impl.setProperty(name, v); |
| return *this; |
| } |
| |
| CA& removeProperty(const tstring& name) { |
| impl.removeProperty(name); |
| return *this; |
| } |
| |
| /** |
| * Like setProperty(), but do nothing if property with the given name |
| * exists and its value is not empty. |
| */ |
| CA& setPropertyIfEmpty(const tstring& name, const tstring& v); |
| |
| MSIHANDLE getHandle() const { |
| return impl.getHandle(); |
| } |
| }; |
| |
| |
| /** |
| * Deferred CA. |
| */ |
| class DeferredCA: public CAFacade { |
| public: |
| DeferredCA(MSIHANDLE h, const tstring& name, |
| UINT* status=NULL): CAFacade(h, status), caArgPropertyName(name) { |
| } |
| |
| typedef std::map<tstring, tstring> ArgsCtnr; |
| |
| DeferredCA& parseArgs() { |
| parseArgs(theParsedArgs, getArg()); |
| return *this; |
| } |
| |
| tstring getArg() const; |
| |
| const ArgsCtnr& parsedArgs() const { |
| return theParsedArgs; |
| } |
| |
| tstring getParsedArg(const tstring& name) const; |
| |
| static void parseArgs(ArgsCtnr& dst, const tstring& src); |
| |
| private: |
| ArgsCtnr theParsedArgs; |
| tstring caArgPropertyName; |
| }; |
| |
| |
| /** |
| * Write log messages into MSI log. |
| */ |
| class MsiLogAppender: public LogAppender { |
| public: |
| explicit MsiLogAppender(MSIHANDLE h); |
| |
| virtual void append(const LogEvent& v); |
| private: |
| MSIHANDLE handle; |
| long ctorThread; |
| }; |
| |
| |
| /** |
| * Configures logging for the current CA. |
| * Log messages that we send with LOG_INFO, LOG_ERROR, etc., go to both |
| * the existing log appender and temporary MSI log file managed by |
| * MSI service for the running MSI session (if any). |
| */ |
| class MsiLogTrigger { |
| public: |
| explicit MsiLogTrigger(MSIHANDLE h); |
| ~MsiLogTrigger(); |
| private: |
| MsiLogAppender msiLogAppender; |
| LogAppender& oldLogAppender; |
| TeeLogAppender teeLogAppender; |
| }; |
| |
| } // namespace msi |
| |
| |
| // |
| // Helpers to define CA functions. |
| // |
| // Sample usage: |
| // Define immediate CA foo: |
| // JP_CA(foo) { |
| // // `ca` is a local variable of type msi::CA. |
| // LOG_TRACE(ca.getProperty("Some property")); |
| // } |
| // |
| // Define deferred CA bar: |
| // JP_DEFERRED_CA(bar) { |
| // // `ca` is a local variable of type msi::DeferredCA. |
| // LOG_TRACE(ca.getArg()); |
| // } |
| // |
| // JP_DEFERRED_CA/JP_CA macros take care of everything related to setup CA |
| // handler: |
| // - define CA function with the right calling convention and arguments |
| // expected by MSI; |
| // - construct local instance of either DeferredCA or CA type to access data |
| // in the running MSI session; |
| // - setup logging, so that log messages issues with LOG_INFO, LOG_ERROR, etc. |
| // macros go to MSI log file; |
| // - registers CA function with linker, so there is no need to manage |
| // separate .def file with the list of CA functions explicitly. |
| // |
| #define JP_CA_BASE(name, ca_type) \ |
| static void name ## Body(ca_type&); \ |
| extern "C" UINT __stdcall name(MSIHANDLE hInstall) { \ |
| __pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)); \ |
| const msi::MsiLogTrigger logTrigger(hInstall); \ |
| JP_DEBUG_BREAK(JP_CA_DEBUG_BREAK, name); \ |
| LOG_TRACE_FUNCTION(); \ |
| JP_TRY; \ |
| UINT status = ca_type::Success; \ |
| ca_type ca(hInstall, _T(#name), &status); \ |
| LOG_TRACE(tstrings::any() << "CA modes=[" << ca.getModes() << "]"); \ |
| name ## Body(ca); \ |
| return status; \ |
| JP_CATCH_ALL; \ |
| return ca_type::FatalError; \ |
| } \ |
| static void name ## Body(ca_type& ca) |
| #define JP_CA(name) JP_CA_BASE(name, msi::CA) |
| #define JP_DEFERRED_CA(name) JP_CA_BASE(name, msi::DeferredCA) |
| |
| #define JP_CA_DECLARE(name) \ |
| extern "C" UINT __stdcall name(MSIHANDLE); \ |
| __pragma(comment(linker, "/INCLUDE:" JP_CA_MANGLED_NAME(name))) |
| |
| #ifdef _WIN64 |
| #define JP_CA_MANGLED_NAME(name) #name |
| #else |
| #define JP_CA_MANGLED_NAME(name) "_" #name "@4" |
| #endif |
| |
| #endif // #ifndef MsiCA_h |