// Copyright 2022 Heath Stewart.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
use crate::ffi;
use crate::{Database, Error, Record, Result};
use std::ffi::CString;
/// A Windows Installer session passed to custom actions.
///
/// # Example
///
/// ```no_run
/// use msica::*;
/// const ERROR_SUCCESS: u32 = 0;
///
/// #[no_mangle]
/// pub extern "C" fn MyCustomAction(session: Session) -> u32 {
/// let record = Record::with_fields(
/// Some("this is [1] [2]"),
/// vec![Field::IntegerData(1), Field::StringData("example".to_owned())],
/// ).expect("failed to create record");
/// session.message(MessageType::User, &record);
/// ERROR_SUCCESS
/// }
/// ```
#[repr(transparent)]
pub struct Session {
h: ffi::MSIHANDLE,
}
impl Session {
/// Returns the active database for the installation. This function returns a read-only [`Database`].
pub fn database(&self) -> Database {
unsafe {
let h = ffi::MsiGetActiveDatabase(self.h);
Database::from_handle(h)
}
}
/// Runs the specified immediate custom action, or schedules a deferred custom action.
/// If `None` the default action is run e.g., `INSTALL`.
///
/// To schedule a deferred custom action with its `CustomActionData`,
/// call `do_deferred_action`.
pub fn do_action(&self, action: Option<&str>) -> Result<()> {
unsafe {
let action = match action {
Some(s) => CString::new(s)?,
None => CString::default(),
};
let ret = ffi::MsiDoAction(self.h, action.as_ptr());
if ret != ffi::ERROR_SUCCESS {
return Err(Error::from_error_code(ret));
}
Ok(())
}
}
/// Sets custom action data and schedules a deferred custom action.
///
/// # Example
///
/// ```no_run
/// use msica::*;
/// const ERROR_SUCCESS: u32 = 0;
///
/// #[no_mangle]
/// pub extern "C" fn MyCustomAction(session: Session) -> u32 {
/// for i in 0..5 {
/// session.do_deferred_action("MyDeferredCustomAction", &i.to_string());
/// }
/// ERROR_SUCCESS
/// }
///
/// #[no_mangle]
/// pub extern "C" fn MyDeferredCustomAction(session: Session) -> u32 {
/// let data = session.property("CustomActionData").expect("failed to get CustomActionData");
/// let record = Record::try_from(data).expect("failed to create record");
/// session.message(MessageType::Info, &record);
/// ERROR_SUCCESS
/// }
/// ```
pub fn do_deferred_action(&self, action: &str, custom_action_data: &str) -> Result<()> {
self.set_property(action, Some(custom_action_data))?;
self.do_action(Some(action))
}
/// The numeric language ID used by the current install session.
pub fn language(&self) -> u16 {
unsafe { ffi::MsiGetLanguage(self.h) }
}
/// Processes a [`Record`] within the [`Session`].
pub fn message(&self, kind: MessageType, record: &Record) -> i32 {
unsafe { ffi::MsiProcessMessage(self.h, kind, *record.h) }
}
/// Returns a boolean indicating whether the specific property passed into the function is currently set (true) or not set (false).
///
/// # Example
///
/// You could use the same custom action entry point for scheduling and executing deferred actions:
///
/// ```no_run
/// use msica::*;
/// const ERROR_SUCCESS: u32 = 0;
///
/// #[no_mangle]
/// pub extern "C" fn MyCustomAction(session: Session) -> u32 {
/// if !session.mode(RunMode::Scheduled) {
/// session.do_deferred_action("MyCustomAction", "Hello, world!");
/// } else {
/// let data = session.property("CustomActionData").expect("failed to get CustomActionData");
/// let record = Record::with_fields(Some(data.as_str()), vec![]).expect("failed to create record");
/// session.message(MessageType::User, &record);
/// }
/// ERROR_SUCCESS
/// }
/// ```
pub fn mode(&self, mode: RunMode) -> bool {
unsafe { ffi::MsiGetMode(self.h, mode).as_bool() }
}
/// Gets the value of the named property, or an empty string if undefined.
pub fn property(&self, name: &str) -> Result<String> {
unsafe {
// TODO: Return result containing NulError if returned.
let name = CString::new(name)?;
let mut value_len = 0u32;
let value = CString::default();
let mut ret = ffi::MsiGetProperty(
self.h,
name.as_ptr(),
value.as_ptr() as ffi::LPSTR,
&mut value_len as *mut u32,
);
if ret != ffi::ERROR_MORE_DATA {
return Err(Error::from_error_code(ret));
}
let mut value_len = value_len + 1u32;
let mut value: Vec<u8> = vec![0; value_len as usize];
ret = ffi::MsiGetProperty(
self.h,
name.as_ptr(),
value.as_mut_ptr() as ffi::LPSTR,
&mut value_len as *mut u32,
);
if ret != ffi::ERROR_SUCCESS {
return Err(Error::from_error_code(ret));
}
value.truncate(value_len as usize);
let text = String::from_utf8(value)?;
Ok(text)
}
}
/// Sets the value of the named property. Pass `None` to clear the field.
pub fn set_property(&self, name: &str, value: Option<&str>) -> Result<()> {
unsafe {
let name = CString::new(name)?;
let value = match value {
Some(s) => CString::new(s)?,
None => CString::default(),
};
let ret = ffi::MsiSetProperty(
self.h,
name.as_ptr() as ffi::LPCSTR,
value.as_ptr() as ffi::LPCSTR,
);
if ret != ffi::ERROR_SUCCESS {
return Err(Error::from_error_code(ret));
}
Ok(())
}
}
}
/// Message types that can be processed by a custom action.
#[repr(u32)]
pub enum MessageType {
Error = 0x0100_0000,
Warning = 0x0200_0000,
User = 0x0300_0000,
Info = 0x0400_0000,
Progress = 0x0a00_0000,
CommonData = 0x0b00_0000,
}
/// Run modes passed to `Session::mode`.
#[repr(u32)]
pub enum RunMode {
/// Administrative mode install, else product install.
Admin = 0,
/// Advertise mode of install.
Advertise = 1,
///Maintenance mode database loaded.
Maintenance = 2,
/// Rollback is enabled.
RollbackEnabled = 3,
/// Log file is active.
LogEnabled = 4,
/// Executing or spooling operations.
Operations = 5,
/// Reboot is needed.
RebootAtEnd = 6,
/// Reboot is needed to continue installation
RebootNow = 7,
/// Installing files from cabinets and files using Media table.
Cabinet = 8,
/// Source files use only short file names.
SourceShortNames = 9,
/// Target files are to use only short file names.
TargetShortNames = 10,
/// Operating system is Windows 98/95.
Windows9x = 12,
/// Operating system supports advertising of products.
ZawEnabled = 13,
/// Deferred custom action called from install script execution.
Scheduled = 16,
/// Deferred custom action called from rollback execution script.
Rollback = 17,
/// Deferred custom action called from commit execution script.
Commit = 18,
}