blob: 67dfde82c9123b0492a9d4fd8cd9f6436edafc1e [file] [log] [blame]
/*
* 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