// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Dtf.WindowsInstaller { using System; using System.Collections; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; /// /// Defines a callback function that the installer calls for progress notification and error messages. /// public delegate MessageResult ExternalUIHandler( InstallMessage messageType, string message, MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton); /// /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages. /// public delegate MessageResult ExternalUIRecordHandler( InstallMessage messageType, Record messageRecord, MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton); internal delegate int NativeExternalUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); internal delegate int NativeExternalUIRecordHandler(IntPtr context, int messageType, int recordHandle); internal class ExternalUIProxy { private ExternalUIHandler handler; internal ExternalUIProxy(ExternalUIHandler handler) { this.handler = handler; } public ExternalUIHandler Handler { get { return this.handler; } } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public int ProxyHandler(IntPtr contextPtr, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message) { try { int msgType = messageType & 0x7F000000; int buttons = messageType & 0x0000000F; int icon = messageType & 0x000000F0; int defButton = messageType & 0x00000F00; return (int) this.handler( (InstallMessage) msgType, message, (MessageButtons) buttons, (MessageIcon) icon, (MessageDefaultButton) defButton); } catch { return (int) MessageResult.Error; } } } internal class ExternalUIRecordProxy { private ExternalUIRecordHandler handler; internal ExternalUIRecordProxy(ExternalUIRecordHandler handler) { this.handler = handler; } public ExternalUIRecordHandler Handler { get { return this.handler; } } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public int ProxyHandler(IntPtr contextPtr, int messageType, int recordHandle) { try { int msgType = messageType & 0x7F000000; int buttons = messageType & 0x0000000F; int icon = messageType & 0x000000F0; int defButton = messageType & 0x00000F00; Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null); using (msgRec) { return (int) this.handler( (InstallMessage) msgType, msgRec, (MessageButtons) buttons, (MessageIcon) icon, (MessageDefaultButton) defButton); } } catch { return (int) MessageResult.Error; } } } public static partial class Installer { private static IList externalUIHandlers = ArrayList.Synchronized(new ArrayList()); /// /// Enables an external user-interface handler. This external UI handler is called before the /// normal internal user-interface handler. The external UI handler has the option to suppress /// the internal UI by returning a non-zero value to indicate that it has handled the messages. /// /// A callback delegate that handles the UI messages /// Specifies which messages to handle using the external message handler. /// If the external handler returns a non-zero result, then that message will not be sent to the UI, /// instead the message will be logged if logging has been enabled. /// The previously set external handler, or null if there was no previously set handler ///

/// To restore the previous UI handler, a second call is made to SetExternalUI using the /// ExternalUIHandler returned by the first call to SetExternalUI and specifying /// as the message filter. ///

/// The external user interface handler does not have full control over the external user /// interface unless is called with the uiLevel parameter set to /// . If SetInternalUI is not called, the internal user /// interface level defaults to . As a result, any message not /// handled by the external user interface handler is handled by Windows Installer. The initial /// "Preparing to install..." dialog always appears even if the external user interface /// handler handles all messages. ///

/// SetExternalUI should only be called from a bootstrapping application. You cannot call /// it from a custom action ///

/// Win32 MSI API: /// MsiSetExternalUI ///

public static ExternalUIHandler SetExternalUI(ExternalUIHandler uiHandler, InstallLogModes messageFilter) { NativeExternalUIHandler nativeHandler = null; if (uiHandler != null) { nativeHandler = new ExternalUIProxy(uiHandler).ProxyHandler; Installer.externalUIHandlers.Add(nativeHandler); } NativeExternalUIHandler oldNativeHandler = NativeMethods.MsiSetExternalUI(nativeHandler, (uint) messageFilter, IntPtr.Zero); if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIProxy) { Installer.externalUIHandlers.Remove(oldNativeHandler); return ((ExternalUIProxy) oldNativeHandler.Target).Handler; } else { return null; } } /// /// [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called /// before the normal internal user-interface handler. The external UI handler has the option to suppress /// the internal UI by returning a non-zero value to indicate that it has handled the messages. /// /// A callback delegate that handles the UI messages /// Specifies which messages to handle using the external message handler. /// If the external handler returns a non-zero result, then that message will not be sent to the UI, /// instead the message will be logged if logging has been enabled. /// The previously set external handler, or null if there was no previously set handler ///

/// To restore the previous UI handler, a second call is made to SetExternalUI using the /// ExternalUIHandler returned by the first call to SetExternalUI and specifying /// as the message filter. ///

/// The external user interface handler does not have full control over the external user /// interface unless is called with the uiLevel parameter set to /// . If SetInternalUI is not called, the internal user /// interface level defaults to . As a result, any message not /// handled by the external user interface handler is handled by Windows Installer. The initial /// "Preparing to install..." dialog always appears even if the external user interface /// handler handles all messages. ///

/// SetExternalUI should only be called from a bootstrapping application. You cannot call /// it from a custom action ///

/// Win32 MSI API: /// MsiSetExternalUIRecord ///

public static ExternalUIRecordHandler SetExternalUI(ExternalUIRecordHandler uiHandler, InstallLogModes messageFilter) { NativeExternalUIRecordHandler nativeHandler = null; if (uiHandler != null) { nativeHandler = new ExternalUIRecordProxy(uiHandler).ProxyHandler; Installer.externalUIHandlers.Add(nativeHandler); } NativeExternalUIRecordHandler oldNativeHandler; uint ret = NativeMethods.MsiSetExternalUIRecord(nativeHandler, (uint) messageFilter, IntPtr.Zero, out oldNativeHandler); if (ret != 0) { Installer.externalUIHandlers.Remove(nativeHandler); throw InstallerException.ExceptionFromReturnCode(ret); } if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIRecordProxy) { Installer.externalUIHandlers.Remove(oldNativeHandler); return ((ExternalUIRecordProxy) oldNativeHandler.Target).Handler; } else { return null; } } } }