aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs
blob: 08f008670c3600d31c59becb7d3581f2d04c6df5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// 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;

    /// <summary>
    /// Defines a callback function that the installer calls for progress notification and error messages.
    /// </summary>
    public delegate MessageResult ExternalUIHandler(
        InstallMessage messageType,
        string message,
        MessageButtons buttons,
        MessageIcon icon,
        MessageDefaultButton defaultButton);

    /// <summary>
    /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages.
    /// </summary>
    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());

        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
        /// <param name="messageFilter">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.</param>
        /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
        /// <remarks><p>
        /// 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
        /// <see cref="InstallLogModes.None"/> as the message filter.
        /// </p><p>
        /// The external user interface handler does not have full control over the external user
        /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
        /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
        /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. 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.
        /// </p><p>
        /// SetExternalUI should only be called from a bootstrapping application. You cannot call
        /// it from a custom action
        /// </p><p>
        /// Win32 MSI API:
        /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternalui.asp">MsiSetExternalUI</a>
        /// </p></remarks>
        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;
            }
        }

        /// <summary>
        /// [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.
        /// </summary>
        /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
        /// <param name="messageFilter">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.</param>
        /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
        /// <remarks><p>
        /// 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
        /// <see cref="InstallLogModes.None"/> as the message filter.
        /// </p><p>
        /// The external user interface handler does not have full control over the external user
        /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
        /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
        /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. 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.
        /// </p><p>
        /// SetExternalUI should only be called from a bootstrapping application. You cannot call
        /// it from a custom action
        /// </p><p>
        /// Win32 MSI API:
        /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternaluirecord.asp">MsiSetExternalUIRecord</a>
        /// </p></remarks>
        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;
            }
        }
    }
}