main
cs 340 lines 13.9 KB
Raw
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.IO;
7 using System.Text;
8 using System.Security;
9 using System.Reflection;
10 using System.Collections;
11 using System.Configuration;
12 using System.Runtime.InteropServices;
13 using System.Diagnostics.CodeAnalysis;
14
15 /// <summary>
16 /// Managed-code portion of the custom action proxy.
17 /// </summary>
18 internal static class CustomActionProxy
19 {
20 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
21 public static int InvokeCustomAction32(int sessionHandle, string entryPoint,
22 int remotingDelegatePtr)
23 {
24 return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
25 }
26
27 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
28 public static int InvokeCustomAction64(int sessionHandle, string entryPoint,
29 long remotingDelegatePtr)
30 {
31 return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
32 }
33
34 /// <summary>
35 /// Invokes a managed custom action method.
36 /// </summary>
37 /// <param name="sessionHandle">Integer handle to the installer session.</param>
38 /// <param name="entryPoint">Name of the custom action entrypoint. This must
39 /// either map to an entrypoint definition in the <c>customActions</c>
40 /// config section, or be an explicit entrypoint of the form:
41 /// &quot;AssemblyName!Namespace.Class.Method&quot;</param>
42 /// <param name="remotingDelegatePtr">Pointer to a delegate used to
43 /// make remote API calls, if this custom action is running out-of-proc.</param>
44 /// <returns>The value returned by the custom action method,
45 /// or ERROR_INSTALL_FAILURE if the custom action could not be invoked.</returns>
46 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
47 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
48 public static int InvokeCustomAction(int sessionHandle, string entryPoint,
49 IntPtr remotingDelegatePtr)
50 {
51 Session session = null;
52 string assemblyName, className, methodName;
53 MethodInfo method;
54
55 try
56 {
57 MsiRemoteInvoke remotingDelegate = (MsiRemoteInvoke)
58 Marshal.GetDelegateForFunctionPointer(
59 remotingDelegatePtr, typeof(MsiRemoteInvoke));
60 RemotableNativeMethods.RemotingDelegate = remotingDelegate;
61
62 sessionHandle = RemotableNativeMethods.MakeRemoteHandle(sessionHandle);
63 session = new Session((IntPtr) sessionHandle, false);
64 if (String.IsNullOrEmpty(entryPoint))
65 {
66 throw new ArgumentNullException("entryPoint");
67 }
68
69 if (!CustomActionProxy.FindEntryPoint(
70 session,
71 entryPoint,
72 out assemblyName,
73 out className,
74 out methodName))
75 {
76 return (int) ActionResult.Failure;
77 }
78 session.Log("Calling custom action {0}!{1}.{2}", assemblyName, className, methodName);
79
80 method = CustomActionProxy.GetCustomActionMethod(
81 session,
82 assemblyName,
83 className,
84 methodName);
85 if (method == null)
86 {
87 return (int) ActionResult.Failure;
88 }
89 }
90 catch (Exception ex)
91 {
92 if (session != null)
93 {
94 try
95 {
96 session.Log("Exception while loading custom action:");
97 session.Log(ex.ToString());
98 }
99 catch (Exception) { }
100 }
101 return (int) ActionResult.Failure;
102 }
103
104 string originalDirectory = null;
105
106 try
107 {
108 // Remember the original directory so we can restore it later.
109 originalDirectory = Environment.CurrentDirectory;
110
111 // Set the current directory to the location of the extracted files.
112 Environment.CurrentDirectory =
113 AppDomain.CurrentDomain.BaseDirectory;
114
115 object[] args = new object[] { session };
116 if (DebugBreakEnabled(new string[] { entryPoint, methodName }))
117 {
118 string message = String.Format(
119 "To debug your custom action, attach to process ID {0} (0x{0:x}) and click OK; otherwise, click Cancel to fail the custom action.",
120 System.Diagnostics.Process.GetCurrentProcess().Id
121 );
122
123 MessageResult button = NativeMethods.MessageBox(
124 IntPtr.Zero,
125 message,
126 "Custom Action Breakpoint",
127 (int)MessageButtons.OKCancel | (int)MessageIcon.Asterisk | (int)(MessageBoxStyles.TopMost | MessageBoxStyles.ServiceNotification)
128 );
129
130 if (MessageResult.Cancel == button)
131 {
132 return (int)ActionResult.UserExit;
133 }
134 }
135
136 ActionResult result = (ActionResult) method.Invoke(null, args);
137 session.Close();
138 return (int) result;
139 }
140 catch (InstallCanceledException)
141 {
142 return (int) ActionResult.UserExit;
143 }
144 catch (Exception ex)
145 {
146 session.Log("Exception thrown by custom action:");
147 session.Log(ex.ToString());
148 return (int) ActionResult.Failure;
149 }
150 finally
151 {
152 try
153 {
154 if (!String.IsNullOrEmpty(originalDirectory))
155 {
156 Environment.CurrentDirectory = originalDirectory;
157 }
158 }
159 catch (Exception ex)
160 {
161 session.Log("Failed to restore current directory after running custom action: {0}", ex.Message);
162 }
163 }
164 }
165
166 /// <summary>
167 /// Checks the "MMsiBreak" environment variable for any matching custom action names.
168 /// </summary>
169 /// <param name="names">List of names to search for in the environment
170 /// variable string.</param>
171 /// <returns>True if a match was found, else false.</returns>
172 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
173 internal static bool DebugBreakEnabled(string[] names)
174 {
175 string mmsibreak = Environment.GetEnvironmentVariable("MMsiBreak");
176 if (mmsibreak != null)
177 {
178 foreach (string breakName in mmsibreak.Split(',', ';'))
179 {
180 foreach (string name in names)
181 {
182 if (breakName == name)
183 {
184 return true;
185 }
186 }
187 }
188 }
189 return false;
190 }
191
192 /// <summary>
193 /// Locates and parses an entrypoint mapping in CustomAction.config.
194 /// </summary>
195 /// <param name="session">Installer session handle, just used for logging.</param>
196 /// <param name="entryPoint">Custom action entrypoint name: the key value
197 /// in an item in the <c>customActions</c> section of the config file.</param>
198 /// <param name="assemblyName">Returned display name of the assembly from
199 /// the entrypoint mapping.</param>
200 /// <param name="className">Returned class name of the entrypoint mapping.</param>
201 /// <param name="methodName">Returned method name of the entrypoint mapping.</param>
202 /// <returns>True if the entrypoint was found, false if not or if some error
203 /// occurred.</returns>
204 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
205 private static bool FindEntryPoint(
206 Session session,
207 string entryPoint,
208 out string assemblyName,
209 out string className,
210 out string methodName)
211 {
212 assemblyName = null;
213 className = null;
214 methodName = null;
215
216 string fullEntryPoint;
217 if (entryPoint.IndexOf('!') > 0)
218 {
219 fullEntryPoint = entryPoint;
220 }
221 else
222 {
223 #if NETFRAMEWORK
224 IDictionary config;
225 try
226 {
227 config = (IDictionary) ConfigurationManager.GetSection("customActions");
228 }
229 catch (ConfigurationException cex)
230 {
231 session.Log("Error: missing or invalid customActions config section.");
232 session.Log(cex.ToString());
233 return false;
234 }
235 fullEntryPoint = (string) config[entryPoint];
236 if (fullEntryPoint == null)
237 {
238 session.Log(
239 "Error: custom action entry point '{0}' not found " +
240 "in customActions config section.",
241 entryPoint);
242 return false;
243 }
244 #else
245 throw new NotImplementedException();
246 #endif
247 }
248
249 int assemblySplit = fullEntryPoint.IndexOf('!');
250 int methodSplit = fullEntryPoint.LastIndexOf('.');
251 if (assemblySplit < 0 || methodSplit < 0 || methodSplit < assemblySplit)
252 {
253 session.Log("Error: invalid custom action entry point:" + entryPoint);
254 return false;
255 }
256
257 assemblyName = fullEntryPoint.Substring(0, assemblySplit);
258 className = fullEntryPoint.Substring(assemblySplit + 1, methodSplit - assemblySplit - 1);
259 methodName = fullEntryPoint.Substring(methodSplit + 1);
260 return true;
261 }
262
263 /// <summary>
264 /// Uses reflection to load the assembly and class and find the method.
265 /// </summary>
266 /// <param name="session">Installer session handle, just used for logging.</param>
267 /// <param name="assemblyName">Display name of the assembly containing the
268 /// custom action method.</param>
269 /// <param name="className">Fully-qualified name of the class containing the
270 /// custom action method.</param>
271 /// <param name="methodName">Name of the custom action method.</param>
272 /// <returns>The method, or null if not found.</returns>
273 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
274 private static MethodInfo GetCustomActionMethod(
275 Session session,
276 string assemblyName,
277 string className,
278 string methodName)
279 {
280 Assembly customActionAssembly;
281 Type customActionClass = null;
282 Exception caughtEx = null;
283 try
284 {
285 customActionAssembly = AppDomain.CurrentDomain.Load(assemblyName);
286 customActionClass = customActionAssembly.GetType(className, true, true);
287 }
288 catch (IOException ex) { caughtEx = ex; }
289 catch (BadImageFormatException ex) { caughtEx = ex; }
290 catch (TypeLoadException ex) { caughtEx = ex; }
291 catch (ReflectionTypeLoadException ex) { caughtEx = ex; }
292 catch (SecurityException ex) { caughtEx = ex; }
293 if (caughtEx != null)
294 {
295 session.Log("Error: could not load custom action class " + className + " from assembly: " + assemblyName);
296 session.Log(caughtEx.ToString());
297 return null;
298 }
299
300 MethodInfo[] methods = customActionClass.GetMethods(
301 BindingFlags.Public | BindingFlags.Static);
302 foreach (MethodInfo method in methods)
303 {
304 if (method.Name == methodName &&
305 CustomActionProxy.MethodHasCustomActionSignature(method))
306 {
307 return method;
308 }
309 }
310 session.Log("Error: custom action method \"" + methodName +
311 "\" is missing or has the wrong signature.");
312 return null;
313 }
314
315 /// <summary>
316 /// Checks if a method has the right return and paramater types
317 /// for a custom action, and that it is marked by a CustomActionAttribute.
318 /// </summary>
319 /// <param name="method">Method to be checked.</param>
320 /// <returns>True if the method is a valid custom action, else false.</returns>
321 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
322 private static bool MethodHasCustomActionSignature(MethodInfo method)
323 {
324 if (method.ReturnType == typeof(ActionResult) &&
325 method.GetParameters().Length == 1 &&
326 method.GetParameters()[0].ParameterType == typeof(Session))
327 {
328 object[] methodAttribs = method.GetCustomAttributes(false);
329 foreach (object attrib in methodAttribs)
330 {
331 if (attrib is CustomActionAttribute)
332 {
333 return true;
334 }
335 }
336 }
337 return false;
338 }
339 }
340 }