aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs321
1 files changed, 321 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs
new file mode 100644
index 00000000..d3fd7d1b
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs
@@ -0,0 +1,321 @@
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
3namespace 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 try
105 {
106 // Set the current directory to the location of the extracted files.
107 Environment.CurrentDirectory =
108 AppDomain.CurrentDomain.BaseDirectory;
109
110 object[] args = new object[] { session };
111 if (DebugBreakEnabled(new string[] { entryPoint, methodName }))
112 {
113 string message = String.Format(
114 "To debug your custom action, attach to process ID {0} (0x{0:x}) and click OK; otherwise, click Cancel to fail the custom action.",
115 System.Diagnostics.Process.GetCurrentProcess().Id
116 );
117
118 MessageResult button = NativeMethods.MessageBox(
119 IntPtr.Zero,
120 message,
121 "Custom Action Breakpoint",
122 (int)MessageButtons.OKCancel | (int)MessageIcon.Asterisk | (int)(MessageBoxStyles.TopMost | MessageBoxStyles.ServiceNotification)
123 );
124
125 if (MessageResult.Cancel == button)
126 {
127 return (int)ActionResult.UserExit;
128 }
129 }
130
131 ActionResult result = (ActionResult) method.Invoke(null, args);
132 session.Close();
133 return (int) result;
134 }
135 catch (InstallCanceledException)
136 {
137 return (int) ActionResult.UserExit;
138 }
139 catch (Exception ex)
140 {
141 session.Log("Exception thrown by custom action:");
142 session.Log(ex.ToString());
143 return (int) ActionResult.Failure;
144 }
145 }
146
147 /// <summary>
148 /// Checks the "MMsiBreak" environment variable for any matching custom action names.
149 /// </summary>
150 /// <param name="names">List of names to search for in the environment
151 /// variable string.</param>
152 /// <returns>True if a match was found, else false.</returns>
153 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
154 internal static bool DebugBreakEnabled(string[] names)
155 {
156 string mmsibreak = Environment.GetEnvironmentVariable("MMsiBreak");
157 if (mmsibreak != null)
158 {
159 foreach (string breakName in mmsibreak.Split(',', ';'))
160 {
161 foreach (string name in names)
162 {
163 if (breakName == name)
164 {
165 return true;
166 }
167 }
168 }
169 }
170 return false;
171 }
172
173 /// <summary>
174 /// Locates and parses an entrypoint mapping in CustomAction.config.
175 /// </summary>
176 /// <param name="session">Installer session handle, just used for logging.</param>
177 /// <param name="entryPoint">Custom action entrypoint name: the key value
178 /// in an item in the <c>customActions</c> section of the config file.</param>
179 /// <param name="assemblyName">Returned display name of the assembly from
180 /// the entrypoint mapping.</param>
181 /// <param name="className">Returned class name of the entrypoint mapping.</param>
182 /// <param name="methodName">Returned method name of the entrypoint mapping.</param>
183 /// <returns>True if the entrypoint was found, false if not or if some error
184 /// occurred.</returns>
185 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
186 private static bool FindEntryPoint(
187 Session session,
188 string entryPoint,
189 out string assemblyName,
190 out string className,
191 out string methodName)
192 {
193 assemblyName = null;
194 className = null;
195 methodName = null;
196
197 string fullEntryPoint;
198 if (entryPoint.IndexOf('!') > 0)
199 {
200 fullEntryPoint = entryPoint;
201 }
202 else
203 {
204#if NET20
205 IDictionary config;
206 try
207 {
208 config = (IDictionary) ConfigurationManager.GetSection("customActions");
209 }
210 catch (ConfigurationException cex)
211 {
212 session.Log("Error: missing or invalid customActions config section.");
213 session.Log(cex.ToString());
214 return false;
215 }
216 fullEntryPoint = (string) config[entryPoint];
217 if (fullEntryPoint == null)
218 {
219 session.Log(
220 "Error: custom action entry point '{0}' not found " +
221 "in customActions config section.",
222 entryPoint);
223 return false;
224 }
225#else
226 throw new NotImplementedException();
227#endif
228 }
229
230 int assemblySplit = fullEntryPoint.IndexOf('!');
231 int methodSplit = fullEntryPoint.LastIndexOf('.');
232 if (assemblySplit < 0 || methodSplit < 0 || methodSplit < assemblySplit)
233 {
234 session.Log("Error: invalid custom action entry point:" + entryPoint);
235 return false;
236 }
237
238 assemblyName = fullEntryPoint.Substring(0, assemblySplit);
239 className = fullEntryPoint.Substring(assemblySplit + 1, methodSplit - assemblySplit - 1);
240 methodName = fullEntryPoint.Substring(methodSplit + 1);
241 return true;
242 }
243
244 /// <summary>
245 /// Uses reflection to load the assembly and class and find the method.
246 /// </summary>
247 /// <param name="session">Installer session handle, just used for logging.</param>
248 /// <param name="assemblyName">Display name of the assembly containing the
249 /// custom action method.</param>
250 /// <param name="className">Fully-qualified name of the class containing the
251 /// custom action method.</param>
252 /// <param name="methodName">Name of the custom action method.</param>
253 /// <returns>The method, or null if not found.</returns>
254 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
255 private static MethodInfo GetCustomActionMethod(
256 Session session,
257 string assemblyName,
258 string className,
259 string methodName)
260 {
261 Assembly customActionAssembly;
262 Type customActionClass = null;
263 Exception caughtEx = null;
264 try
265 {
266 customActionAssembly = AppDomain.CurrentDomain.Load(assemblyName);
267 customActionClass = customActionAssembly.GetType(className, true, true);
268 }
269 catch (IOException ex) { caughtEx = ex; }
270 catch (BadImageFormatException ex) { caughtEx = ex; }
271 catch (TypeLoadException ex) { caughtEx = ex; }
272 catch (ReflectionTypeLoadException ex) { caughtEx = ex; }
273 catch (SecurityException ex) { caughtEx = ex; }
274 if (caughtEx != null)
275 {
276 session.Log("Error: could not load custom action class " + className + " from assembly: " + assemblyName);
277 session.Log(caughtEx.ToString());
278 return null;
279 }
280
281 MethodInfo[] methods = customActionClass.GetMethods(
282 BindingFlags.Public | BindingFlags.Static);
283 foreach (MethodInfo method in methods)
284 {
285 if (method.Name == methodName &&
286 CustomActionProxy.MethodHasCustomActionSignature(method))
287 {
288 return method;
289 }
290 }
291 session.Log("Error: custom action method \"" + methodName +
292 "\" is missing or has the wrong signature.");
293 return null;
294 }
295
296 /// <summary>
297 /// Checks if a method has the right return and paramater types
298 /// for a custom action, and that it is marked by a CustomActionAttribute.
299 /// </summary>
300 /// <param name="method">Method to be checked.</param>
301 /// <returns>True if the method is a valid custom action, else false.</returns>
302 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
303 private static bool MethodHasCustomActionSignature(MethodInfo method)
304 {
305 if (method.ReturnType == typeof(ActionResult) &&
306 method.GetParameters().Length == 1 &&
307 method.GetParameters()[0].ParameterType == typeof(Session))
308 {
309 object[] methodAttribs = method.GetCustomAttributes(false);
310 foreach (object attrib in methodAttribs)
311 {
312 if (attrib is CustomActionAttribute)
313 {
314 return true;
315 }
316 }
317 }
318 return false;
319 }
320 }
321}