| 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.Configuration; |
| 8 | using System.Diagnostics.CodeAnalysis; |
| 9 | using System.IO; |
| 10 | using System.Reflection; |
| 11 | using System.Runtime.InteropServices; |
| 12 | using System.Security; |
| 13 | using System.Text; |
| 14 | |
| 15 | /// <summary> |
| 16 | /// Managed-code portion of the embedded UI proxy. |
| 17 | /// </summary> |
| 18 | internal static class EmbeddedUIProxy |
| 19 | { |
| 20 | private static IEmbeddedUI uiInstance; |
| 21 | private static string uiClass; |
| 22 | |
| 23 | private static bool DebugBreakEnabled(string method) |
| 24 | { |
| 25 | return CustomActionProxy.DebugBreakEnabled(new string[] { method, EmbeddedUIProxy.uiClass + "." + method } ); |
| 26 | } |
| 27 | |
| 28 | /// <summary> |
| 29 | /// Initializes managed embedded UI by loading the UI class and invoking its Initialize method. |
| 30 | /// </summary> |
| 31 | /// <param name="sessionHandle">Integer handle to the installer session.</param> |
| 32 | /// <param name="uiClass">Name of the class that implements the embedded UI. This must |
| 33 | /// be of the form: "AssemblyName!Namespace.Class"</param> |
| 34 | /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this |
| 35 | /// method returns, the installer resets the UI level to the returned value of this parameter.</param> |
| 36 | /// <returns>0 if the embedded UI was successfully loaded and initialized, |
| 37 | /// ERROR_INSTALL_USEREXIT if the user canceled the installation during initialization, |
| 38 | /// or ERROR_INSTALL_FAILURE if the embedded UI could not be initialized.</returns> |
| 39 | /// <remarks> |
| 40 | /// Due to interop limitations, the successful resulting UILevel is actually returned |
| 41 | /// as the high-word of the return value instead of via a ref parameter. |
| 42 | /// </remarks> |
| 43 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 44 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| 45 | public static int Initialize(int sessionHandle, string uiClass, int internalUILevel) |
| 46 | { |
| 47 | Session session = null; |
| 48 | |
| 49 | try |
| 50 | { |
| 51 | session = new Session((IntPtr) sessionHandle, false); |
| 52 | |
| 53 | if (String.IsNullOrEmpty(uiClass)) |
| 54 | { |
| 55 | throw new ArgumentNullException("uiClass"); |
| 56 | } |
| 57 | |
| 58 | EmbeddedUIProxy.uiInstance = EmbeddedUIProxy.InstantiateUI(session, uiClass); |
| 59 | } |
| 60 | catch (Exception ex) |
| 61 | { |
| 62 | if (session != null) |
| 63 | { |
| 64 | try |
| 65 | { |
| 66 | session.Log("Exception while loading embedded UI:"); |
| 67 | session.Log(ex.ToString()); |
| 68 | } |
| 69 | catch (Exception) |
| 70 | { |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | if (EmbeddedUIProxy.uiInstance == null) |
| 76 | { |
| 77 | return (int) ActionResult.Failure; |
| 78 | } |
| 79 | |
| 80 | try |
| 81 | { |
| 82 | string resourcePath = Path.GetDirectoryName(EmbeddedUIProxy.uiInstance.GetType().Assembly.Location); |
| 83 | InstallUIOptions uiOptions = (InstallUIOptions) internalUILevel; |
| 84 | if (EmbeddedUIProxy.DebugBreakEnabled("Initialize")) |
| 85 | { |
| 86 | System.Diagnostics.Debugger.Launch(); |
| 87 | } |
| 88 | |
| 89 | if (EmbeddedUIProxy.uiInstance.Initialize(session, resourcePath, ref uiOptions)) |
| 90 | { |
| 91 | // The embedded UI initialized and the installation should continue |
| 92 | // with internal UI reset according to options. |
| 93 | return ((int) uiOptions) << 16; |
| 94 | } |
| 95 | else |
| 96 | { |
| 97 | // The embedded UI did not initialize but the installation should still continue |
| 98 | // with internal UI reset according to options. |
| 99 | return (int) uiOptions; |
| 100 | } |
| 101 | } |
| 102 | catch (InstallCanceledException) |
| 103 | { |
| 104 | // The installation was canceled by the user. |
| 105 | return (int) ActionResult.UserExit; |
| 106 | } |
| 107 | catch (Exception ex) |
| 108 | { |
| 109 | // An unhandled exception causes the installation to fail immediately. |
| 110 | session.Log("Exception thrown by embedded UI initialization:"); |
| 111 | session.Log(ex.ToString()); |
| 112 | return (int) ActionResult.Failure; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /// <summary> |
| 117 | /// Passes a progress message to the UI class. |
| 118 | /// </summary> |
| 119 | /// <param name="messageType">Installer message type and message box options.</param> |
| 120 | /// <param name="recordHandle">Handle to a record containing message data.</param> |
| 121 | /// <returns>Return value returned by the UI class.</returns> |
| 122 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| 123 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 124 | public static int ProcessMessage(int messageType, int recordHandle) |
| 125 | { |
| 126 | if (EmbeddedUIProxy.uiInstance != null) |
| 127 | { |
| 128 | try |
| 129 | { |
| 130 | int msgType = messageType & 0x7F000000; |
| 131 | int buttons = messageType & 0x0000000F; |
| 132 | int icon = messageType & 0x000000F0; |
| 133 | int defButton = messageType & 0x00000F00; |
| 134 | |
| 135 | Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null); |
| 136 | using (msgRec) |
| 137 | { |
| 138 | if (EmbeddedUIProxy.DebugBreakEnabled("ProcessMessage")) |
| 139 | { |
| 140 | System.Diagnostics.Debugger.Launch(); |
| 141 | } |
| 142 | |
| 143 | return (int) EmbeddedUIProxy.uiInstance.ProcessMessage( |
| 144 | (InstallMessage) msgType, |
| 145 | msgRec, |
| 146 | (MessageButtons) buttons, |
| 147 | (MessageIcon) icon, |
| 148 | (MessageDefaultButton) defButton); |
| 149 | } |
| 150 | } |
| 151 | catch (Exception) |
| 152 | { |
| 153 | // Ignore it... just hope future messages will not throw exceptions. |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | return 0; |
| 158 | } |
| 159 | |
| 160 | /// <summary> |
| 161 | /// Passes a shutdown message to the UI class. |
| 162 | /// </summary> |
| 163 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| 164 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 165 | public static void Shutdown() |
| 166 | { |
| 167 | if (EmbeddedUIProxy.uiInstance != null) |
| 168 | { |
| 169 | try |
| 170 | { |
| 171 | if (EmbeddedUIProxy.DebugBreakEnabled("Shutdown")) |
| 172 | { |
| 173 | System.Diagnostics.Debugger.Launch(); |
| 174 | } |
| 175 | |
| 176 | EmbeddedUIProxy.uiInstance.Shutdown(); |
| 177 | } |
| 178 | catch (Exception) |
| 179 | { |
| 180 | // Nothing to do at this point... the installation is done anyway. |
| 181 | } |
| 182 | |
| 183 | EmbeddedUIProxy.uiInstance = null; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | /// <summary> |
| 188 | /// Instantiates a UI class from a given assembly and class name. |
| 189 | /// </summary> |
| 190 | /// <param name="session">Installer session, for logging.</param> |
| 191 | /// <param name="uiClass">Name of the class that implements the embedded UI. This must |
| 192 | /// be of the form: "AssemblyName!Namespace.Class"</param> |
| 193 | /// <returns>Interface on the UI class for handling UI messages.</returns> |
| 194 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| 195 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] |
| 196 | private static IEmbeddedUI InstantiateUI(Session session, string uiClass) |
| 197 | { |
| 198 | int assemblySplit = uiClass.IndexOf('!'); |
| 199 | if (assemblySplit < 0) |
| 200 | { |
| 201 | session.Log("Error: invalid embedded UI assembly and class:" + uiClass); |
| 202 | return null; |
| 203 | } |
| 204 | |
| 205 | string assemblyName = uiClass.Substring(0, assemblySplit); |
| 206 | EmbeddedUIProxy.uiClass = uiClass.Substring(assemblySplit + 1); |
| 207 | |
| 208 | Assembly uiAssembly; |
| 209 | try |
| 210 | { |
| 211 | uiAssembly = AppDomain.CurrentDomain.Load(assemblyName); |
| 212 | |
| 213 | // This calls out to CustomActionProxy.DebugBreakEnabled() directly instead |
| 214 | // of calling EmbeddedUIProxy.DebugBreakEnabled() because we don't compose a |
| 215 | // class.method name for this breakpoint. |
| 216 | if (CustomActionProxy.DebugBreakEnabled(new string[] { "EmbeddedUI" })) |
| 217 | { |
| 218 | System.Diagnostics.Debugger.Launch(); |
| 219 | } |
| 220 | |
| 221 | return (IEmbeddedUI) uiAssembly.CreateInstance(EmbeddedUIProxy.uiClass); |
| 222 | } |
| 223 | catch (Exception ex) |
| 224 | { |
| 225 | session.Log("Error: could not load embedded UI class " + EmbeddedUIProxy.uiClass + " from assembly: " + assemblyName); |
| 226 | session.Log(ex.ToString()); |
| 227 | return null; |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | } |