| 1 | // 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. |
| 2 | |
| 3 | namespace WixToolset.Dtf.WindowsInstaller |
| 4 | { |
| 5 | using System; |
| 6 | using System.Collections; |
| 7 | using System.Runtime.InteropServices; |
| 8 | using System.Diagnostics.CodeAnalysis; |
| 9 | |
| 10 | /// <summary> |
| 11 | /// Defines a callback function that the installer calls for progress notification and error messages. |
| 12 | /// </summary> |
| 13 | public delegate MessageResult ExternalUIHandler( |
| 14 | InstallMessage messageType, |
| 15 | string message, |
| 16 | MessageButtons buttons, |
| 17 | MessageIcon icon, |
| 18 | MessageDefaultButton defaultButton); |
| 19 | |
| 20 | /// <summary> |
| 21 | /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages. |
| 22 | /// </summary> |
| 23 | public delegate MessageResult ExternalUIRecordHandler( |
| 24 | InstallMessage messageType, |
| 25 | Record messageRecord, |
| 26 | MessageButtons buttons, |
| 27 | MessageIcon icon, |
| 28 | MessageDefaultButton defaultButton); |
| 29 | |
| 30 | internal delegate int NativeExternalUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); |
| 31 | |
| 32 | internal delegate int NativeExternalUIRecordHandler(IntPtr context, int messageType, int recordHandle); |
| 33 | |
| 34 | internal class ExternalUIProxy |
| 35 | { |
| 36 | private ExternalUIHandler handler; |
| 37 | |
| 38 | internal ExternalUIProxy(ExternalUIHandler handler) |
| 39 | { |
| 40 | this.handler = handler; |
| 41 | } |
| 42 | |
| 43 | public ExternalUIHandler Handler |
| 44 | { |
| 45 | get { return this.handler; } |
| 46 | } |
| 47 | |
| 48 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 49 | public int ProxyHandler(IntPtr contextPtr, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message) |
| 50 | { |
| 51 | try |
| 52 | { |
| 53 | int msgType = messageType & 0x7F000000; |
| 54 | int buttons = messageType & 0x0000000F; |
| 55 | int icon = messageType & 0x000000F0; |
| 56 | int defButton = messageType & 0x00000F00; |
| 57 | |
| 58 | return (int) this.handler( |
| 59 | (InstallMessage) msgType, |
| 60 | message, |
| 61 | (MessageButtons) buttons, |
| 62 | (MessageIcon) icon, |
| 63 | (MessageDefaultButton) defButton); |
| 64 | } |
| 65 | catch |
| 66 | { |
| 67 | return (int) MessageResult.Error; |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | internal class ExternalUIRecordProxy |
| 73 | { |
| 74 | private ExternalUIRecordHandler handler; |
| 75 | |
| 76 | internal ExternalUIRecordProxy(ExternalUIRecordHandler handler) |
| 77 | { |
| 78 | this.handler = handler; |
| 79 | } |
| 80 | |
| 81 | public ExternalUIRecordHandler Handler |
| 82 | { |
| 83 | get { return this.handler; } |
| 84 | } |
| 85 | |
| 86 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 87 | public int ProxyHandler(IntPtr contextPtr, int messageType, int recordHandle) |
| 88 | { |
| 89 | try |
| 90 | { |
| 91 | int msgType = messageType & 0x7F000000; |
| 92 | int buttons = messageType & 0x0000000F; |
| 93 | int icon = messageType & 0x000000F0; |
| 94 | int defButton = messageType & 0x00000F00; |
| 95 | |
| 96 | Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null); |
| 97 | using (msgRec) |
| 98 | { |
| 99 | return (int) this.handler( |
| 100 | (InstallMessage) msgType, |
| 101 | msgRec, |
| 102 | (MessageButtons) buttons, |
| 103 | (MessageIcon) icon, |
| 104 | (MessageDefaultButton) defButton); |
| 105 | } |
| 106 | } |
| 107 | catch |
| 108 | { |
| 109 | return (int) MessageResult.Error; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | public static partial class Installer |
| 115 | { |
| 116 | private static IList externalUIHandlers = ArrayList.Synchronized(new ArrayList()); |
| 117 | |
| 118 | /// <summary> |
| 119 | /// Enables an external user-interface handler. This external UI handler is called before the |
| 120 | /// normal internal user-interface handler. The external UI handler has the option to suppress |
| 121 | /// the internal UI by returning a non-zero value to indicate that it has handled the messages. |
| 122 | /// </summary> |
| 123 | /// <param name="uiHandler">A callback delegate that handles the UI messages</param> |
| 124 | /// <param name="messageFilter">Specifies which messages to handle using the external message handler. |
| 125 | /// If the external handler returns a non-zero result, then that message will not be sent to the UI, |
| 126 | /// instead the message will be logged if logging has been enabled.</param> |
| 127 | /// <returns>The previously set external handler, or null if there was no previously set handler</returns> |
| 128 | /// <remarks><p> |
| 129 | /// To restore the previous UI handler, a second call is made to SetExternalUI using the |
| 130 | /// ExternalUIHandler returned by the first call to SetExternalUI and specifying |
| 131 | /// <see cref="InstallLogModes.None"/> as the message filter. |
| 132 | /// </p><p> |
| 133 | /// The external user interface handler does not have full control over the external user |
| 134 | /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to |
| 135 | /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user |
| 136 | /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not |
| 137 | /// handled by the external user interface handler is handled by Windows Installer. The initial |
| 138 | /// "Preparing to install..." dialog always appears even if the external user interface |
| 139 | /// handler handles all messages. |
| 140 | /// </p><p> |
| 141 | /// SetExternalUI should only be called from a bootstrapping application. You cannot call |
| 142 | /// it from a custom action |
| 143 | /// </p><p> |
| 144 | /// Win32 MSI API: |
| 145 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternalui.asp">MsiSetExternalUI</a> |
| 146 | /// </p></remarks> |
| 147 | public static ExternalUIHandler SetExternalUI(ExternalUIHandler uiHandler, InstallLogModes messageFilter) |
| 148 | { |
| 149 | NativeExternalUIHandler nativeHandler = null; |
| 150 | if (uiHandler != null) |
| 151 | { |
| 152 | nativeHandler = new ExternalUIProxy(uiHandler).ProxyHandler; |
| 153 | Installer.externalUIHandlers.Add(nativeHandler); |
| 154 | } |
| 155 | NativeExternalUIHandler oldNativeHandler = NativeMethods.MsiSetExternalUI(nativeHandler, (uint) messageFilter, IntPtr.Zero); |
| 156 | if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIProxy) |
| 157 | { |
| 158 | Installer.externalUIHandlers.Remove(oldNativeHandler); |
| 159 | return ((ExternalUIProxy) oldNativeHandler.Target).Handler; |
| 160 | } |
| 161 | else |
| 162 | { |
| 163 | return null; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /// <summary> |
| 168 | /// [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called |
| 169 | /// before the normal internal user-interface handler. The external UI handler has the option to suppress |
| 170 | /// the internal UI by returning a non-zero value to indicate that it has handled the messages. |
| 171 | /// </summary> |
| 172 | /// <param name="uiHandler">A callback delegate that handles the UI messages</param> |
| 173 | /// <param name="messageFilter">Specifies which messages to handle using the external message handler. |
| 174 | /// If the external handler returns a non-zero result, then that message will not be sent to the UI, |
| 175 | /// instead the message will be logged if logging has been enabled.</param> |
| 176 | /// <returns>The previously set external handler, or null if there was no previously set handler</returns> |
| 177 | /// <remarks><p> |
| 178 | /// To restore the previous UI handler, a second call is made to SetExternalUI using the |
| 179 | /// ExternalUIHandler returned by the first call to SetExternalUI and specifying |
| 180 | /// <see cref="InstallLogModes.None"/> as the message filter. |
| 181 | /// </p><p> |
| 182 | /// The external user interface handler does not have full control over the external user |
| 183 | /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to |
| 184 | /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user |
| 185 | /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not |
| 186 | /// handled by the external user interface handler is handled by Windows Installer. The initial |
| 187 | /// "Preparing to install..." dialog always appears even if the external user interface |
| 188 | /// handler handles all messages. |
| 189 | /// </p><p> |
| 190 | /// SetExternalUI should only be called from a bootstrapping application. You cannot call |
| 191 | /// it from a custom action |
| 192 | /// </p><p> |
| 193 | /// Win32 MSI API: |
| 194 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternaluirecord.asp">MsiSetExternalUIRecord</a> |
| 195 | /// </p></remarks> |
| 196 | public static ExternalUIRecordHandler SetExternalUI(ExternalUIRecordHandler uiHandler, InstallLogModes messageFilter) |
| 197 | { |
| 198 | NativeExternalUIRecordHandler nativeHandler = null; |
| 199 | if (uiHandler != null) |
| 200 | { |
| 201 | nativeHandler = new ExternalUIRecordProxy(uiHandler).ProxyHandler; |
| 202 | Installer.externalUIHandlers.Add(nativeHandler); |
| 203 | } |
| 204 | NativeExternalUIRecordHandler oldNativeHandler; |
| 205 | uint ret = NativeMethods.MsiSetExternalUIRecord(nativeHandler, (uint) messageFilter, IntPtr.Zero, out oldNativeHandler); |
| 206 | if (ret != 0) |
| 207 | { |
| 208 | Installer.externalUIHandlers.Remove(nativeHandler); |
| 209 | throw InstallerException.ExceptionFromReturnCode(ret); |
| 210 | } |
| 211 | |
| 212 | if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIRecordProxy) |
| 213 | { |
| 214 | Installer.externalUIHandlers.Remove(oldNativeHandler); |
| 215 | return ((ExternalUIRecordProxy) oldNativeHandler.Target).Handler; |
| 216 | } |
| 217 | else |
| 218 | { |
| 219 | return null; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | } |