diff options
| author | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
| commit | 3f583916719eeef598d10a5d4e14ef14f008243b (patch) | |
| tree | 3d528e0ddb5c0550954217c97059d2f19cd6152a /src/samples/Dtf/Tools | |
| parent | 2e5ab696b8b4666d551b2a0532b95fb7fe6dbe03 (diff) | |
| download | wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.gz wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.bz2 wix-3f583916719eeef598d10a5d4e14ef14f008243b.zip | |
Merge Dtf
Diffstat (limited to 'src/samples/Dtf/Tools')
21 files changed, 4211 insertions, 0 deletions
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs new file mode 100644 index 00000000..76ff79b3 --- /dev/null +++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs | |||
| @@ -0,0 +1,711 @@ | |||
| 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.Tools.MakeSfxCA | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Security; | ||
| 9 | using System.Text; | ||
| 10 | using System.Reflection; | ||
| 11 | using Compression; | ||
| 12 | using Compression.Cab; | ||
| 13 | using Resources; | ||
| 14 | using ResourceCollection = Resources.ResourceCollection; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Command-line tool for building self-extracting custom action packages. | ||
| 18 | /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's | ||
| 19 | /// entry-points and file version to look like the CA module. | ||
| 20 | /// </summary> | ||
| 21 | public static class MakeSfxCA | ||
| 22 | { | ||
| 23 | private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; | ||
| 24 | |||
| 25 | private static TextWriter log; | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Prints usage text for the tool. | ||
| 29 | /// </summary> | ||
| 30 | /// <param name="w">Console text writer.</param> | ||
| 31 | public static void Usage(TextWriter w) | ||
| 32 | { | ||
| 33 | w.WriteLine("Deployment Tools Foundation custom action packager version {0}", | ||
| 34 | Assembly.GetExecutingAssembly().GetName().Version); | ||
| 35 | w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); | ||
| 36 | w.WriteLine(); | ||
| 37 | w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]"); | ||
| 38 | w.WriteLine(); | ||
| 39 | w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); | ||
| 40 | w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); | ||
| 41 | w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); | ||
| 42 | } | ||
| 43 | |||
| 44 | /// <summary> | ||
| 45 | /// Runs the MakeSfxCA command-line tool. | ||
| 46 | /// </summary> | ||
| 47 | /// <param name="args">Command-line arguments.</param> | ||
| 48 | /// <returns>0 on success, nonzero on failure.</returns> | ||
| 49 | public static int Main(string[] args) | ||
| 50 | { | ||
| 51 | if (args.Length < 3) | ||
| 52 | { | ||
| 53 | Usage(Console.Out); | ||
| 54 | return 1; | ||
| 55 | } | ||
| 56 | |||
| 57 | var output = args[0]; | ||
| 58 | var sfxDll = args[1]; | ||
| 59 | var inputs = new string[args.Length - 2]; | ||
| 60 | Array.Copy(args, 2, inputs, 0, inputs.Length); | ||
| 61 | |||
| 62 | try | ||
| 63 | { | ||
| 64 | Build(output, sfxDll, inputs, Console.Out); | ||
| 65 | return 0; | ||
| 66 | } | ||
| 67 | catch (ArgumentException ex) | ||
| 68 | { | ||
| 69 | Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); | ||
| 70 | return 1; | ||
| 71 | } | ||
| 72 | catch (FileNotFoundException ex) | ||
| 73 | { | ||
| 74 | Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); | ||
| 75 | return 1; | ||
| 76 | } | ||
| 77 | catch (Exception ex) | ||
| 78 | { | ||
| 79 | Console.Error.WriteLine("Error: Unexpected error: " + ex); | ||
| 80 | return 1; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// <summary> | ||
| 85 | /// Packages up all the inputs to the output location. | ||
| 86 | /// </summary> | ||
| 87 | /// <exception cref="Exception">Various exceptions are thrown | ||
| 88 | /// if things go wrong.</exception> | ||
| 89 | public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log) | ||
| 90 | { | ||
| 91 | MakeSfxCA.log = log; | ||
| 92 | |||
| 93 | if (string.IsNullOrEmpty(output)) | ||
| 94 | { | ||
| 95 | throw new ArgumentNullException("output"); | ||
| 96 | } | ||
| 97 | |||
| 98 | if (string.IsNullOrEmpty(sfxDll)) | ||
| 99 | { | ||
| 100 | throw new ArgumentNullException("sfxDll"); | ||
| 101 | } | ||
| 102 | |||
| 103 | if (inputs == null || inputs.Count == 0) | ||
| 104 | { | ||
| 105 | throw new ArgumentNullException("inputs"); | ||
| 106 | } | ||
| 107 | |||
| 108 | if (!File.Exists(sfxDll)) | ||
| 109 | { | ||
| 110 | throw new FileNotFoundException(sfxDll); | ||
| 111 | } | ||
| 112 | |||
| 113 | var customActionAssembly = inputs[0]; | ||
| 114 | if (!File.Exists(customActionAssembly)) | ||
| 115 | { | ||
| 116 | throw new FileNotFoundException(customActionAssembly); | ||
| 117 | } | ||
| 118 | |||
| 119 | inputs = MakeSfxCA.SplitList(inputs); | ||
| 120 | |||
| 121 | var inputsMap = MakeSfxCA.GetPackFileMap(inputs); | ||
| 122 | |||
| 123 | var foundWIAssembly = false; | ||
| 124 | foreach (var input in inputsMap.Keys) | ||
| 125 | { | ||
| 126 | if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, | ||
| 127 | StringComparison.OrdinalIgnoreCase) == 0) | ||
| 128 | { | ||
| 129 | foundWIAssembly = true; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | if (!foundWIAssembly) | ||
| 134 | { | ||
| 135 | throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + | ||
| 136 | " must be included in the list of support files. " + | ||
| 137 | "If using the MSBuild targets, make sure the assembly reference " + | ||
| 138 | "has the Private (Copy Local) flag set."); | ||
| 139 | } | ||
| 140 | |||
| 141 | MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); | ||
| 142 | |||
| 143 | var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); | ||
| 144 | var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); | ||
| 145 | |||
| 146 | if (entryPoints.Count == 0 && uiClass == null) | ||
| 147 | { | ||
| 148 | throw new ArgumentException( | ||
| 149 | "No CA or UI entry points found in module: " + customActionAssembly); | ||
| 150 | } | ||
| 151 | else if (entryPoints.Count > 0 && uiClass != null) | ||
| 152 | { | ||
| 153 | throw new NotSupportedException( | ||
| 154 | "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); | ||
| 155 | } | ||
| 156 | |||
| 157 | var dir = Path.GetDirectoryName(output); | ||
| 158 | if (dir.Length > 0 && !Directory.Exists(dir)) | ||
| 159 | { | ||
| 160 | Directory.CreateDirectory(dir); | ||
| 161 | } | ||
| 162 | |||
| 163 | using (Stream outputStream = File.Create(output)) | ||
| 164 | { | ||
| 165 | MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); | ||
| 166 | } | ||
| 167 | |||
| 168 | MakeSfxCA.CopyVersionResource(customActionAssembly, output); | ||
| 169 | |||
| 170 | MakeSfxCA.PackInputFiles(output, inputsMap); | ||
| 171 | |||
| 172 | log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// <summary> | ||
| 176 | /// Splits any list items delimited by semicolons into separate items. | ||
| 177 | /// </summary> | ||
| 178 | /// <param name="list">Read-only input list.</param> | ||
| 179 | /// <returns>New list with resulting split items.</returns> | ||
| 180 | private static IList<string> SplitList(IList<string> list) | ||
| 181 | { | ||
| 182 | var newList = new List<string>(list.Count); | ||
| 183 | |||
| 184 | foreach (var item in list) | ||
| 185 | { | ||
| 186 | if (!string.IsNullOrEmpty(item)) | ||
| 187 | { | ||
| 188 | foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) | ||
| 189 | { | ||
| 190 | newList.Add(splitItem); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | return newList; | ||
| 196 | } | ||
| 197 | |||
| 198 | /// <summary> | ||
| 199 | /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. | ||
| 200 | /// </summary> | ||
| 201 | /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param> | ||
| 202 | /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param> | ||
| 203 | /// <remarks> | ||
| 204 | /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them | ||
| 205 | /// to the list of input files if found. | ||
| 206 | /// </remarks> | ||
| 207 | private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir) | ||
| 208 | { | ||
| 209 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) | ||
| 210 | { | ||
| 211 | AssemblyName resolveName = new AssemblyName(args.Name); | ||
| 212 | Assembly assembly = null; | ||
| 213 | |||
| 214 | // First, try to find the assembly in the list of input files. | ||
| 215 | foreach (var inputFile in inputFiles.Values) | ||
| 216 | { | ||
| 217 | var inputName = Path.GetFileNameWithoutExtension(inputFile); | ||
| 218 | var inputExtension = Path.GetExtension(inputFile); | ||
| 219 | if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && | ||
| 220 | (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || | ||
| 221 | string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) | ||
| 222 | { | ||
| 223 | assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); | ||
| 224 | |||
| 225 | if (assembly != null) | ||
| 226 | { | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | // Second, try to find the assembly in the input directory. | ||
| 233 | if (assembly == null && inputDir != null) | ||
| 234 | { | ||
| 235 | string assemblyPath = null; | ||
| 236 | if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) | ||
| 237 | { | ||
| 238 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; | ||
| 239 | } | ||
| 240 | else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) | ||
| 241 | { | ||
| 242 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; | ||
| 243 | } | ||
| 244 | |||
| 245 | if (assemblyPath != null) | ||
| 246 | { | ||
| 247 | assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); | ||
| 248 | |||
| 249 | if (assembly != null) | ||
| 250 | { | ||
| 251 | // Add this detected dependency to the list of files to be packed. | ||
| 252 | inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | // Third, try to load the assembly from the GAC. | ||
| 258 | if (assembly == null) | ||
| 259 | { | ||
| 260 | try | ||
| 261 | { | ||
| 262 | assembly = Assembly.ReflectionOnlyLoad(args.Name); | ||
| 263 | } | ||
| 264 | catch (FileNotFoundException) | ||
| 265 | { | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | if (assembly != null) | ||
| 270 | { | ||
| 271 | if (string.Equals(assembly.GetName().ToString(), resolveName.ToString())) | ||
| 272 | { | ||
| 273 | log.WriteLine(" Loaded dependent assembly: " + assembly.Location); | ||
| 274 | return assembly; | ||
| 275 | } | ||
| 276 | |||
| 277 | log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); | ||
| 278 | log.WriteLine(" Loaded assembly : " + assembly.GetName()); | ||
| 279 | log.WriteLine(" Reference assembly: " + resolveName); | ||
| 280 | } | ||
| 281 | else | ||
| 282 | { | ||
| 283 | log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); | ||
| 284 | } | ||
| 285 | |||
| 286 | return null; | ||
| 287 | }; | ||
| 288 | } | ||
| 289 | |||
| 290 | /// <summary> | ||
| 291 | /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. | ||
| 292 | /// </summary> | ||
| 293 | /// <param name="assemblyPath">Path of the assembly file to laod.</param> | ||
| 294 | /// <returns>Loaded assembly, or null if the load failed.</returns> | ||
| 295 | private static Assembly TryLoadDependentAssembly(string assemblyPath) | ||
| 296 | { | ||
| 297 | Assembly assembly = null; | ||
| 298 | try | ||
| 299 | { | ||
| 300 | assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); | ||
| 301 | } | ||
| 302 | catch (IOException ex) | ||
| 303 | { | ||
| 304 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
| 305 | } | ||
| 306 | catch (BadImageFormatException ex) | ||
| 307 | { | ||
| 308 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
| 309 | } | ||
| 310 | catch (SecurityException ex) | ||
| 311 | { | ||
| 312 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
| 313 | } | ||
| 314 | |||
| 315 | return assembly; | ||
| 316 | } | ||
| 317 | |||
| 318 | /// <summary> | ||
| 319 | /// Searches the types in the input assembly for a type that implements IEmbeddedUI. | ||
| 320 | /// </summary> | ||
| 321 | /// <param name="module"></param> | ||
| 322 | /// <returns></returns> | ||
| 323 | private static string FindEmbeddedUIClass(string module) | ||
| 324 | { | ||
| 325 | log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); | ||
| 326 | |||
| 327 | string uiClass = null; | ||
| 328 | |||
| 329 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
| 330 | |||
| 331 | foreach (var type in assembly.GetExportedTypes()) | ||
| 332 | { | ||
| 333 | if (!type.IsAbstract) | ||
| 334 | { | ||
| 335 | foreach (var interfaceType in type.GetInterfaces()) | ||
| 336 | { | ||
| 337 | if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") | ||
| 338 | { | ||
| 339 | if (uiClass == null) | ||
| 340 | { | ||
| 341 | uiClass = assembly.GetName().Name + "!" + type.FullName; | ||
| 342 | } | ||
| 343 | else | ||
| 344 | { | ||
| 345 | throw new ArgumentException("Multiple IEmbeddedUI implementations found."); | ||
| 346 | } | ||
| 347 | } | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | return uiClass; | ||
| 353 | } | ||
| 354 | |||
| 355 | /// <summary> | ||
| 356 | /// Reflects on an input CA module to locate custom action entry-points. | ||
| 357 | /// </summary> | ||
| 358 | /// <param name="module">Assembly module with CA entry-points.</param> | ||
| 359 | /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns> | ||
| 360 | private static IDictionary<string, string> FindEntryPoints(string module) | ||
| 361 | { | ||
| 362 | log.WriteLine("Searching for custom action entry points " + | ||
| 363 | "in {0}", Path.GetFileName(module)); | ||
| 364 | |||
| 365 | var entryPoints = new Dictionary<string, string>(); | ||
| 366 | |||
| 367 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
| 368 | |||
| 369 | foreach (var type in assembly.GetExportedTypes()) | ||
| 370 | { | ||
| 371 | foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) | ||
| 372 | { | ||
| 373 | var entryPointName = MakeSfxCA.GetEntryPoint(method); | ||
| 374 | if (entryPointName != null) | ||
| 375 | { | ||
| 376 | var entryPointPath = string.Format( | ||
| 377 | "{0}!{1}.{2}", | ||
| 378 | Path.GetFileNameWithoutExtension(module), | ||
| 379 | type.FullName, | ||
| 380 | method.Name); | ||
| 381 | entryPoints.Add(entryPointName, entryPointPath); | ||
| 382 | |||
| 383 | log.WriteLine(" {0}={1}", entryPointName, entryPointPath); | ||
| 384 | } | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | return entryPoints; | ||
| 389 | } | ||
| 390 | |||
| 391 | /// <summary> | ||
| 392 | /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. | ||
| 393 | /// </summary> | ||
| 394 | /// <param name="method">A public static method.</param> | ||
| 395 | /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name, | ||
| 396 | /// or null if the method is not a custom action method.</returns> | ||
| 397 | private static string GetEntryPoint(MethodInfo method) | ||
| 398 | { | ||
| 399 | IList<CustomAttributeData> attributes; | ||
| 400 | try | ||
| 401 | { | ||
| 402 | attributes = CustomAttributeData.GetCustomAttributes(method); | ||
| 403 | } | ||
| 404 | catch (FileLoadException) | ||
| 405 | { | ||
| 406 | // Already logged load failures in the assembly-resolve-handler. | ||
| 407 | return null; | ||
| 408 | } | ||
| 409 | |||
| 410 | foreach (CustomAttributeData attribute in attributes) | ||
| 411 | { | ||
| 412 | if (attribute.ToString().StartsWith( | ||
| 413 | "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", | ||
| 414 | StringComparison.Ordinal)) | ||
| 415 | { | ||
| 416 | string entryPointName = null; | ||
| 417 | foreach (var argument in attribute.ConstructorArguments) | ||
| 418 | { | ||
| 419 | // The entry point name is the first positional argument, if specified. | ||
| 420 | entryPointName = (string) argument.Value; | ||
| 421 | break; | ||
| 422 | } | ||
| 423 | |||
| 424 | if (string.IsNullOrEmpty(entryPointName)) | ||
| 425 | { | ||
| 426 | entryPointName = method.Name; | ||
| 427 | } | ||
| 428 | |||
| 429 | return entryPointName; | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | return null; | ||
| 434 | } | ||
| 435 | |||
| 436 | /// <summary> | ||
| 437 | /// Counts the number of template entrypoints in SfxCA.dll. | ||
| 438 | /// </summary> | ||
| 439 | /// <remarks> | ||
| 440 | /// Depending on the requirements, SfxCA.dll might be built with | ||
| 441 | /// more entrypoints than the default. | ||
| 442 | /// </remarks> | ||
| 443 | private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) | ||
| 444 | { | ||
| 445 | for (var count = 0; ; count++) | ||
| 446 | { | ||
| 447 | var templateName = string.Format(entryPointFormat, count); | ||
| 448 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
| 449 | |||
| 450 | var nameOffset = FindBytes(fileBytes, templateAsciiBytes); | ||
| 451 | if (nameOffset < 0) | ||
| 452 | { | ||
| 453 | return count; | ||
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | /// <summary> | ||
| 459 | /// Writes a modified version of SfxCA.dll to the output stream, | ||
| 460 | /// with the template entry-points mapped to the CA entry-points. | ||
| 461 | /// </summary> | ||
| 462 | /// <remarks> | ||
| 463 | /// To avoid having to recompile SfxCA.dll for every different set of CAs, | ||
| 464 | /// this method looks for a preset number of template entry-points in the | ||
| 465 | /// binary file and overwrites their entrypoint name and string data with | ||
| 466 | /// CA-specific values. | ||
| 467 | /// </remarks> | ||
| 468 | private static void WriteEntryModule( | ||
| 469 | string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass) | ||
| 470 | { | ||
| 471 | log.WriteLine("Modifying SfxCA.dll stub"); | ||
| 472 | |||
| 473 | byte[] fileBytes; | ||
| 474 | using (var readStream = File.OpenRead(sfxDll)) | ||
| 475 | { | ||
| 476 | fileBytes = new byte[(int) readStream.Length]; | ||
| 477 | readStream.Read(fileBytes, 0, fileBytes.Length); | ||
| 478 | } | ||
| 479 | |||
| 480 | const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; | ||
| 481 | const int MAX_ENTRYPOINT_NAME = 72; | ||
| 482 | const int MAX_ENTRYPOINT_PATH = 160; | ||
| 483 | //var emptyBytes = new byte[0]; | ||
| 484 | |||
| 485 | var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); | ||
| 486 | |||
| 487 | if (slotCount == 0) | ||
| 488 | { | ||
| 489 | throw new ArgumentException("Invalid SfxCA.dll file."); | ||
| 490 | } | ||
| 491 | |||
| 492 | if (entryPoints.Count > slotCount) | ||
| 493 | { | ||
| 494 | throw new ArgumentException(string.Format( | ||
| 495 | "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + | ||
| 496 | "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", | ||
| 497 | entryPoints.Count, slotCount)); | ||
| 498 | } | ||
| 499 | |||
| 500 | var slotSort = new string[slotCount]; | ||
| 501 | for (var i = 0; i < slotCount - entryPoints.Count; i++) | ||
| 502 | { | ||
| 503 | slotSort[i] = string.Empty; | ||
| 504 | } | ||
| 505 | |||
| 506 | entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); | ||
| 507 | Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); | ||
| 508 | |||
| 509 | for (var i = 0; ; i++) | ||
| 510 | { | ||
| 511 | var templateName = string.Format(ENTRYPOINT_FORMAT, i); | ||
| 512 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
| 513 | var templateUniBytes = Encoding.Unicode.GetBytes(templateName); | ||
| 514 | |||
| 515 | var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); | ||
| 516 | if (nameOffset < 0) | ||
| 517 | { | ||
| 518 | break; | ||
| 519 | } | ||
| 520 | |||
| 521 | var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); | ||
| 522 | if (pathOffset < 0) | ||
| 523 | { | ||
| 524 | break; | ||
| 525 | } | ||
| 526 | |||
| 527 | var entryPointName = slotSort[i]; | ||
| 528 | var entryPointPath = entryPointName.Length > 0 ? | ||
| 529 | entryPoints[entryPointName] : string.Empty; | ||
| 530 | |||
| 531 | if (entryPointName.Length > MAX_ENTRYPOINT_NAME) | ||
| 532 | { | ||
| 533 | throw new ArgumentException(string.Format( | ||
| 534 | "Entry point name exceeds limit of {0} characters: {1}", | ||
| 535 | MAX_ENTRYPOINT_NAME, | ||
| 536 | entryPointName)); | ||
| 537 | } | ||
| 538 | |||
| 539 | if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) | ||
| 540 | { | ||
| 541 | throw new ArgumentException(string.Format( | ||
| 542 | "Entry point path exceeds limit of {0} characters: {1}", | ||
| 543 | MAX_ENTRYPOINT_PATH, | ||
| 544 | entryPointPath)); | ||
| 545 | } | ||
| 546 | |||
| 547 | var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); | ||
| 548 | var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); | ||
| 549 | |||
| 550 | MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); | ||
| 551 | MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); | ||
| 552 | } | ||
| 553 | |||
| 554 | if (entryPoints.Count == 0 && uiClass != null) | ||
| 555 | { | ||
| 556 | // Remove the zzz prefix from exported EmbeddedUI entry-points. | ||
| 557 | foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) | ||
| 558 | { | ||
| 559 | var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); | ||
| 560 | |||
| 561 | var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); | ||
| 562 | if (exportOffset < 0) | ||
| 563 | { | ||
| 564 | throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); | ||
| 565 | } | ||
| 566 | |||
| 567 | var replaceNameBytes = Encoding.ASCII.GetBytes(export); | ||
| 568 | MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); | ||
| 569 | } | ||
| 570 | |||
| 571 | if (uiClass.Length > MAX_ENTRYPOINT_PATH) | ||
| 572 | { | ||
| 573 | throw new ArgumentException(string.Format( | ||
| 574 | "UI class full name exceeds limit of {0} characters: {1}", | ||
| 575 | MAX_ENTRYPOINT_PATH, | ||
| 576 | uiClass)); | ||
| 577 | } | ||
| 578 | |||
| 579 | var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); | ||
| 580 | var replaceBytes = Encoding.Unicode.GetBytes(uiClass); | ||
| 581 | |||
| 582 | // Fill in the embedded UI implementor class so the proxy knows which one to load. | ||
| 583 | var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); | ||
| 584 | if (replaceOffset >= 0) | ||
| 585 | { | ||
| 586 | MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); | ||
| 587 | } | ||
| 588 | } | ||
| 589 | |||
| 590 | outputStream.Write(fileBytes, 0, fileBytes.Length); | ||
| 591 | } | ||
| 592 | |||
| 593 | /// <summary> | ||
| 594 | /// Searches for a sub-array of bytes within a larger array of bytes. | ||
| 595 | /// </summary> | ||
| 596 | private static int FindBytes(byte[] source, byte[] find) | ||
| 597 | { | ||
| 598 | for (var i = 0; i < source.Length; i++) | ||
| 599 | { | ||
| 600 | int j; | ||
| 601 | for (j = 0; j < find.Length; j++) | ||
| 602 | { | ||
| 603 | if (source[i + j] != find[j]) | ||
| 604 | { | ||
| 605 | break; | ||
| 606 | } | ||
| 607 | } | ||
| 608 | |||
| 609 | if (j == find.Length) | ||
| 610 | { | ||
| 611 | return i; | ||
| 612 | } | ||
| 613 | } | ||
| 614 | |||
| 615 | return -1; | ||
| 616 | } | ||
| 617 | |||
| 618 | /// <summary> | ||
| 619 | /// Replaces a range of bytes with new bytes, padding any extra part | ||
| 620 | /// of the range with zeroes. | ||
| 621 | /// </summary> | ||
| 622 | private static void ReplaceBytes( | ||
| 623 | byte[] source, int offset, int length, byte[] replace) | ||
| 624 | { | ||
| 625 | for (var i = 0; i < length; i++) | ||
| 626 | { | ||
| 627 | if (i < replace.Length) | ||
| 628 | { | ||
| 629 | source[offset + i] = replace[i]; | ||
| 630 | } | ||
| 631 | else | ||
| 632 | { | ||
| 633 | source[offset + i] = 0; | ||
| 634 | } | ||
| 635 | } | ||
| 636 | } | ||
| 637 | |||
| 638 | /// <summary> | ||
| 639 | /// Print the name of one file as it is being packed into the cab. | ||
| 640 | /// </summary> | ||
| 641 | private static void PackProgress(object source, ArchiveProgressEventArgs e) | ||
| 642 | { | ||
| 643 | if (e.ProgressType == ArchiveProgressType.StartFile && log != null) | ||
| 644 | { | ||
| 645 | log.WriteLine(" {0}", e.CurrentFileName); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | /// <summary> | ||
| 650 | /// Gets a mapping from filenames as they will be in the cab to filenames | ||
| 651 | /// as they are currently on disk. | ||
| 652 | /// </summary> | ||
| 653 | /// <remarks> | ||
| 654 | /// By default, all files will be placed in the root of the cab. But inputs may | ||
| 655 | /// optionally include an alternate inside-cab file path before an equals sign. | ||
| 656 | /// </remarks> | ||
| 657 | private static IDictionary<string, string> GetPackFileMap(IList<string> inputs) | ||
| 658 | { | ||
| 659 | var fileMap = new Dictionary<string, string>(); | ||
| 660 | foreach (var inputFile in inputs) | ||
| 661 | { | ||
| 662 | if (inputFile.IndexOf('=') > 0) | ||
| 663 | { | ||
| 664 | var parse = inputFile.Split('='); | ||
| 665 | if (!fileMap.ContainsKey(parse[0])) | ||
| 666 | { | ||
| 667 | fileMap.Add(parse[0], parse[1]); | ||
| 668 | } | ||
| 669 | } | ||
| 670 | else | ||
| 671 | { | ||
| 672 | var fileName = Path.GetFileName(inputFile); | ||
| 673 | if (!fileMap.ContainsKey(fileName)) | ||
| 674 | { | ||
| 675 | fileMap.Add(fileName, inputFile); | ||
| 676 | } | ||
| 677 | } | ||
| 678 | } | ||
| 679 | return fileMap; | ||
| 680 | } | ||
| 681 | |||
| 682 | /// <summary> | ||
| 683 | /// Packs the input files into a cab that is appended to the | ||
| 684 | /// output SfxCA.dll. | ||
| 685 | /// </summary> | ||
| 686 | private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap) | ||
| 687 | { | ||
| 688 | log.WriteLine("Packaging files"); | ||
| 689 | |||
| 690 | var cabInfo = new CabInfo(outputFile); | ||
| 691 | cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); | ||
| 692 | } | ||
| 693 | |||
| 694 | /// <summary> | ||
| 695 | /// Copies the version resource information from the CA module to | ||
| 696 | /// the CA package. This gives the package the file version and | ||
| 697 | /// description of the CA module, instead of the version and | ||
| 698 | /// description of SfxCA.dll. | ||
| 699 | /// </summary> | ||
| 700 | private static void CopyVersionResource(string sourceFile, string destFile) | ||
| 701 | { | ||
| 702 | log.WriteLine("Copying file version info from {0} to {1}", | ||
| 703 | sourceFile, destFile); | ||
| 704 | |||
| 705 | var rc = new ResourceCollection(); | ||
| 706 | rc.Find(sourceFile, ResourceType.Version); | ||
| 707 | rc.Load(sourceFile); | ||
| 708 | rc.Save(destFile); | ||
| 709 | } | ||
| 710 | } | ||
| 711 | } | ||
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj new file mode 100644 index 00000000..c6982532 --- /dev/null +++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netcoreapp3.1;net461</TargetFrameworks> | ||
| 7 | <OutputType>Exe</OutputType> | ||
| 8 | <RootNamespace>WixToolset.Dtf.Tools.MakeSfxCA</RootNamespace> | ||
| 9 | <AssemblyName>MakeSfxCA</AssemblyName> | ||
| 10 | <DebugType>embedded</DebugType> | ||
| 11 | <AppConfig>app.config</AppConfig> | ||
| 12 | <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest> | ||
| 13 | <RollForward>Major</RollForward> | ||
| 14 | <RuntimeIdentifier>win-x86</RuntimeIdentifier> | ||
| 15 | </PropertyGroup> | ||
| 16 | |||
| 17 | <ItemGroup> | ||
| 18 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
| 19 | </ItemGroup> | ||
| 20 | |||
| 21 | <ItemGroup> | ||
| 22 | <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" /> | ||
| 23 | <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" /> | ||
| 24 | <ProjectReference Include="..\..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" /> | ||
| 25 | </ItemGroup> | ||
| 26 | |||
| 27 | <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | ||
| 28 | <PropertyGroup> | ||
| 29 | <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | ||
| 30 | </PropertyGroup> | ||
| 31 | <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" /> | ||
| 32 | </Target> | ||
| 33 | </Project> | ||
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest new file mode 100644 index 00000000..49b074e0 --- /dev/null +++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | |||
| 5 | <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
| 6 | <assemblyIdentity name="WixToolset.Dtf.Tools.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/> | ||
| 7 | <description>WiX Toolset Compiler</description> | ||
| 8 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 9 | <security> | ||
| 10 | <requestedPrivileges> | ||
| 11 | <requestedExecutionLevel level="asInvoker" uiAccess="false"/> | ||
| 12 | </requestedPrivileges> | ||
| 13 | </security> | ||
| 14 | </trustInfo> | ||
| 15 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 16 | <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> | ||
| 17 | <ws2:longPathAware>true</ws2:longPathAware> | ||
| 18 | </windowsSettings> | ||
| 19 | </application> | ||
| 20 | </assembly> | ||
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/app.config b/src/samples/Dtf/Tools/MakeSfxCA/app.config new file mode 100644 index 00000000..65d3d6c3 --- /dev/null +++ b/src/samples/Dtf/Tools/MakeSfxCA/app.config | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | |||
| 5 | <configuration> | ||
| 6 | <runtime> | ||
| 7 | <loadFromRemoteSources enabled="true"/> | ||
| 8 | <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" /> | ||
| 9 | </runtime> | ||
| 10 | </configuration> | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp new file mode 100644 index 00000000..1988fb2a --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp | |||
| @@ -0,0 +1,262 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | |||
| 5 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); | ||
| 6 | |||
| 7 | //--------------------------------------------------------------------- | ||
| 8 | // CLR HOSTING | ||
| 9 | //--------------------------------------------------------------------- | ||
| 10 | |||
| 11 | /// <summary> | ||
| 12 | /// Binds to the CLR after determining the appropriate version. | ||
| 13 | /// </summary> | ||
| 14 | /// <param name="hSession">Handle to the installer session, | ||
| 15 | /// used just for logging.</param> | ||
| 16 | /// <param name="version">Specific version of the CLR to load. | ||
| 17 | /// If null, then the config file and/or primary assembly are | ||
| 18 | /// used to determine the version.</param> | ||
| 19 | /// <param name="szConfigFile">XML .config file which may contain | ||
| 20 | /// a startup section to direct which version of the CLR to use. | ||
| 21 | /// May be NULL.</param> | ||
| 22 | /// <param name="szPrimaryAssembly">Assembly to be used to determine | ||
| 23 | /// the version of the CLR in the absence of other configuration. | ||
| 24 | /// May be NULL.</param> | ||
| 25 | /// <param name="ppHost">Returned runtime host interface.</param> | ||
| 26 | /// <returns>True if the CLR was loaded successfully, false if | ||
| 27 | /// there was some error.</returns> | ||
| 28 | /// <remarks> | ||
| 29 | /// If szPrimaryAssembly is NULL and szConfigFile is also NULL or | ||
| 30 | /// does not contain any version configuration, the CLR will not be loaded. | ||
| 31 | /// </remarks> | ||
| 32 | bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, | ||
| 33 | const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost) | ||
| 34 | { | ||
| 35 | typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, | ||
| 36 | LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, | ||
| 37 | LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, | ||
| 38 | LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); | ||
| 39 | typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, | ||
| 40 | DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv); | ||
| 41 | |||
| 42 | HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll"); | ||
| 43 | if (hmodMscoree == NULL) | ||
| 44 | { | ||
| 45 | Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action " | ||
| 46 | L"requires the .NET Framework to be installed.", GetLastError()); | ||
| 47 | return false; | ||
| 48 | } | ||
| 49 | PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo) | ||
| 50 | GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo"); | ||
| 51 | PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx) | ||
| 52 | GetProcAddress(hmodMscoree, "CorBindToRuntimeEx"); | ||
| 53 | if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL) | ||
| 54 | { | ||
| 55 | Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action " | ||
| 56 | L"requires the .NET Framework to be installed.", GetLastError()); | ||
| 57 | FreeLibrary(hmodMscoree); | ||
| 58 | return false; | ||
| 59 | } | ||
| 60 | |||
| 61 | wchar_t szClrVersion[20]; | ||
| 62 | HRESULT hr; | ||
| 63 | |||
| 64 | if (szVersion != NULL && szVersion[0] != L'\0') | ||
| 65 | { | ||
| 66 | wcsncpy_s(szClrVersion, 20, szVersion, 20); | ||
| 67 | } | ||
| 68 | else | ||
| 69 | { | ||
| 70 | wchar_t szVersionDir[MAX_PATH]; | ||
| 71 | hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL, | ||
| 72 | szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL); | ||
| 73 | if (FAILED(hr)) | ||
| 74 | { | ||
| 75 | Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr); | ||
| 76 | Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or " | ||
| 77 | L"that there is a matching supportedRuntime element in CustomAction.config. " | ||
| 78 | L"If you are binding to .NET 4 or greater add " | ||
| 79 | L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element."); | ||
| 80 | FreeLibrary(hmodMscoree); | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | Log(hSession, L"Binding to CLR version %s", szClrVersion); | ||
| 86 | |||
| 87 | ICorRuntimeHost* pHost; | ||
| 88 | hr = pCorBindToRuntimeEx(szClrVersion, NULL, | ||
| 89 | STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, | ||
| 90 | CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost); | ||
| 91 | if (FAILED(hr)) | ||
| 92 | { | ||
| 93 | Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr); | ||
| 94 | FreeLibrary(hmodMscoree); | ||
| 95 | return false; | ||
| 96 | } | ||
| 97 | hr = pHost->Start(); | ||
| 98 | if (FAILED(hr)) | ||
| 99 | { | ||
| 100 | Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr); | ||
| 101 | pHost->Release(); | ||
| 102 | FreeLibrary(hmodMscoree); | ||
| 103 | return false; | ||
| 104 | } | ||
| 105 | *ppHost = pHost; | ||
| 106 | FreeLibrary(hmodMscoree); | ||
| 107 | return true; | ||
| 108 | } | ||
| 109 | |||
| 110 | /// <summary> | ||
| 111 | /// Creates a new CLR application domain. | ||
| 112 | /// </summary> | ||
| 113 | /// <param name="hSession">Handle to the installer session, | ||
| 114 | /// used just for logging</param> | ||
| 115 | /// <param name="pHost">Interface to the runtime host where the | ||
| 116 | /// app domain will be created.</param> | ||
| 117 | /// <param name="szName">Name of the app domain to create.</param> | ||
| 118 | /// <param name="szAppBase">Application base directory path, where | ||
| 119 | /// the app domain will look first to load its assemblies.</param> | ||
| 120 | /// <param name="szConfigFile">Optional XML .config file containing any | ||
| 121 | /// configuration for thae app domain.</param> | ||
| 122 | /// <param name="ppAppDomain">Returned app domain interface.</param> | ||
| 123 | /// <returns>True if the app domain was created successfully, false if | ||
| 124 | /// there was some error.</returns> | ||
| 125 | bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, | ||
| 126 | const wchar_t* szName, const wchar_t* szAppBase, | ||
| 127 | const wchar_t* szConfigFile, _AppDomain** ppAppDomain) | ||
| 128 | { | ||
| 129 | IUnknown* punkAppDomainSetup = NULL; | ||
| 130 | IAppDomainSetup* pAppDomainSetup = NULL; | ||
| 131 | HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup); | ||
| 132 | if (SUCCEEDED(hr)) | ||
| 133 | { | ||
| 134 | hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup); | ||
| 135 | punkAppDomainSetup->Release(); | ||
| 136 | } | ||
| 137 | if (FAILED(hr)) | ||
| 138 | { | ||
| 139 | Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr); | ||
| 140 | return false; | ||
| 141 | } | ||
| 142 | |||
| 143 | const wchar_t* szUrlPrefix = L"file:///"; | ||
| 144 | size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase); | ||
| 145 | wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t)); | ||
| 146 | if (szApplicationBase == NULL) hr = E_OUTOFMEMORY; | ||
| 147 | else | ||
| 148 | { | ||
| 149 | StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix); | ||
| 150 | StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase); | ||
| 151 | BSTR bstrApplicationBase = SysAllocString(szApplicationBase); | ||
| 152 | if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY; | ||
| 153 | else | ||
| 154 | { | ||
| 155 | hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase); | ||
| 156 | SysFreeString(bstrApplicationBase); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | if (SUCCEEDED(hr) && szConfigFile != NULL) | ||
| 161 | { | ||
| 162 | BSTR bstrConfigFile = SysAllocString(szConfigFile); | ||
| 163 | if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY; | ||
| 164 | else | ||
| 165 | { | ||
| 166 | hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile); | ||
| 167 | SysFreeString(bstrConfigFile); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | if (FAILED(hr)) | ||
| 172 | { | ||
| 173 | Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr); | ||
| 174 | pAppDomainSetup->Release(); | ||
| 175 | return false; | ||
| 176 | } | ||
| 177 | |||
| 178 | IUnknown* punkAppDomain; | ||
| 179 | hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain); | ||
| 180 | pAppDomainSetup->Release(); | ||
| 181 | if (SUCCEEDED(hr)) | ||
| 182 | { | ||
| 183 | hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain); | ||
| 184 | punkAppDomain->Release(); | ||
| 185 | } | ||
| 186 | |||
| 187 | if (FAILED(hr)) | ||
| 188 | { | ||
| 189 | Log(hSession, L"Failed to create app domain. Error code 0x%X", hr); | ||
| 190 | return false; | ||
| 191 | } | ||
| 192 | |||
| 193 | return true; | ||
| 194 | } | ||
| 195 | |||
| 196 | /// <summary> | ||
| 197 | /// Locates a specific method in a specific class and assembly. | ||
| 198 | /// </summary> | ||
| 199 | /// <param name="hSession">Handle to the installer session, | ||
| 200 | /// used just for logging</param> | ||
| 201 | /// <param name="pAppDomain">Application domain in which to | ||
| 202 | /// load assemblies.</param> | ||
| 203 | /// <param name="szAssembly">Display name of the assembly | ||
| 204 | /// containing the method.</param> | ||
| 205 | /// <param name="szClass">Fully-qualified name of the class | ||
| 206 | /// containing the method.</param> | ||
| 207 | /// <param name="szMethod">Name of the method.</param> | ||
| 208 | /// <param name="ppMethod">Returned method interface.</param> | ||
| 209 | /// <returns>True if the method was located, otherwise false.</returns> | ||
| 210 | /// <remarks>Only public static methods are searched. Method | ||
| 211 | /// parameter types are not considered; if there are multiple | ||
| 212 | /// matching methods with different parameters, an error results.</remarks> | ||
| 213 | bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
| 214 | const wchar_t* szAssembly, const wchar_t* szClass, | ||
| 215 | const wchar_t* szMethod, _MethodInfo** ppMethod) | ||
| 216 | { | ||
| 217 | HRESULT hr; | ||
| 218 | _Assembly* pAssembly = NULL; | ||
| 219 | BSTR bstrAssemblyName = SysAllocString(szAssembly); | ||
| 220 | if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY; | ||
| 221 | else | ||
| 222 | { | ||
| 223 | hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly); | ||
| 224 | SysFreeString(bstrAssemblyName); | ||
| 225 | } | ||
| 226 | if (FAILED(hr)) | ||
| 227 | { | ||
| 228 | Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr); | ||
| 229 | return false; | ||
| 230 | } | ||
| 231 | |||
| 232 | _Type* pType = NULL; | ||
| 233 | BSTR bstrClass = SysAllocString(szClass); | ||
| 234 | if (bstrClass == NULL) hr = E_OUTOFMEMORY; | ||
| 235 | else | ||
| 236 | { | ||
| 237 | hr = pAssembly->GetType_2(bstrClass, &pType); | ||
| 238 | SysFreeString(bstrClass); | ||
| 239 | } | ||
| 240 | pAssembly->Release(); | ||
| 241 | if (FAILED(hr) || pType == NULL) | ||
| 242 | { | ||
| 243 | Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr); | ||
| 244 | return false; | ||
| 245 | } | ||
| 246 | |||
| 247 | BSTR bstrMethod = SysAllocString(szMethod); | ||
| 248 | if (bstrMethod == NULL) hr = E_OUTOFMEMORY; | ||
| 249 | else | ||
| 250 | { | ||
| 251 | hr = pType->GetMethod_2(bstrMethod, | ||
| 252 | (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod); | ||
| 253 | SysFreeString(bstrMethod); | ||
| 254 | } | ||
| 255 | pType->Release(); | ||
| 256 | if (FAILED(hr) || *ppMethod == NULL) | ||
| 257 | { | ||
| 258 | Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr); | ||
| 259 | return false; | ||
| 260 | } | ||
| 261 | return true; | ||
| 262 | } | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp new file mode 100644 index 00000000..a49cdeec --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp | |||
| @@ -0,0 +1,281 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | #include "SfxUtil.h" | ||
| 5 | |||
| 6 | // Globals for keeping track of things across UI messages. | ||
| 7 | static const wchar_t* g_szWorkingDir; | ||
| 8 | static ICorRuntimeHost* g_pClrHost; | ||
| 9 | static _AppDomain* g_pAppDomain; | ||
| 10 | static _MethodInfo* g_pProcessMessageMethod; | ||
| 11 | static _MethodInfo* g_pShutdownMethod; | ||
| 12 | |||
| 13 | // Reserve extra space for strings to be replaced at build time. | ||
| 14 | #define NULLSPACE \ | ||
| 15 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 16 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 17 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 18 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
| 19 | |||
| 20 | // Prototypes for local functions. | ||
| 21 | // See the function definitions for comments. | ||
| 22 | |||
| 23 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, | ||
| 24 | const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// First entry-point for the UI DLL when loaded and called by MSI. | ||
| 28 | /// Extracts the payload, hosts the CLR, and invokes the managed | ||
| 29 | /// initialize method. | ||
| 30 | /// </summary> | ||
| 31 | /// <param name="hSession">Handle to the installer session, | ||
| 32 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
| 33 | /// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table | ||
| 34 | /// have been extracted, and where additional payload from this package will be extracted.</param> | ||
| 35 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
| 36 | /// the managed initialize method.</param> | ||
| 37 | extern "C" | ||
| 38 | UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) | ||
| 39 | { | ||
| 40 | // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. | ||
| 41 | UINT uiResult = INSTALLUILEVEL_BASIC; | ||
| 42 | |||
| 43 | const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; | ||
| 44 | |||
| 45 | g_szWorkingDir = szResourcePath; | ||
| 46 | |||
| 47 | wchar_t szModule[MAX_PATH]; | ||
| 48 | DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); | ||
| 49 | if (cchCopied == 0) | ||
| 50 | { | ||
| 51 | Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); | ||
| 52 | return uiResult; | ||
| 53 | } | ||
| 54 | else if (cchCopied == MAX_PATH - 1) | ||
| 55 | { | ||
| 56 | Log(hSession, L"Failed to get module path -- path is too long."); | ||
| 57 | return uiResult; | ||
| 58 | } | ||
| 59 | |||
| 60 | Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); | ||
| 61 | int err = ExtractCabinet(szModule, g_szWorkingDir); | ||
| 62 | if (err != 0) | ||
| 63 | { | ||
| 64 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); | ||
| 65 | Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " | ||
| 66 | L"any file contained in the embedded UI package."); | ||
| 67 | return uiResult; | ||
| 68 | } | ||
| 69 | |||
| 70 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
| 71 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); | ||
| 72 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); | ||
| 73 | |||
| 74 | const wchar_t* szConfigFile = szConfigFilePath; | ||
| 75 | if (!PathFileExists(szConfigFilePath)) | ||
| 76 | { | ||
| 77 | szConfigFile = NULL; | ||
| 78 | } | ||
| 79 | |||
| 80 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
| 81 | StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); | ||
| 82 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
| 83 | |||
| 84 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) | ||
| 85 | { | ||
| 86 | if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, | ||
| 87 | szConfigFile, &g_pAppDomain)) | ||
| 88 | { | ||
| 89 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
| 90 | const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; | ||
| 91 | const wchar_t* szInitMethod = L"Initialize"; | ||
| 92 | const wchar_t* szProcessMessageMethod = L"ProcessMessage"; | ||
| 93 | const wchar_t* szShutdownMethod = L"Shutdown"; | ||
| 94 | |||
| 95 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
| 96 | szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && | ||
| 97 | GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
| 98 | szProxyClass, szShutdownMethod, &g_pShutdownMethod)) | ||
| 99 | { | ||
| 100 | _MethodInfo* pInitMethod; | ||
| 101 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
| 102 | szProxyClass, szInitMethod, &pInitMethod)) | ||
| 103 | { | ||
| 104 | bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); | ||
| 105 | pInitMethod->Release(); | ||
| 106 | if (invokeSuccess) | ||
| 107 | { | ||
| 108 | if (uiResult == 0) | ||
| 109 | { | ||
| 110 | return ERROR_SUCCESS; | ||
| 111 | } | ||
| 112 | else if (uiResult == ERROR_INSTALL_USEREXIT) | ||
| 113 | { | ||
| 114 | // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. | ||
| 115 | // So return success here and then IDCANCEL on the next progress message. | ||
| 116 | uiResult = 0; | ||
| 117 | *pdwInternalUILevel = INSTALLUILEVEL_NONE; | ||
| 118 | Log(hSession, L"Initialization canceled by user."); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | g_pProcessMessageMethod->Release(); | ||
| 125 | g_pProcessMessageMethod = NULL; | ||
| 126 | g_pShutdownMethod->Release(); | ||
| 127 | g_pShutdownMethod = NULL; | ||
| 128 | |||
| 129 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
| 130 | g_pAppDomain->Release(); | ||
| 131 | g_pAppDomain = NULL; | ||
| 132 | } | ||
| 133 | g_pClrHost->Stop(); | ||
| 134 | g_pClrHost->Release(); | ||
| 135 | g_pClrHost = NULL; | ||
| 136 | } | ||
| 137 | |||
| 138 | return uiResult; | ||
| 139 | } | ||
| 140 | |||
| 141 | /// <summary> | ||
| 142 | /// Entry-point for UI progress messages received from the MSI engine during an active installation. | ||
| 143 | /// Forwards the progress messages to the managed handler method and returns its result. | ||
| 144 | /// </summary> | ||
| 145 | extern "C" | ||
| 146 | INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) | ||
| 147 | { | ||
| 148 | if (g_pProcessMessageMethod == NULL) | ||
| 149 | { | ||
| 150 | // Initialization was canceled. | ||
| 151 | return IDCANCEL; | ||
| 152 | } | ||
| 153 | |||
| 154 | VARIANT vResult; | ||
| 155 | VariantInit(&vResult); | ||
| 156 | |||
| 157 | VARIANT vNull; | ||
| 158 | vNull.vt = VT_EMPTY; | ||
| 159 | |||
| 160 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); | ||
| 161 | VARIANT vMessageType; | ||
| 162 | vMessageType.vt = VT_I4; | ||
| 163 | vMessageType.lVal = (LONG) uiMessageType; | ||
| 164 | LONG index = 0; | ||
| 165 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); | ||
| 166 | if (FAILED(hr)) goto LExit; | ||
| 167 | VARIANT vRecord; | ||
| 168 | vRecord.vt = VT_I4; | ||
| 169 | vRecord.lVal = (LONG) hRecord; | ||
| 170 | index = 1; | ||
| 171 | hr = SafeArrayPutElement(saArgs, &index, &vRecord); | ||
| 172 | if (FAILED(hr)) goto LExit; | ||
| 173 | |||
| 174 | hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); | ||
| 175 | |||
| 176 | LExit: | ||
| 177 | SafeArrayDestroy(saArgs); | ||
| 178 | if (SUCCEEDED(hr)) | ||
| 179 | { | ||
| 180 | return vResult.intVal; | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | return -1; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | /// <summary> | ||
| 189 | /// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. | ||
| 190 | /// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. | ||
| 191 | /// </summary> | ||
| 192 | extern "C" | ||
| 193 | DWORD __stdcall ShutdownEmbeddedUI() | ||
| 194 | { | ||
| 195 | if (g_pShutdownMethod != NULL) | ||
| 196 | { | ||
| 197 | VARIANT vNull; | ||
| 198 | vNull.vt = VT_EMPTY; | ||
| 199 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); | ||
| 200 | g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); | ||
| 201 | SafeArrayDestroy(saArgs); | ||
| 202 | |||
| 203 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
| 204 | g_pAppDomain->Release(); | ||
| 205 | g_pClrHost->Stop(); | ||
| 206 | g_pClrHost->Release(); | ||
| 207 | } | ||
| 208 | |||
| 209 | return 0; | ||
| 210 | } | ||
| 211 | |||
| 212 | /// <summary> | ||
| 213 | /// Loads and invokes the managed portion of the proxy. | ||
| 214 | /// </summary> | ||
| 215 | /// <param name="pInitMethod">Managed initialize method to be invoked.</param> | ||
| 216 | /// <param name="hSession">Handle to the installer session, | ||
| 217 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
| 218 | /// <param name="szClassName">Name of the UI class to be loaded. | ||
| 219 | /// This must be of the form: AssemblyName!Namespace.Class</param> | ||
| 220 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
| 221 | /// the managed initialize method.</param> | ||
| 222 | /// <param name="puiResult">Return value of the invoked initialize method.</param> | ||
| 223 | /// <returns>True if the managed proxy was invoked successfully, or an | ||
| 224 | /// error code if there was some error. Note the initialize method itself may | ||
| 225 | /// return an error via puiResult while this method still returns true | ||
| 226 | /// since the invocation was successful.</returns> | ||
| 227 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) | ||
| 228 | { | ||
| 229 | VARIANT vResult; | ||
| 230 | VariantInit(&vResult); | ||
| 231 | |||
| 232 | VARIANT vNull; | ||
| 233 | vNull.vt = VT_EMPTY; | ||
| 234 | |||
| 235 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
| 236 | VARIANT vSessionHandle; | ||
| 237 | vSessionHandle.vt = VT_I4; | ||
| 238 | vSessionHandle.lVal = (LONG) hSession; | ||
| 239 | LONG index = 0; | ||
| 240 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
| 241 | if (FAILED(hr)) goto LExit; | ||
| 242 | VARIANT vEntryPoint; | ||
| 243 | vEntryPoint.vt = VT_BSTR; | ||
| 244 | vEntryPoint.bstrVal = SysAllocString(szClassName); | ||
| 245 | if (vEntryPoint.bstrVal == NULL) | ||
| 246 | { | ||
| 247 | hr = E_OUTOFMEMORY; | ||
| 248 | goto LExit; | ||
| 249 | } | ||
| 250 | index = 1; | ||
| 251 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
| 252 | if (FAILED(hr)) goto LExit; | ||
| 253 | VARIANT vUILevel; | ||
| 254 | vUILevel.vt = VT_I4; | ||
| 255 | vUILevel.ulVal = *pdwInternalUILevel; | ||
| 256 | index = 2; | ||
| 257 | hr = SafeArrayPutElement(saArgs, &index, &vUILevel); | ||
| 258 | if (FAILED(hr)) goto LExit; | ||
| 259 | |||
| 260 | hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); | ||
| 261 | |||
| 262 | LExit: | ||
| 263 | SafeArrayDestroy(saArgs); | ||
| 264 | if (SUCCEEDED(hr)) | ||
| 265 | { | ||
| 266 | *puiResult = (UINT) vResult.lVal; | ||
| 267 | if ((*puiResult & 0xFFFF) == 0) | ||
| 268 | { | ||
| 269 | // Due to interop limitations, the successful resulting UILevel is returned | ||
| 270 | // as the high-word of the return value instead of via a ref parameter. | ||
| 271 | *pdwInternalUILevel = *puiResult >> 16; | ||
| 272 | *puiResult = 0; | ||
| 273 | } | ||
| 274 | return true; | ||
| 275 | } | ||
| 276 | else | ||
| 277 | { | ||
| 278 | Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); | ||
| 279 | return false; | ||
| 280 | } | ||
| 281 | } | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.def b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def new file mode 100644 index 00000000..dd28b920 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def | |||
| @@ -0,0 +1,140 @@ | |||
| 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 | |||
| 4 | LIBRARY "SfxCA" | ||
| 5 | |||
| 6 | EXPORTS | ||
| 7 | |||
| 8 | CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 | ||
| 9 | CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 | ||
| 10 | CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 | ||
| 11 | CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 | ||
| 12 | CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 | ||
| 13 | CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 | ||
| 14 | CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 | ||
| 15 | CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 | ||
| 16 | CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 | ||
| 17 | CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 | ||
| 18 | CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 | ||
| 19 | CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 | ||
| 20 | CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 | ||
| 21 | CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 | ||
| 22 | CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 | ||
| 23 | CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 | ||
| 24 | CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 | ||
| 25 | CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 | ||
| 26 | CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 | ||
| 27 | CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 | ||
| 28 | CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 | ||
| 29 | CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 | ||
| 30 | CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 | ||
| 31 | CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 | ||
| 32 | CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 | ||
| 33 | CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 | ||
| 34 | CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 | ||
| 35 | CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 | ||
| 36 | CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 | ||
| 37 | CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 | ||
| 38 | CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 | ||
| 39 | CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 | ||
| 40 | CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 | ||
| 41 | CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 | ||
| 42 | CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 | ||
| 43 | CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 | ||
| 44 | CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 | ||
| 45 | CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 | ||
| 46 | CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 | ||
| 47 | CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 | ||
| 48 | CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 | ||
| 49 | CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 | ||
| 50 | CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 | ||
| 51 | CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 | ||
| 52 | CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 | ||
| 53 | CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 | ||
| 54 | CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 | ||
| 55 | CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 | ||
| 56 | CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 | ||
| 57 | CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 | ||
| 58 | CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 | ||
| 59 | CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 | ||
| 60 | CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 | ||
| 61 | CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 | ||
| 62 | CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 | ||
| 63 | CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 | ||
| 64 | CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 | ||
| 65 | CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 | ||
| 66 | CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 | ||
| 67 | CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 | ||
| 68 | CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 | ||
| 69 | CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 | ||
| 70 | CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 | ||
| 71 | CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 | ||
| 72 | CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 | ||
| 73 | CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 | ||
| 74 | CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 | ||
| 75 | CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 | ||
| 76 | CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 | ||
| 77 | CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 | ||
| 78 | CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 | ||
| 79 | CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 | ||
| 80 | CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 | ||
| 81 | CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 | ||
| 82 | CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 | ||
| 83 | CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 | ||
| 84 | CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 | ||
| 85 | CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 | ||
| 86 | CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 | ||
| 87 | CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 | ||
| 88 | CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 | ||
| 89 | CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 | ||
| 90 | CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 | ||
| 91 | CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 | ||
| 92 | CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 | ||
| 93 | CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 | ||
| 94 | CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 | ||
| 95 | CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 | ||
| 96 | CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 | ||
| 97 | CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 | ||
| 98 | CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 | ||
| 99 | CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 | ||
| 100 | CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 | ||
| 101 | CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 | ||
| 102 | CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 | ||
| 103 | CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 | ||
| 104 | CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 | ||
| 105 | CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 | ||
| 106 | CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 | ||
| 107 | CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 | ||
| 108 | CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 | ||
| 109 | CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 | ||
| 110 | CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 | ||
| 111 | CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 | ||
| 112 | CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 | ||
| 113 | CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 | ||
| 114 | CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 | ||
| 115 | CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 | ||
| 116 | CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 | ||
| 117 | CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 | ||
| 118 | CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 | ||
| 119 | CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 | ||
| 120 | CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 | ||
| 121 | CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 | ||
| 122 | CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 | ||
| 123 | CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 | ||
| 124 | CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 | ||
| 125 | CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 | ||
| 126 | CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 | ||
| 127 | CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 | ||
| 128 | CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 | ||
| 129 | CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 | ||
| 130 | CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 | ||
| 131 | CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 | ||
| 132 | CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 | ||
| 133 | CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 | ||
| 134 | CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 | ||
| 135 | CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 | ||
| 136 | |||
| 137 | zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc | ||
| 138 | zzzInitializeEmbeddedUI=InitializeEmbeddedUI | ||
| 139 | zzzEmbeddedUIHandler=EmbeddedUIHandler | ||
| 140 | zzzShutdownEmbeddedUI=ShutdownEmbeddedUI | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.h b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h new file mode 100644 index 00000000..bd2fa970 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h | |||
| @@ -0,0 +1,162 @@ | |||
| 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 | int InvokeCustomAction(MSIHANDLE hSession, | ||
| 4 | const wchar_t* szWorkingDir, const wchar_t* szEntryPoint); | ||
| 5 | |||
| 6 | /// <summary> | ||
| 7 | /// Macro for defining and exporting a custom action entrypoint. | ||
| 8 | /// </summary> | ||
| 9 | /// <param name="name">Name of the entrypoint as exported from | ||
| 10 | /// the DLL.</param> | ||
| 11 | /// <param name="method">Path to the managed custom action method, | ||
| 12 | /// in the form: "AssemblyName!Namespace.Class.Method"</param> | ||
| 13 | /// <remarks> | ||
| 14 | /// To prevent the exported name from being decorated, add | ||
| 15 | /// /EXPORT:name to the linker options for every entrypoint. | ||
| 16 | /// </remarks> | ||
| 17 | #define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \ | ||
| 18 | name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); } | ||
| 19 | |||
| 20 | // TEMPLATE ENTRYPOINTS | ||
| 21 | // To be edited by the MakeSfxCA tool. | ||
| 22 | |||
| 23 | #define NULLSPACE \ | ||
| 24 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 25 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 26 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
| 27 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
| 28 | |||
| 29 | #define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \ | ||
| 30 | CustomActionEntryPoint##id##, \ | ||
| 31 | L"CustomActionEntryPoint" sid NULLSPACE) | ||
| 32 | |||
| 33 | TEMPLATE_CA_ENTRYPOINT(000,L"000"); | ||
| 34 | TEMPLATE_CA_ENTRYPOINT(001,L"001"); | ||
| 35 | TEMPLATE_CA_ENTRYPOINT(002,L"002"); | ||
| 36 | TEMPLATE_CA_ENTRYPOINT(003,L"003"); | ||
| 37 | TEMPLATE_CA_ENTRYPOINT(004,L"004"); | ||
| 38 | TEMPLATE_CA_ENTRYPOINT(005,L"005"); | ||
| 39 | TEMPLATE_CA_ENTRYPOINT(006,L"006"); | ||
| 40 | TEMPLATE_CA_ENTRYPOINT(007,L"007"); | ||
| 41 | TEMPLATE_CA_ENTRYPOINT(008,L"008"); | ||
| 42 | TEMPLATE_CA_ENTRYPOINT(009,L"009"); | ||
| 43 | TEMPLATE_CA_ENTRYPOINT(010,L"010"); | ||
| 44 | TEMPLATE_CA_ENTRYPOINT(011,L"011"); | ||
| 45 | TEMPLATE_CA_ENTRYPOINT(012,L"012"); | ||
| 46 | TEMPLATE_CA_ENTRYPOINT(013,L"013"); | ||
| 47 | TEMPLATE_CA_ENTRYPOINT(014,L"014"); | ||
| 48 | TEMPLATE_CA_ENTRYPOINT(015,L"015"); | ||
| 49 | TEMPLATE_CA_ENTRYPOINT(016,L"016"); | ||
| 50 | TEMPLATE_CA_ENTRYPOINT(017,L"017"); | ||
| 51 | TEMPLATE_CA_ENTRYPOINT(018,L"018"); | ||
| 52 | TEMPLATE_CA_ENTRYPOINT(019,L"019"); | ||
| 53 | TEMPLATE_CA_ENTRYPOINT(020,L"020"); | ||
| 54 | TEMPLATE_CA_ENTRYPOINT(021,L"021"); | ||
| 55 | TEMPLATE_CA_ENTRYPOINT(022,L"022"); | ||
| 56 | TEMPLATE_CA_ENTRYPOINT(023,L"023"); | ||
| 57 | TEMPLATE_CA_ENTRYPOINT(024,L"024"); | ||
| 58 | TEMPLATE_CA_ENTRYPOINT(025,L"025"); | ||
| 59 | TEMPLATE_CA_ENTRYPOINT(026,L"026"); | ||
| 60 | TEMPLATE_CA_ENTRYPOINT(027,L"027"); | ||
| 61 | TEMPLATE_CA_ENTRYPOINT(028,L"028"); | ||
| 62 | TEMPLATE_CA_ENTRYPOINT(029,L"029"); | ||
| 63 | TEMPLATE_CA_ENTRYPOINT(030,L"030"); | ||
| 64 | TEMPLATE_CA_ENTRYPOINT(031,L"031"); | ||
| 65 | TEMPLATE_CA_ENTRYPOINT(032,L"032"); | ||
| 66 | TEMPLATE_CA_ENTRYPOINT(033,L"033"); | ||
| 67 | TEMPLATE_CA_ENTRYPOINT(034,L"034"); | ||
| 68 | TEMPLATE_CA_ENTRYPOINT(035,L"035"); | ||
| 69 | TEMPLATE_CA_ENTRYPOINT(036,L"036"); | ||
| 70 | TEMPLATE_CA_ENTRYPOINT(037,L"037"); | ||
| 71 | TEMPLATE_CA_ENTRYPOINT(038,L"038"); | ||
| 72 | TEMPLATE_CA_ENTRYPOINT(039,L"039"); | ||
| 73 | TEMPLATE_CA_ENTRYPOINT(040,L"040"); | ||
| 74 | TEMPLATE_CA_ENTRYPOINT(041,L"041"); | ||
| 75 | TEMPLATE_CA_ENTRYPOINT(042,L"042"); | ||
| 76 | TEMPLATE_CA_ENTRYPOINT(043,L"043"); | ||
| 77 | TEMPLATE_CA_ENTRYPOINT(044,L"044"); | ||
| 78 | TEMPLATE_CA_ENTRYPOINT(045,L"045"); | ||
| 79 | TEMPLATE_CA_ENTRYPOINT(046,L"046"); | ||
| 80 | TEMPLATE_CA_ENTRYPOINT(047,L"047"); | ||
| 81 | TEMPLATE_CA_ENTRYPOINT(048,L"048"); | ||
| 82 | TEMPLATE_CA_ENTRYPOINT(049,L"049"); | ||
| 83 | TEMPLATE_CA_ENTRYPOINT(050,L"050"); | ||
| 84 | TEMPLATE_CA_ENTRYPOINT(051,L"051"); | ||
| 85 | TEMPLATE_CA_ENTRYPOINT(052,L"052"); | ||
| 86 | TEMPLATE_CA_ENTRYPOINT(053,L"053"); | ||
| 87 | TEMPLATE_CA_ENTRYPOINT(054,L"054"); | ||
| 88 | TEMPLATE_CA_ENTRYPOINT(055,L"055"); | ||
| 89 | TEMPLATE_CA_ENTRYPOINT(056,L"056"); | ||
| 90 | TEMPLATE_CA_ENTRYPOINT(057,L"057"); | ||
| 91 | TEMPLATE_CA_ENTRYPOINT(058,L"058"); | ||
| 92 | TEMPLATE_CA_ENTRYPOINT(059,L"059"); | ||
| 93 | TEMPLATE_CA_ENTRYPOINT(060,L"060"); | ||
| 94 | TEMPLATE_CA_ENTRYPOINT(061,L"061"); | ||
| 95 | TEMPLATE_CA_ENTRYPOINT(062,L"062"); | ||
| 96 | TEMPLATE_CA_ENTRYPOINT(063,L"063"); | ||
| 97 | TEMPLATE_CA_ENTRYPOINT(064,L"064"); | ||
| 98 | TEMPLATE_CA_ENTRYPOINT(065,L"065"); | ||
| 99 | TEMPLATE_CA_ENTRYPOINT(066,L"066"); | ||
| 100 | TEMPLATE_CA_ENTRYPOINT(067,L"067"); | ||
| 101 | TEMPLATE_CA_ENTRYPOINT(068,L"068"); | ||
| 102 | TEMPLATE_CA_ENTRYPOINT(069,L"069"); | ||
| 103 | TEMPLATE_CA_ENTRYPOINT(070,L"070"); | ||
| 104 | TEMPLATE_CA_ENTRYPOINT(071,L"071"); | ||
| 105 | TEMPLATE_CA_ENTRYPOINT(072,L"072"); | ||
| 106 | TEMPLATE_CA_ENTRYPOINT(073,L"073"); | ||
| 107 | TEMPLATE_CA_ENTRYPOINT(074,L"074"); | ||
| 108 | TEMPLATE_CA_ENTRYPOINT(075,L"075"); | ||
| 109 | TEMPLATE_CA_ENTRYPOINT(076,L"076"); | ||
| 110 | TEMPLATE_CA_ENTRYPOINT(077,L"077"); | ||
| 111 | TEMPLATE_CA_ENTRYPOINT(078,L"078"); | ||
| 112 | TEMPLATE_CA_ENTRYPOINT(079,L"079"); | ||
| 113 | TEMPLATE_CA_ENTRYPOINT(080,L"080"); | ||
| 114 | TEMPLATE_CA_ENTRYPOINT(081,L"081"); | ||
| 115 | TEMPLATE_CA_ENTRYPOINT(082,L"082"); | ||
| 116 | TEMPLATE_CA_ENTRYPOINT(083,L"083"); | ||
| 117 | TEMPLATE_CA_ENTRYPOINT(084,L"084"); | ||
| 118 | TEMPLATE_CA_ENTRYPOINT(085,L"085"); | ||
| 119 | TEMPLATE_CA_ENTRYPOINT(086,L"086"); | ||
| 120 | TEMPLATE_CA_ENTRYPOINT(087,L"087"); | ||
| 121 | TEMPLATE_CA_ENTRYPOINT(088,L"088"); | ||
| 122 | TEMPLATE_CA_ENTRYPOINT(089,L"089"); | ||
| 123 | TEMPLATE_CA_ENTRYPOINT(090,L"090"); | ||
| 124 | TEMPLATE_CA_ENTRYPOINT(091,L"091"); | ||
| 125 | TEMPLATE_CA_ENTRYPOINT(092,L"092"); | ||
| 126 | TEMPLATE_CA_ENTRYPOINT(093,L"093"); | ||
| 127 | TEMPLATE_CA_ENTRYPOINT(094,L"094"); | ||
| 128 | TEMPLATE_CA_ENTRYPOINT(095,L"095"); | ||
| 129 | TEMPLATE_CA_ENTRYPOINT(096,L"096"); | ||
| 130 | TEMPLATE_CA_ENTRYPOINT(097,L"097"); | ||
| 131 | TEMPLATE_CA_ENTRYPOINT(098,L"098"); | ||
| 132 | TEMPLATE_CA_ENTRYPOINT(099,L"099"); | ||
| 133 | TEMPLATE_CA_ENTRYPOINT(100,L"100"); | ||
| 134 | TEMPLATE_CA_ENTRYPOINT(101,L"101"); | ||
| 135 | TEMPLATE_CA_ENTRYPOINT(102,L"102"); | ||
| 136 | TEMPLATE_CA_ENTRYPOINT(103,L"103"); | ||
| 137 | TEMPLATE_CA_ENTRYPOINT(104,L"104"); | ||
| 138 | TEMPLATE_CA_ENTRYPOINT(105,L"105"); | ||
| 139 | TEMPLATE_CA_ENTRYPOINT(106,L"106"); | ||
| 140 | TEMPLATE_CA_ENTRYPOINT(107,L"107"); | ||
| 141 | TEMPLATE_CA_ENTRYPOINT(108,L"108"); | ||
| 142 | TEMPLATE_CA_ENTRYPOINT(109,L"109"); | ||
| 143 | TEMPLATE_CA_ENTRYPOINT(110,L"110"); | ||
| 144 | TEMPLATE_CA_ENTRYPOINT(111,L"111"); | ||
| 145 | TEMPLATE_CA_ENTRYPOINT(112,L"112"); | ||
| 146 | TEMPLATE_CA_ENTRYPOINT(113,L"113"); | ||
| 147 | TEMPLATE_CA_ENTRYPOINT(114,L"114"); | ||
| 148 | TEMPLATE_CA_ENTRYPOINT(115,L"115"); | ||
| 149 | TEMPLATE_CA_ENTRYPOINT(116,L"116"); | ||
| 150 | TEMPLATE_CA_ENTRYPOINT(117,L"117"); | ||
| 151 | TEMPLATE_CA_ENTRYPOINT(118,L"118"); | ||
| 152 | TEMPLATE_CA_ENTRYPOINT(119,L"119"); | ||
| 153 | TEMPLATE_CA_ENTRYPOINT(120,L"120"); | ||
| 154 | TEMPLATE_CA_ENTRYPOINT(121,L"121"); | ||
| 155 | TEMPLATE_CA_ENTRYPOINT(122,L"122"); | ||
| 156 | TEMPLATE_CA_ENTRYPOINT(123,L"123"); | ||
| 157 | TEMPLATE_CA_ENTRYPOINT(124,L"124"); | ||
| 158 | TEMPLATE_CA_ENTRYPOINT(125,L"125"); | ||
| 159 | TEMPLATE_CA_ENTRYPOINT(126,L"126"); | ||
| 160 | TEMPLATE_CA_ENTRYPOINT(127,L"127"); | ||
| 161 | |||
| 162 | // Note: Keep in sync with EntryPoints.def | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/Extract.cpp b/src/samples/Dtf/Tools/SfxCA/Extract.cpp new file mode 100644 index 00000000..171cf52f --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/Extract.cpp | |||
| @@ -0,0 +1,282 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | |||
| 5 | //--------------------------------------------------------------------- | ||
| 6 | // CABINET EXTRACTION | ||
| 7 | //--------------------------------------------------------------------- | ||
| 8 | |||
| 9 | // Globals make this code unsuited for multhreaded use, | ||
| 10 | // but FDI doesn't provide any other way to pass context. | ||
| 11 | |||
| 12 | // Handle to the FDI (cab extraction) engine. Need access to this in a callback. | ||
| 13 | static HFDI g_hfdi; | ||
| 14 | |||
| 15 | // FDI is not unicode-aware, so avoid passing these paths through the callbacks. | ||
| 16 | static const wchar_t* g_szExtractDir; | ||
| 17 | static const wchar_t* g_szCabFile; | ||
| 18 | |||
| 19 | // Offset into the source file where the cabinet really starts. | ||
| 20 | // Used to trick FDI into extracting from a concatenated cabinet. | ||
| 21 | static int g_lCabOffset; | ||
| 22 | |||
| 23 | // Use the secure CRT version of _wsopen if available. | ||
| 24 | #ifdef __GOT_SECURE_LIB__ | ||
| 25 | #define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) | ||
| 26 | #else | ||
| 27 | #define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) | ||
| 28 | #endif | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// FDI callback to open a cabinet file. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="pszFile">Name of the file to be opened. This parameter | ||
| 34 | /// is ignored since with our limited use this method is only ever called | ||
| 35 | /// to open the main cabinet file.</param> | ||
| 36 | /// <param name="oflag">Type of operations allowed.</param> | ||
| 37 | /// <param name="pmode">Permission setting.</param> | ||
| 38 | /// <returns>Integer file handle, or -1 if the file could not be opened.</returns> | ||
| 39 | /// <remarks> | ||
| 40 | /// To support reading from a cabinet that is concatenated onto | ||
| 41 | /// another file, this function first searches for the offset of the cabinet, | ||
| 42 | /// then saves that offset for use in recalculating later seeks. | ||
| 43 | /// </remarks> | ||
| 44 | static FNOPEN(CabOpen) | ||
| 45 | { | ||
| 46 | UNREFERENCED_PARAMETER(pszFile); | ||
| 47 | int hf; | ||
| 48 | _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); | ||
| 49 | if (hf != -1) | ||
| 50 | { | ||
| 51 | FDICABINETINFO cabInfo; | ||
| 52 | int length = _lseek(hf, 0, SEEK_END); | ||
| 53 | for(int offset = 0; offset < length; offset += 256) | ||
| 54 | { | ||
| 55 | if (_lseek(hf, offset, SEEK_SET) != offset) break; | ||
| 56 | if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) | ||
| 57 | { | ||
| 58 | g_lCabOffset = offset; | ||
| 59 | _lseek(hf, offset, SEEK_SET); | ||
| 60 | return hf; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | _close(hf); | ||
| 64 | } | ||
| 65 | return -1; | ||
| 66 | } | ||
| 67 | |||
| 68 | /// <summary> | ||
| 69 | /// FDI callback to seek within a file. | ||
| 70 | /// </summary> | ||
| 71 | /// <param name="hf">File handle.</param> | ||
| 72 | /// <param name="dist">Seek distance</param> | ||
| 73 | /// <param name="seektype">Whether to seek relative to the | ||
| 74 | /// beginning, current position, or end of the file.</param> | ||
| 75 | /// <returns>Resultant position within the cabinet.</returns> | ||
| 76 | /// <remarks> | ||
| 77 | /// To support reading from a cabinet that is concatenated onto | ||
| 78 | /// another file, this function recalculates seeks based on the | ||
| 79 | /// offset that was determined when the cabinet was opened. | ||
| 80 | /// </remarks> | ||
| 81 | static FNSEEK(CabSeek) | ||
| 82 | { | ||
| 83 | if (seektype == SEEK_SET) dist += g_lCabOffset; | ||
| 84 | int pos = _lseek((int) hf, dist, seektype); | ||
| 85 | pos -= g_lCabOffset; | ||
| 86 | return pos; | ||
| 87 | } | ||
| 88 | |||
| 89 | /// <summary> | ||
| 90 | /// Ensures a directory and its parent directory path exists. | ||
| 91 | /// </summary> | ||
| 92 | /// <param name="szDirPath">Directory path, not including file name.</param> | ||
| 93 | /// <returns>0 if the directory exists or was successfully created, else nonzero.</returns> | ||
| 94 | /// <remarks> | ||
| 95 | /// This function modifies characters in szDirPath, but always restores them | ||
| 96 | /// regardless of error condition. | ||
| 97 | /// </remarks> | ||
| 98 | static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) | ||
| 99 | { | ||
| 100 | int ret = 0; | ||
| 101 | if (!::CreateDirectoryW(szDirPath, NULL)) | ||
| 102 | { | ||
| 103 | UINT err = ::GetLastError(); | ||
| 104 | if (err != ERROR_ALREADY_EXISTS) | ||
| 105 | { | ||
| 106 | // Directory creation failed for some reason other than already existing. | ||
| 107 | // Try to create the parent directory first. | ||
| 108 | wchar_t* szLastSlash = NULL; | ||
| 109 | for (wchar_t* sz = szDirPath; *sz; sz++) | ||
| 110 | { | ||
| 111 | if (*sz == L'\\') | ||
| 112 | { | ||
| 113 | szLastSlash = sz; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | if (szLastSlash) | ||
| 117 | { | ||
| 118 | // Temporarily take one directory off the path and recurse. | ||
| 119 | *szLastSlash = L'\0'; | ||
| 120 | ret = EnsureDirectoryExists(szDirPath); | ||
| 121 | *szLastSlash = L'\\'; | ||
| 122 | |||
| 123 | // Try to create the directory if all parents are created. | ||
| 124 | if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) | ||
| 125 | { | ||
| 126 | err = ::GetLastError(); | ||
| 127 | if (err != ERROR_ALREADY_EXISTS) | ||
| 128 | { | ||
| 129 | ret = -1; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | else | ||
| 134 | { | ||
| 135 | ret = -1; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | return ret; | ||
| 140 | } | ||
| 141 | |||
| 142 | /// <summary> | ||
| 143 | /// Ensures a file's directory and its parent directory path exists. | ||
| 144 | /// </summary> | ||
| 145 | /// <param name="szDirPath">Path including file name.</param> | ||
| 146 | /// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns> | ||
| 147 | /// <remarks> | ||
| 148 | /// This function modifies characters in szFilePath, but always restores them | ||
| 149 | /// regardless of error condition. | ||
| 150 | /// </remarks> | ||
| 151 | static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) | ||
| 152 | { | ||
| 153 | int ret = 0; | ||
| 154 | wchar_t* szLastSlash = NULL; | ||
| 155 | for (wchar_t* sz = szFilePath; *sz; sz++) | ||
| 156 | { | ||
| 157 | if (*sz == L'\\') | ||
| 158 | { | ||
| 159 | szLastSlash = sz; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | if (szLastSlash) | ||
| 163 | { | ||
| 164 | *szLastSlash = L'\0'; | ||
| 165 | ret = EnsureDirectoryExists(szFilePath); | ||
| 166 | *szLastSlash = L'\\'; | ||
| 167 | } | ||
| 168 | return ret; | ||
| 169 | } | ||
| 170 | |||
| 171 | /// <summary> | ||
| 172 | /// FDI callback for handling files in the cabinet. | ||
| 173 | /// </summary> | ||
| 174 | /// <param name="fdint">Type of notification.</param> | ||
| 175 | /// <param name="pfdin">Structure containing data about the notification.</param> | ||
| 176 | /// <remarks> | ||
| 177 | /// Refer to fdi.h for more comments on this notification callback. | ||
| 178 | /// </remarks> | ||
| 179 | static FNFDINOTIFY(CabNotification) | ||
| 180 | { | ||
| 181 | // fdintCOPY_FILE: | ||
| 182 | // Called for each file that *starts* in the current cabinet, giving | ||
| 183 | // the client the opportunity to request that the file be copied or | ||
| 184 | // skipped. | ||
| 185 | // Entry: | ||
| 186 | // pfdin->psz1 = file name in cabinet | ||
| 187 | // pfdin->cb = uncompressed size of file | ||
| 188 | // pfdin->date = file date | ||
| 189 | // pfdin->time = file time | ||
| 190 | // pfdin->attribs = file attributes | ||
| 191 | // pfdin->iFolder = file's folder index | ||
| 192 | // Exit-Success: | ||
| 193 | // Return non-zero file handle for destination file; FDI writes | ||
| 194 | // data to this file use the PFNWRITE function supplied to FDICreate, | ||
| 195 | // and then calls fdintCLOSE_FILE_INFO to close the file and set | ||
| 196 | // the date, time, and attributes. | ||
| 197 | // Exit-Failure: | ||
| 198 | // Returns 0 => Skip file, do not copy | ||
| 199 | // Returns -1 => Abort FDICopy() call | ||
| 200 | if (fdint == fdintCOPY_FILE) | ||
| 201 | { | ||
| 202 | size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); | ||
| 203 | size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; | ||
| 204 | wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); | ||
| 205 | if (szFilePath == NULL) return -1; | ||
| 206 | StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); | ||
| 207 | StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); | ||
| 208 | MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, | ||
| 209 | szFilePath + cchFilePath - cchFile, (int) cchFile + 1); | ||
| 210 | int hf = -1; | ||
| 211 | if (EnsureFileDirectoryExists(szFilePath) == 0) | ||
| 212 | { | ||
| 213 | _wsopen__s(hf, szFilePath, | ||
| 214 | _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, | ||
| 215 | _SH_DENYWR, _S_IREAD | _S_IWRITE); | ||
| 216 | } | ||
| 217 | return hf; | ||
| 218 | } | ||
| 219 | |||
| 220 | // fdintCLOSE_FILE_INFO: | ||
| 221 | // Called after all of the data has been written to a target file. | ||
| 222 | // This function must close the file and set the file date, time, | ||
| 223 | // and attributes. | ||
| 224 | // Entry: | ||
| 225 | // pfdin->psz1 = file name in cabinet | ||
| 226 | // pfdin->hf = file handle | ||
| 227 | // pfdin->date = file date | ||
| 228 | // pfdin->time = file time | ||
| 229 | // pfdin->attribs = file attributes | ||
| 230 | // pfdin->iFolder = file's folder index | ||
| 231 | // pfdin->cb = Run After Extract (0 - don't run, 1 Run) | ||
| 232 | // Exit-Success: | ||
| 233 | // Returns TRUE | ||
| 234 | // Exit-Failure: | ||
| 235 | // Returns FALSE, or -1 to abort | ||
| 236 | else if (fdint == fdintCLOSE_FILE_INFO) | ||
| 237 | { | ||
| 238 | _close((int) pfdin->hf); | ||
| 239 | return TRUE; | ||
| 240 | } | ||
| 241 | return 0; | ||
| 242 | } | ||
| 243 | |||
| 244 | /// <summary> | ||
| 245 | /// Extracts all contents of a cabinet file to a directory. | ||
| 246 | /// </summary> | ||
| 247 | /// <param name="szCabFile">Path to the cabinet file to be extracted. | ||
| 248 | /// The cabinet may actually start at some offset within the file, | ||
| 249 | /// as long as that offset is a multiple of 256.</param> | ||
| 250 | /// <param name="szExtractDir">Directory where files are to be extracted. | ||
| 251 | /// This directory must already exist, but should be empty.</param> | ||
| 252 | /// <returns>0 if the cabinet was extracted successfully, | ||
| 253 | /// or an error code if any error occurred.</returns> | ||
| 254 | /// <remarks> | ||
| 255 | /// The extraction will not overwrite any files in the destination | ||
| 256 | /// directory; extraction will be interrupted and fail if any files | ||
| 257 | /// with the same name already exist. | ||
| 258 | /// </remarks> | ||
| 259 | int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) | ||
| 260 | { | ||
| 261 | ERF erf; | ||
| 262 | // Most of the FDI callbacks can be handled by existing CRT I/O functions. | ||
| 263 | // For our functionality we only need to handle the open and seek callbacks. | ||
| 264 | HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, | ||
| 265 | (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, | ||
| 266 | CabSeek, cpu80386, &erf); | ||
| 267 | if (hfdi != NULL) | ||
| 268 | { | ||
| 269 | g_hfdi = hfdi; | ||
| 270 | g_szCabFile = szCabFile; | ||
| 271 | g_szExtractDir = szExtractDir; | ||
| 272 | char szEmpty[1] = {0}; | ||
| 273 | if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) | ||
| 274 | { | ||
| 275 | FDIDestroy(hfdi); | ||
| 276 | return 0; | ||
| 277 | } | ||
| 278 | FDIDestroy(hfdi); | ||
| 279 | } | ||
| 280 | |||
| 281 | return erf.erfOper; | ||
| 282 | } | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp new file mode 100644 index 00000000..ba59fdf7 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp | |||
| @@ -0,0 +1,629 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | #include "RemoteMsiSession.h" | ||
| 5 | |||
| 6 | |||
| 7 | // | ||
| 8 | // Ensures that the request buffer is large enough to hold a request, | ||
| 9 | // reallocating the buffer if necessary. | ||
| 10 | // It will also reduce the buffer size if the previous allocation was very large. | ||
| 11 | // | ||
| 12 | static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired) | ||
| 13 | { | ||
| 14 | // It will also reduce the buffer size if the previous allocation was very large. | ||
| 15 | if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf)) | ||
| 16 | { | ||
| 17 | if (*pszBuf != NULL) | ||
| 18 | { | ||
| 19 | SecureZeroMemory(*pszBuf, *pcchBuf); | ||
| 20 | delete[] *pszBuf; | ||
| 21 | } | ||
| 22 | |||
| 23 | *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired); | ||
| 24 | *pszBuf = new wchar_t[*pcchBuf]; | ||
| 25 | |||
| 26 | if (*pszBuf == NULL) | ||
| 27 | { | ||
| 28 | return ERROR_OUTOFMEMORY; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | return ERROR_SUCCESS; | ||
| 33 | } | ||
| 34 | |||
| 35 | typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); | ||
| 36 | typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); | ||
| 37 | typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); | ||
| 38 | typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); | ||
| 39 | typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); | ||
| 40 | typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); | ||
| 41 | typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 42 | typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 43 | typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 44 | typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
| 45 | typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3); | ||
| 46 | |||
| 47 | UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 48 | { | ||
| 49 | int in1 = pReq->fields[0].iValue; | ||
| 50 | int out1; | ||
| 51 | UINT ret = (UINT) func(in1, &out1); | ||
| 52 | if (ret == 0) | ||
| 53 | { | ||
| 54 | pResp->fields[1].vt = VT_I4; | ||
| 55 | pResp->fields[1].iValue = out1; | ||
| 56 | } | ||
| 57 | return ret; | ||
| 58 | } | ||
| 59 | |||
| 60 | UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 61 | { | ||
| 62 | int in1 = pReq->fields[0].iValue; | ||
| 63 | int in2 = pReq->fields[1].iValue; | ||
| 64 | int out1; | ||
| 65 | UINT ret = (UINT) func(in1, in2, &out1); | ||
| 66 | if (ret == 0) | ||
| 67 | { | ||
| 68 | pResp->fields[1].vt = VT_I4; | ||
| 69 | pResp->fields[1].iValue = out1; | ||
| 70 | } | ||
| 71 | return ret; | ||
| 72 | } | ||
| 73 | |||
| 74 | UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 75 | { | ||
| 76 | int in1 = pReq->fields[0].iValue; | ||
| 77 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 78 | int out1; | ||
| 79 | UINT ret = (UINT) func(in1, in2, &out1); | ||
| 80 | if (ret == 0) | ||
| 81 | { | ||
| 82 | pResp->fields[1].vt = VT_I4; | ||
| 83 | pResp->fields[1].iValue = out1; | ||
| 84 | } | ||
| 85 | return ret; | ||
| 86 | } | ||
| 87 | |||
| 88 | UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 89 | { | ||
| 90 | int in1 = pReq->fields[0].iValue; | ||
| 91 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 92 | int in3 = pReq->fields[2].iValue; | ||
| 93 | int out1; | ||
| 94 | UINT ret = (UINT) func(in1, in2, in3, &out1); | ||
| 95 | if (ret == 0) | ||
| 96 | { | ||
| 97 | pResp->fields[1].vt = VT_I4; | ||
| 98 | pResp->fields[1].iValue = out1; | ||
| 99 | } | ||
| 100 | return ret; | ||
| 101 | } | ||
| 102 | |||
| 103 | UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 104 | { | ||
| 105 | int in1 = pReq->fields[0].iValue; | ||
| 106 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 107 | int in3 = pReq->fields[2].iValue; | ||
| 108 | int in4 = pReq->fields[3].iValue; | ||
| 109 | int out1; | ||
| 110 | UINT ret = (UINT) func(in1, in2, in3, in4, &out1); | ||
| 111 | if (ret == 0) | ||
| 112 | { | ||
| 113 | pResp->fields[1].vt = VT_I4; | ||
| 114 | pResp->fields[1].iValue = out1; | ||
| 115 | } | ||
| 116 | return ret; | ||
| 117 | } | ||
| 118 | |||
| 119 | UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
| 120 | { | ||
| 121 | int in1 = pReq->fields[0].iValue; | ||
| 122 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 123 | int out1, out2; | ||
| 124 | UINT ret = (UINT) func(in1, in2, &out1, &out2); | ||
| 125 | if (ret == 0) | ||
| 126 | { | ||
| 127 | pResp->fields[1].vt = VT_I4; | ||
| 128 | pResp->fields[1].iValue = out1; | ||
| 129 | pResp->fields[2].vt = VT_I4; | ||
| 130 | pResp->fields[2].iValue = out2; | ||
| 131 | } | ||
| 132 | return ret; | ||
| 133 | } | ||
| 134 | |||
| 135 | UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
| 136 | { | ||
| 137 | int in1 = pReq->fields[0].iValue; | ||
| 138 | szBuf[0] = L'\0'; | ||
| 139 | DWORD cchValue = cchBuf; | ||
| 140 | UINT ret = (UINT) func(in1, szBuf, &cchValue); | ||
| 141 | if (ret == ERROR_MORE_DATA) | ||
| 142 | { | ||
| 143 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
| 144 | if (ret == 0) | ||
| 145 | { | ||
| 146 | ret = (UINT) func(in1, szBuf, &cchValue); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | if (ret == 0) | ||
| 150 | { | ||
| 151 | pResp->fields[1].vt = VT_LPWSTR; | ||
| 152 | pResp->fields[1].szValue = szBuf; | ||
| 153 | } | ||
| 154 | return ret; | ||
| 155 | } | ||
| 156 | |||
| 157 | MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
| 158 | { | ||
| 159 | int in1 = pReq->fields[0].iValue; | ||
| 160 | szBuf[0] = L'\0'; | ||
| 161 | DWORD cchValue = cchBuf; | ||
| 162 | MSIDBERROR ret = func(in1, szBuf, &cchValue); | ||
| 163 | if (ret == MSIDBERROR_MOREDATA) | ||
| 164 | { | ||
| 165 | if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue)) | ||
| 166 | { | ||
| 167 | ret = func(in1, szBuf, &cchValue); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | if (ret != MSIDBERROR_MOREDATA) | ||
| 171 | { | ||
| 172 | pResp->fields[1].vt = VT_LPWSTR; | ||
| 173 | pResp->fields[1].szValue = szBuf; | ||
| 174 | } | ||
| 175 | return ret; | ||
| 176 | } | ||
| 177 | |||
| 178 | UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
| 179 | { | ||
| 180 | int in1 = pReq->fields[0].iValue; | ||
| 181 | int in2 = pReq->fields[1].iValue; | ||
| 182 | szBuf[0] = L'\0'; | ||
| 183 | DWORD cchValue = cchBuf; | ||
| 184 | UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
| 185 | if (ret == ERROR_MORE_DATA) | ||
| 186 | { | ||
| 187 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
| 188 | if (ret == 0) | ||
| 189 | { | ||
| 190 | ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | if (ret == 0) | ||
| 194 | { | ||
| 195 | pResp->fields[1].vt = VT_LPWSTR; | ||
| 196 | pResp->fields[1].szValue = szBuf; | ||
| 197 | } | ||
| 198 | return ret; | ||
| 199 | } | ||
| 200 | |||
| 201 | UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
| 202 | { | ||
| 203 | int in1 = pReq->fields[0].iValue; | ||
| 204 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 205 | szBuf[0] = L'\0'; | ||
| 206 | DWORD cchValue = cchBuf; | ||
| 207 | UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
| 208 | if (ret == ERROR_MORE_DATA) | ||
| 209 | { | ||
| 210 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
| 211 | if (ret == 0) | ||
| 212 | { | ||
| 213 | ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | if (ret == 0) | ||
| 217 | { | ||
| 218 | pResp->fields[1].vt = VT_LPWSTR; | ||
| 219 | pResp->fields[1].szValue = szBuf; | ||
| 220 | } | ||
| 221 | return ret; | ||
| 222 | } | ||
| 223 | |||
| 224 | UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
| 225 | { | ||
| 226 | int in1 = pReq->fields[0].iValue; | ||
| 227 | wchar_t* in2 = pReq->fields[1].szValue; | ||
| 228 | int in3 = pReq->fields[2].iValue; | ||
| 229 | int in4 = pReq->fields[3].iValue; | ||
| 230 | szBuf[0] = L'\0'; | ||
| 231 | DWORD cchValue = cchBuf; | ||
| 232 | int out2, out3; | ||
| 233 | UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); | ||
| 234 | if (ret == ERROR_MORE_DATA) | ||
| 235 | { | ||
| 236 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
| 237 | if (ret == 0) | ||
| 238 | { | ||
| 239 | ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | if (ret == 0) | ||
| 243 | { | ||
| 244 | pResp->fields[1].vt = VT_LPWSTR; | ||
| 245 | pResp->fields[1].szValue = szBuf; | ||
| 246 | pResp->fields[2].vt = VT_I4; | ||
| 247 | pResp->fields[2].iValue = out2; | ||
| 248 | pResp->fields[3].vt = VT_I4; | ||
| 249 | pResp->fields[3].iValue = out3; | ||
| 250 | } | ||
| 251 | return ret; | ||
| 252 | } | ||
| 253 | |||
| 254 | void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp) | ||
| 255 | { | ||
| 256 | SecureZeroMemory(pResp, sizeof(RequestData)); | ||
| 257 | |||
| 258 | UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024); | ||
| 259 | |||
| 260 | if (0 == ret) | ||
| 261 | { | ||
| 262 | switch (id) | ||
| 263 | { | ||
| 264 | case RemoteMsiSession::EndSession: | ||
| 265 | { | ||
| 266 | this->ExitCode = pReq->fields[0].iValue; | ||
| 267 | } | ||
| 268 | break; | ||
| 269 | case RemoteMsiSession::MsiCloseHandle: | ||
| 270 | { | ||
| 271 | MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 272 | ret = ::MsiCloseHandle(h); | ||
| 273 | } | ||
| 274 | break; | ||
| 275 | case RemoteMsiSession::MsiProcessMessage: | ||
| 276 | { | ||
| 277 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 278 | INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue; | ||
| 279 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; | ||
| 280 | ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord); | ||
| 281 | } | ||
| 282 | break; | ||
| 283 | case RemoteMsiSession::MsiGetProperty: | ||
| 284 | { | ||
| 285 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 286 | } | ||
| 287 | break; | ||
| 288 | case RemoteMsiSession::MsiSetProperty: | ||
| 289 | { | ||
| 290 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 291 | const wchar_t* szName = pReq->fields[1].szValue; | ||
| 292 | const wchar_t* szValue = pReq->fields[2].szValue; | ||
| 293 | ret = ::MsiSetProperty(hInstall, szName, szValue); | ||
| 294 | } | ||
| 295 | break; | ||
| 296 | case RemoteMsiSession::MsiCreateRecord: | ||
| 297 | { | ||
| 298 | UINT cParams = pReq->fields[0].uiValue; | ||
| 299 | ret = ::MsiCreateRecord(cParams); | ||
| 300 | } | ||
| 301 | break; | ||
| 302 | case RemoteMsiSession::MsiRecordGetFieldCount: | ||
| 303 | { | ||
| 304 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 305 | ret = ::MsiRecordGetFieldCount(hRecord); | ||
| 306 | } | ||
| 307 | break; | ||
| 308 | case RemoteMsiSession::MsiRecordGetInteger: | ||
| 309 | { | ||
| 310 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 311 | UINT iField = pReq->fields[1].uiValue; | ||
| 312 | ret = ::MsiRecordGetInteger(hRecord, iField); | ||
| 313 | } | ||
| 314 | break; | ||
| 315 | case RemoteMsiSession::MsiRecordSetInteger: | ||
| 316 | { | ||
| 317 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 318 | UINT iField = pReq->fields[1].uiValue; | ||
| 319 | int iValue = pReq->fields[2].iValue; | ||
| 320 | ret = ::MsiRecordSetInteger(hRecord, iField, iValue); | ||
| 321 | } | ||
| 322 | break; | ||
| 323 | case RemoteMsiSession::MsiRecordGetString: | ||
| 324 | { | ||
| 325 | ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 326 | } | ||
| 327 | break; | ||
| 328 | case RemoteMsiSession::MsiRecordSetString: | ||
| 329 | { | ||
| 330 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 331 | UINT iField = pReq->fields[1].uiValue; | ||
| 332 | const wchar_t* szValue = pReq->fields[2].szValue; | ||
| 333 | ret = ::MsiRecordSetString(hRecord, iField, szValue); | ||
| 334 | } | ||
| 335 | break; | ||
| 336 | case RemoteMsiSession::MsiRecordClearData: | ||
| 337 | { | ||
| 338 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 339 | ret = ::MsiRecordClearData(hRecord); | ||
| 340 | } | ||
| 341 | break; | ||
| 342 | case RemoteMsiSession::MsiRecordIsNull: | ||
| 343 | { | ||
| 344 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 345 | UINT iField = pReq->fields[1].uiValue; | ||
| 346 | ret = ::MsiRecordIsNull(hRecord, iField); | ||
| 347 | } | ||
| 348 | break; | ||
| 349 | case RemoteMsiSession::MsiFormatRecord: | ||
| 350 | { | ||
| 351 | ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 352 | } | ||
| 353 | break; | ||
| 354 | case RemoteMsiSession::MsiGetActiveDatabase: | ||
| 355 | { | ||
| 356 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 357 | ret = (UINT) ::MsiGetActiveDatabase(hInstall); | ||
| 358 | } | ||
| 359 | break; | ||
| 360 | case RemoteMsiSession::MsiDatabaseOpenView: | ||
| 361 | { | ||
| 362 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp); | ||
| 363 | } | ||
| 364 | break; | ||
| 365 | case RemoteMsiSession::MsiViewExecute: | ||
| 366 | { | ||
| 367 | MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 368 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue; | ||
| 369 | ret = ::MsiViewExecute(hView, hRecord); | ||
| 370 | } | ||
| 371 | break; | ||
| 372 | case RemoteMsiSession::MsiViewFetch: | ||
| 373 | { | ||
| 374 | ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp); | ||
| 375 | } | ||
| 376 | break; | ||
| 377 | case RemoteMsiSession::MsiViewModify: | ||
| 378 | { | ||
| 379 | MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 380 | MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue; | ||
| 381 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; | ||
| 382 | ret = ::MsiViewModify(hView, eModifyMode, hRecord); | ||
| 383 | } | ||
| 384 | break; | ||
| 385 | case RemoteMsiSession::MsiViewGetError: | ||
| 386 | { | ||
| 387 | ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 388 | } | ||
| 389 | break; | ||
| 390 | case RemoteMsiSession::MsiViewGetColumnInfo: | ||
| 391 | { | ||
| 392 | ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp); | ||
| 393 | } | ||
| 394 | break; | ||
| 395 | case RemoteMsiSession::MsiDatabaseGetPrimaryKeys: | ||
| 396 | { | ||
| 397 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp); | ||
| 398 | } | ||
| 399 | break; | ||
| 400 | case RemoteMsiSession::MsiDatabaseIsTablePersistent: | ||
| 401 | { | ||
| 402 | MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 403 | const wchar_t* szTable = pReq->fields[1].szValue; | ||
| 404 | ret = ::MsiDatabaseIsTablePersistent(hDb, szTable); | ||
| 405 | } | ||
| 406 | break; | ||
| 407 | case RemoteMsiSession::MsiDoAction: | ||
| 408 | { | ||
| 409 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 410 | const wchar_t* szAction = pReq->fields[1].szValue; | ||
| 411 | ret = ::MsiDoAction(hInstall, szAction); | ||
| 412 | } | ||
| 413 | break; | ||
| 414 | case RemoteMsiSession::MsiEnumComponentCosts: | ||
| 415 | { | ||
| 416 | ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 417 | } | ||
| 418 | break; | ||
| 419 | case RemoteMsiSession::MsiEvaluateCondition: | ||
| 420 | { | ||
| 421 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 422 | const wchar_t* szCondition = pReq->fields[1].szValue; | ||
| 423 | ret = ::MsiEvaluateCondition(hInstall, szCondition); | ||
| 424 | } | ||
| 425 | break; | ||
| 426 | case RemoteMsiSession::MsiGetComponentState: | ||
| 427 | { | ||
| 428 | ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp); | ||
| 429 | } | ||
| 430 | break; | ||
| 431 | case RemoteMsiSession::MsiGetFeatureCost: | ||
| 432 | { | ||
| 433 | ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp); | ||
| 434 | } | ||
| 435 | break; | ||
| 436 | case RemoteMsiSession::MsiGetFeatureState: | ||
| 437 | { | ||
| 438 | ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp); | ||
| 439 | } | ||
| 440 | break; | ||
| 441 | case RemoteMsiSession::MsiGetFeatureValidStates: | ||
| 442 | { | ||
| 443 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp); | ||
| 444 | } | ||
| 445 | break; | ||
| 446 | case RemoteMsiSession::MsiGetLanguage: | ||
| 447 | { | ||
| 448 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 449 | ret = ::MsiGetLanguage(hInstall); | ||
| 450 | } | ||
| 451 | break; | ||
| 452 | case RemoteMsiSession::MsiGetLastErrorRecord: | ||
| 453 | { | ||
| 454 | ret = ::MsiGetLastErrorRecord(); | ||
| 455 | } | ||
| 456 | break; | ||
| 457 | case RemoteMsiSession::MsiGetMode: | ||
| 458 | { | ||
| 459 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 460 | MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue; | ||
| 461 | ret = ::MsiGetMode(hInstall, iRunMode); | ||
| 462 | } | ||
| 463 | break; | ||
| 464 | case RemoteMsiSession::MsiGetSourcePath: | ||
| 465 | { | ||
| 466 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 467 | } | ||
| 468 | break; | ||
| 469 | case RemoteMsiSession::MsiGetSummaryInformation: | ||
| 470 | { | ||
| 471 | ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp); | ||
| 472 | } | ||
| 473 | break; | ||
| 474 | case RemoteMsiSession::MsiGetTargetPath: | ||
| 475 | { | ||
| 476 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
| 477 | } | ||
| 478 | break; | ||
| 479 | case RemoteMsiSession::MsiRecordDataSize: | ||
| 480 | { | ||
| 481 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 482 | UINT iField = pReq->fields[1].uiValue; | ||
| 483 | ret = ::MsiRecordDataSize(hRecord, iField); | ||
| 484 | } | ||
| 485 | break; | ||
| 486 | case RemoteMsiSession::MsiRecordReadStream: | ||
| 487 | { | ||
| 488 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 489 | UINT iField = pReq->fields[1].uiValue; | ||
| 490 | DWORD cbRead = (DWORD) pReq->fields[2].uiValue; | ||
| 491 | ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2); | ||
| 492 | if (ret == 0) | ||
| 493 | { | ||
| 494 | ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead); | ||
| 495 | if (ret == 0) | ||
| 496 | { | ||
| 497 | pResp->fields[1].vt = VT_STREAM; | ||
| 498 | pResp->fields[1].szValue = m_pBufSend; | ||
| 499 | pResp->fields[2].vt = VT_I4; | ||
| 500 | pResp->fields[2].uiValue = (UINT) cbRead; | ||
| 501 | } | ||
| 502 | } | ||
| 503 | } | ||
| 504 | break; | ||
| 505 | case RemoteMsiSession::MsiRecordSetStream: | ||
| 506 | { | ||
| 507 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 508 | UINT iField = pReq->fields[1].uiValue; | ||
| 509 | const wchar_t* szFilePath = pReq->fields[2].szValue; | ||
| 510 | ret = ::MsiRecordSetStream(hRecord, iField, szFilePath); | ||
| 511 | } | ||
| 512 | break; | ||
| 513 | case RemoteMsiSession::MsiSequence: | ||
| 514 | { | ||
| 515 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 516 | const wchar_t* szTable = pReq->fields[1].szValue; | ||
| 517 | UINT iSequenceMode = pReq->fields[2].uiValue; | ||
| 518 | ret = ::MsiSequence(hRecord, szTable, iSequenceMode); | ||
| 519 | } | ||
| 520 | break; | ||
| 521 | case RemoteMsiSession::MsiSetComponentState: | ||
| 522 | { | ||
| 523 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 524 | const wchar_t* szComponent = pReq->fields[1].szValue; | ||
| 525 | INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; | ||
| 526 | ret = ::MsiSetComponentState(hInstall, szComponent, iState); | ||
| 527 | } | ||
| 528 | break; | ||
| 529 | case RemoteMsiSession::MsiSetFeatureAttributes: | ||
| 530 | { | ||
| 531 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 532 | const wchar_t* szFeature = pReq->fields[1].szValue; | ||
| 533 | DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue; | ||
| 534 | ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs); | ||
| 535 | } | ||
| 536 | break; | ||
| 537 | case RemoteMsiSession::MsiSetFeatureState: | ||
| 538 | { | ||
| 539 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 540 | const wchar_t* szFeature = pReq->fields[1].szValue; | ||
| 541 | INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; | ||
| 542 | ret = ::MsiSetFeatureState(hInstall, szFeature, iState); | ||
| 543 | } | ||
| 544 | break; | ||
| 545 | case RemoteMsiSession::MsiSetInstallLevel: | ||
| 546 | { | ||
| 547 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 548 | int iInstallLevel = pReq->fields[1].iValue; | ||
| 549 | ret = ::MsiSetInstallLevel(hInstall, iInstallLevel); | ||
| 550 | } | ||
| 551 | break; | ||
| 552 | case RemoteMsiSession::MsiSetMode: | ||
| 553 | { | ||
| 554 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 555 | MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue; | ||
| 556 | BOOL fState = (BOOL) pReq->fields[2].iValue; | ||
| 557 | ret = ::MsiSetMode(hInstall, iRunMode, fState); | ||
| 558 | } | ||
| 559 | break; | ||
| 560 | case RemoteMsiSession::MsiSetTargetPath: | ||
| 561 | { | ||
| 562 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 563 | const wchar_t* szFolder = pReq->fields[1].szValue; | ||
| 564 | const wchar_t* szFolderPath = pReq->fields[2].szValue; | ||
| 565 | ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath); | ||
| 566 | } | ||
| 567 | break; | ||
| 568 | case RemoteMsiSession::MsiSummaryInfoGetProperty: | ||
| 569 | { | ||
| 570 | MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 571 | UINT uiProperty = pReq->fields[1].uiValue; | ||
| 572 | UINT uiDataType; | ||
| 573 | int iValue; | ||
| 574 | FILETIME ftValue; | ||
| 575 | m_pBufSend[0] = L'\0'; | ||
| 576 | DWORD cchValue = m_cbBufSend; | ||
| 577 | ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); | ||
| 578 | if (ret == ERROR_MORE_DATA) | ||
| 579 | { | ||
| 580 | ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue); | ||
| 581 | if (ret == 0) | ||
| 582 | { | ||
| 583 | ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); | ||
| 584 | } | ||
| 585 | } | ||
| 586 | if (ret == 0) | ||
| 587 | { | ||
| 588 | pResp->fields[1].vt = VT_UI4; | ||
| 589 | pResp->fields[1].uiValue = uiDataType; | ||
| 590 | |||
| 591 | switch (uiDataType) | ||
| 592 | { | ||
| 593 | case VT_I2: | ||
| 594 | case VT_I4: | ||
| 595 | pResp->fields[2].vt = VT_I4; | ||
| 596 | pResp->fields[2].iValue = iValue; | ||
| 597 | break; | ||
| 598 | case VT_FILETIME: | ||
| 599 | pResp->fields[2].vt = VT_UI4; | ||
| 600 | pResp->fields[2].iValue = ftValue.dwHighDateTime; | ||
| 601 | pResp->fields[3].vt = VT_UI4; | ||
| 602 | pResp->fields[3].iValue = ftValue.dwLowDateTime; | ||
| 603 | break; | ||
| 604 | case VT_LPSTR: | ||
| 605 | pResp->fields[2].vt = VT_LPWSTR; | ||
| 606 | pResp->fields[2].szValue = m_pBufSend; | ||
| 607 | break; | ||
| 608 | } | ||
| 609 | } | ||
| 610 | } | ||
| 611 | break; | ||
| 612 | case RemoteMsiSession::MsiVerifyDiskSpace: | ||
| 613 | { | ||
| 614 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
| 615 | ret = ::MsiVerifyDiskSpace(hInstall); | ||
| 616 | } | ||
| 617 | break; | ||
| 618 | |||
| 619 | default: | ||
| 620 | { | ||
| 621 | ret = ERROR_INVALID_FUNCTION; | ||
| 622 | } | ||
| 623 | break; | ||
| 624 | } | ||
| 625 | } | ||
| 626 | |||
| 627 | pResp->fields[0].vt = VT_UI4; | ||
| 628 | pResp->fields[0].uiValue = ret; | ||
| 629 | } | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h new file mode 100644 index 00000000..90c7c01f --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h | |||
| @@ -0,0 +1,898 @@ | |||
| 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 | #define LARGE_BUFFER_THRESHOLD 65536 // bytes | ||
| 4 | #define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts | ||
| 5 | |||
| 6 | /////////////////////////////////////////////////////////////////////////////////////// | ||
| 7 | // RemoteMsiSession // | ||
| 8 | ////////////////////// | ||
| 9 | // | ||
| 10 | // Allows accessing MSI APIs from another process using named pipes. | ||
| 11 | // | ||
| 12 | class RemoteMsiSession | ||
| 13 | { | ||
| 14 | public: | ||
| 15 | |||
| 16 | // This enumeration MUST stay in sync with the | ||
| 17 | // managed equivalent in RemotableNativeMethods.cs! | ||
| 18 | enum RequestId | ||
| 19 | { | ||
| 20 | EndSession = 0, | ||
| 21 | MsiCloseHandle, | ||
| 22 | MsiCreateRecord, | ||
| 23 | MsiDatabaseGetPrimaryKeys, | ||
| 24 | MsiDatabaseIsTablePersistent, | ||
| 25 | MsiDatabaseOpenView, | ||
| 26 | MsiDoAction, | ||
| 27 | MsiEnumComponentCosts, | ||
| 28 | MsiEvaluateCondition, | ||
| 29 | MsiFormatRecord, | ||
| 30 | MsiGetActiveDatabase, | ||
| 31 | MsiGetComponentState, | ||
| 32 | MsiGetFeatureCost, | ||
| 33 | MsiGetFeatureState, | ||
| 34 | MsiGetFeatureValidStates, | ||
| 35 | MsiGetLanguage, | ||
| 36 | MsiGetLastErrorRecord, | ||
| 37 | MsiGetMode, | ||
| 38 | MsiGetProperty, | ||
| 39 | MsiGetSourcePath, | ||
| 40 | MsiGetSummaryInformation, | ||
| 41 | MsiGetTargetPath, | ||
| 42 | MsiProcessMessage, | ||
| 43 | MsiRecordClearData, | ||
| 44 | MsiRecordDataSize, | ||
| 45 | MsiRecordGetFieldCount, | ||
| 46 | MsiRecordGetInteger, | ||
| 47 | MsiRecordGetString, | ||
| 48 | MsiRecordIsNull, | ||
| 49 | MsiRecordReadStream, | ||
| 50 | MsiRecordSetInteger, | ||
| 51 | MsiRecordSetStream, | ||
| 52 | MsiRecordSetString, | ||
| 53 | MsiSequence, | ||
| 54 | MsiSetComponentState, | ||
| 55 | MsiSetFeatureAttributes, | ||
| 56 | MsiSetFeatureState, | ||
| 57 | MsiSetInstallLevel, | ||
| 58 | MsiSetMode, | ||
| 59 | MsiSetProperty, | ||
| 60 | MsiSetTargetPath, | ||
| 61 | MsiSummaryInfoGetProperty, | ||
| 62 | MsiVerifyDiskSpace, | ||
| 63 | MsiViewExecute, | ||
| 64 | MsiViewFetch, | ||
| 65 | MsiViewGetError, | ||
| 66 | MsiViewGetColumnInfo, | ||
| 67 | MsiViewModify, | ||
| 68 | }; | ||
| 69 | |||
| 70 | static const int MAX_REQUEST_FIELDS = 4; | ||
| 71 | |||
| 72 | // Used to pass data back and forth for remote API calls, | ||
| 73 | // including in & out params & return values. | ||
| 74 | // Only strings and ints are supported. | ||
| 75 | struct RequestData | ||
| 76 | { | ||
| 77 | struct | ||
| 78 | { | ||
| 79 | VARENUM vt; | ||
| 80 | union { | ||
| 81 | int iValue; | ||
| 82 | UINT uiValue; | ||
| 83 | DWORD cchValue; | ||
| 84 | LPWSTR szValue; | ||
| 85 | BYTE* sValue; | ||
| 86 | DWORD cbValue; | ||
| 87 | }; | ||
| 88 | } fields[MAX_REQUEST_FIELDS]; | ||
| 89 | }; | ||
| 90 | |||
| 91 | public: | ||
| 92 | |||
| 93 | // This value is set from the single data parameter in the EndSession request. | ||
| 94 | // It saves the exit code of the out-of-proc custom action. | ||
| 95 | int ExitCode; | ||
| 96 | |||
| 97 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 98 | // RemoteMsiSession constructor | ||
| 99 | // | ||
| 100 | // Creates a new remote session instance, for use either by the server | ||
| 101 | // or client process. | ||
| 102 | // | ||
| 103 | // szName - Identifies the session instance being remoted. The server and | ||
| 104 | // the client must use the same name. The name should be unique | ||
| 105 | // enough to avoid conflicting with other instances on the system. | ||
| 106 | // | ||
| 107 | // fServer - True if the calling process is the server process, false if the | ||
| 108 | // calling process is the client process. | ||
| 109 | // | ||
| 110 | RemoteMsiSession(const wchar_t* szName, bool fServer=true) | ||
| 111 | : m_fServer(fServer), | ||
| 112 | m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"), | ||
| 113 | m_szPipeName(NULL), | ||
| 114 | m_hPipe(NULL), | ||
| 115 | m_fConnecting(false), | ||
| 116 | m_fConnected(false), | ||
| 117 | m_hReceiveThread(NULL), | ||
| 118 | m_hReceiveStopEvent(NULL), | ||
| 119 | m_pBufReceive(NULL), | ||
| 120 | m_cbBufReceive(0), | ||
| 121 | m_pBufSend(NULL), | ||
| 122 | m_cbBufSend(0), | ||
| 123 | ExitCode(ERROR_INSTALL_FAILURE) | ||
| 124 | { | ||
| 125 | SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED)); | ||
| 126 | m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
| 127 | } | ||
| 128 | |||
| 129 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 130 | // RemoteMsiSession destructor | ||
| 131 | // | ||
| 132 | // Closes any open handles and frees any allocated memory. | ||
| 133 | // | ||
| 134 | ~RemoteMsiSession() | ||
| 135 | { | ||
| 136 | WaitExitCode(); | ||
| 137 | if (m_hPipe != NULL) | ||
| 138 | { | ||
| 139 | CloseHandle(m_hPipe); | ||
| 140 | m_hPipe = NULL; | ||
| 141 | } | ||
| 142 | if (m_overlapped.hEvent != NULL) | ||
| 143 | { | ||
| 144 | CloseHandle(m_overlapped.hEvent); | ||
| 145 | m_overlapped.hEvent = NULL; | ||
| 146 | } | ||
| 147 | if (m_szPipeName != NULL) | ||
| 148 | { | ||
| 149 | delete[] m_szPipeName; | ||
| 150 | m_szPipeName = NULL; | ||
| 151 | } | ||
| 152 | if (m_pBufReceive != NULL) | ||
| 153 | { | ||
| 154 | SecureZeroMemory(m_pBufReceive, m_cbBufReceive); | ||
| 155 | delete[] m_pBufReceive; | ||
| 156 | m_pBufReceive = NULL; | ||
| 157 | } | ||
| 158 | if (m_pBufSend != NULL) | ||
| 159 | { | ||
| 160 | SecureZeroMemory(m_pBufSend, m_cbBufSend); | ||
| 161 | delete[] m_pBufSend; | ||
| 162 | m_pBufSend = NULL; | ||
| 163 | } | ||
| 164 | m_fConnecting = false; | ||
| 165 | m_fConnected = false; | ||
| 166 | } | ||
| 167 | |||
| 168 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 169 | // RemoteMsiSession::WaitExitCode() | ||
| 170 | // | ||
| 171 | // Waits for the server processing thread to complete. | ||
| 172 | // | ||
| 173 | void WaitExitCode() | ||
| 174 | { | ||
| 175 | if (m_hReceiveThread != NULL) | ||
| 176 | { | ||
| 177 | SetEvent(m_hReceiveStopEvent); | ||
| 178 | WaitForSingleObject(m_hReceiveThread, INFINITE); | ||
| 179 | CloseHandle(m_hReceiveThread); | ||
| 180 | m_hReceiveThread = NULL; | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 185 | // RemoteMsiSession::Connect() | ||
| 186 | // | ||
| 187 | // Connects the inter-process communication channel. | ||
| 188 | // (Currently implemented as a named pipe.) | ||
| 189 | // | ||
| 190 | // This method must be called first by the server process, then by the client | ||
| 191 | // process. The method does not block; the server will asynchronously wait | ||
| 192 | // for the client process to make the connection. | ||
| 193 | // | ||
| 194 | // Returns: 0 on success, Win32 error code on failure. | ||
| 195 | // | ||
| 196 | virtual DWORD Connect() | ||
| 197 | { | ||
| 198 | const wchar_t* szPipePrefix = L"\\\\.\\pipe\\"; | ||
| 199 | size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1; | ||
| 200 | m_szPipeName = new wchar_t[cchPipeNameBuf]; | ||
| 201 | |||
| 202 | if (m_szPipeName == NULL) | ||
| 203 | { | ||
| 204 | return ERROR_OUTOFMEMORY; | ||
| 205 | } | ||
| 206 | else | ||
| 207 | { | ||
| 208 | wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix); | ||
| 209 | wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName); | ||
| 210 | |||
| 211 | if (m_fServer) | ||
| 212 | { | ||
| 213 | return this->ConnectPipeServer(); | ||
| 214 | } | ||
| 215 | else | ||
| 216 | { | ||
| 217 | return this->ConnectPipeClient(); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 223 | // RemoteMsiSession::IsConnected() | ||
| 224 | // | ||
| 225 | // Checks if the server process and client process are currently connected. | ||
| 226 | // | ||
| 227 | virtual bool IsConnected() const | ||
| 228 | { | ||
| 229 | return m_fConnected; | ||
| 230 | } | ||
| 231 | |||
| 232 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 233 | // RemoteMsiSession::ProcessRequests() | ||
| 234 | // | ||
| 235 | // For use by the service process. Watches for requests in the input buffer and calls | ||
| 236 | // the callback for each one. | ||
| 237 | // | ||
| 238 | // This method does not block; it spawns a separate thread to do the work. | ||
| 239 | // | ||
| 240 | // Returns: 0 on success, Win32 error code on failure. | ||
| 241 | // | ||
| 242 | virtual DWORD ProcessRequests() | ||
| 243 | { | ||
| 244 | return this->StartProcessingReqests(); | ||
| 245 | } | ||
| 246 | |||
| 247 | ///////////////////////////////////////////////////////////////////////////////////// | ||
| 248 | // RemoteMsiSession::SendRequest() | ||
| 249 | // | ||
| 250 | // For use by the client process. Sends a request to the server and | ||
| 251 | // synchronously waits on a response, up to the timeout value. | ||
| 252 | // | ||
| 253 | // id - ID code of the MSI API call being requested. | ||
| 254 | // | ||
| 255 | // pRequest - Pointer to a data structure containing request parameters. | ||
| 256 | // | ||
| 257 | // ppResponse - [OUT] Pointer to a location that receives the response parameters. | ||
| 258 | // | ||
| 259 | // Returns: 0 on success, Win32 error code on failure. | ||
| 260 | // Returns WAIT_TIMEOUT if no response was received in time. | ||
| 261 | // | ||
| 262 | virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse) | ||
| 263 | { | ||
| 264 | if (m_fServer) | ||
| 265 | { | ||
| 266 | return ERROR_INVALID_OPERATION; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (!m_fConnected) | ||
| 270 | { | ||
| 271 | *ppResponse = NULL; | ||
| 272 | return 0; | ||
| 273 | } | ||
| 274 | |||
| 275 | DWORD dwRet = this->SendRequest(id, pRequest); | ||
| 276 | if (dwRet != 0) | ||
| 277 | { | ||
| 278 | return dwRet; | ||
| 279 | } | ||
| 280 | |||
| 281 | if (id != EndSession) | ||
| 282 | { | ||
| 283 | static RequestData response; | ||
| 284 | if (ppResponse != NULL) | ||
| 285 | { | ||
| 286 | *ppResponse = &response; | ||
| 287 | } | ||
| 288 | |||
| 289 | return this->ReceiveResponse(id, &response); | ||
| 290 | } | ||
| 291 | else | ||
| 292 | { | ||
| 293 | CloseHandle(m_hPipe); | ||
| 294 | m_hPipe = NULL; | ||
| 295 | m_fConnected = false; | ||
| 296 | return 0; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | private: | ||
| 301 | |||
| 302 | // | ||
| 303 | // Do not allow assignment. | ||
| 304 | // | ||
| 305 | RemoteMsiSession& operator=(const RemoteMsiSession&); | ||
| 306 | |||
| 307 | // | ||
| 308 | // Called only by the server process. | ||
| 309 | // Create a new thread to handle receiving requests. | ||
| 310 | // | ||
| 311 | DWORD StartProcessingReqests() | ||
| 312 | { | ||
| 313 | if (!m_fServer || m_hReceiveStopEvent != NULL) | ||
| 314 | { | ||
| 315 | return ERROR_INVALID_OPERATION; | ||
| 316 | } | ||
| 317 | |||
| 318 | DWORD dwRet = 0; | ||
| 319 | |||
| 320 | m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
| 321 | |||
| 322 | if (m_hReceiveStopEvent == NULL) | ||
| 323 | { | ||
| 324 | dwRet = GetLastError(); | ||
| 325 | } | ||
| 326 | else | ||
| 327 | { | ||
| 328 | if (m_hReceiveThread != NULL) | ||
| 329 | { | ||
| 330 | CloseHandle(m_hReceiveThread); | ||
| 331 | } | ||
| 332 | |||
| 333 | m_hReceiveThread = CreateThread(NULL, 0, | ||
| 334 | RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL); | ||
| 335 | |||
| 336 | if (m_hReceiveThread == NULL) | ||
| 337 | { | ||
| 338 | dwRet = GetLastError(); | ||
| 339 | CloseHandle(m_hReceiveStopEvent); | ||
| 340 | m_hReceiveStopEvent = NULL; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | return dwRet; | ||
| 345 | } | ||
| 346 | |||
| 347 | // | ||
| 348 | // Called only by the watcher process. | ||
| 349 | // First verify the connection is complete. Then continually read and parse messages, | ||
| 350 | // invoke the callback, and send the replies. | ||
| 351 | // | ||
| 352 | static DWORD WINAPI ProcessRequestsThreadStatic(void* pv) | ||
| 353 | { | ||
| 354 | return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread(); | ||
| 355 | } | ||
| 356 | |||
| 357 | DWORD ProcessRequestsThread() | ||
| 358 | { | ||
| 359 | DWORD dwRet; | ||
| 360 | |||
| 361 | dwRet = CompleteConnection(); | ||
| 362 | if (dwRet != 0) | ||
| 363 | { | ||
| 364 | if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0; | ||
| 365 | } | ||
| 366 | |||
| 367 | while (m_fConnected) | ||
| 368 | { | ||
| 369 | RequestId id; | ||
| 370 | RequestData req; | ||
| 371 | dwRet = ReceiveRequest(&id, &req); | ||
| 372 | if (dwRet != 0) | ||
| 373 | { | ||
| 374 | if (dwRet == ERROR_OPERATION_ABORTED || | ||
| 375 | dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA) | ||
| 376 | { | ||
| 377 | dwRet = 0; | ||
| 378 | } | ||
| 379 | } | ||
| 380 | else | ||
| 381 | { | ||
| 382 | RequestData resp; | ||
| 383 | ProcessRequest(id, &req, &resp); | ||
| 384 | |||
| 385 | if (id == EndSession) | ||
| 386 | { | ||
| 387 | break; | ||
| 388 | } | ||
| 389 | |||
| 390 | dwRet = SendResponse(id, &resp); | ||
| 391 | if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA) | ||
| 392 | { | ||
| 393 | dwRet = 0; | ||
| 394 | } | ||
| 395 | } | ||
| 396 | } | ||
| 397 | |||
| 398 | CloseHandle(m_hReceiveStopEvent); | ||
| 399 | m_hReceiveStopEvent = NULL; | ||
| 400 | return dwRet; | ||
| 401 | } | ||
| 402 | |||
| 403 | // | ||
| 404 | // Called only by the server process's receive thread. | ||
| 405 | // Read one request into a RequestData object. | ||
| 406 | // | ||
| 407 | DWORD ReceiveRequest(RequestId* pId, RequestData* pReq) | ||
| 408 | { | ||
| 409 | DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId)); | ||
| 410 | |||
| 411 | if (dwRet == 0) | ||
| 412 | { | ||
| 413 | dwRet = this->ReadRequestData(pReq); | ||
| 414 | } | ||
| 415 | |||
| 416 | return dwRet; | ||
| 417 | } | ||
| 418 | |||
| 419 | // | ||
| 420 | // Called by the server process's receive thread or the client's request call | ||
| 421 | // to read the response. Read data from the pipe, allowing interruption by the | ||
| 422 | // stop event if on the server. | ||
| 423 | // | ||
| 424 | DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead) | ||
| 425 | { | ||
| 426 | DWORD dwRet = 0; | ||
| 427 | DWORD dwTotalBytesRead = 0; | ||
| 428 | |||
| 429 | while (dwRet == 0 && dwTotalBytesRead < cbRead) | ||
| 430 | { | ||
| 431 | DWORD dwBytesReadThisTime; | ||
| 432 | ResetEvent(m_overlapped.hEvent); | ||
| 433 | if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped)) | ||
| 434 | { | ||
| 435 | dwRet = GetLastError(); | ||
| 436 | if (dwRet == ERROR_IO_PENDING) | ||
| 437 | { | ||
| 438 | if (m_fServer) | ||
| 439 | { | ||
| 440 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
| 441 | dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
| 442 | } | ||
| 443 | else | ||
| 444 | { | ||
| 445 | dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); | ||
| 446 | } | ||
| 447 | |||
| 448 | if (dwRet == WAIT_OBJECT_0) | ||
| 449 | { | ||
| 450 | if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE)) | ||
| 451 | { | ||
| 452 | dwRet = GetLastError(); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | else if (dwRet == WAIT_FAILED) | ||
| 456 | { | ||
| 457 | dwRet = GetLastError(); | ||
| 458 | } | ||
| 459 | else | ||
| 460 | { | ||
| 461 | dwRet = ERROR_OPERATION_ABORTED; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | dwTotalBytesRead += dwBytesReadThisTime; | ||
| 467 | } | ||
| 468 | |||
| 469 | if (dwRet != 0) | ||
| 470 | { | ||
| 471 | if (m_fServer) | ||
| 472 | { | ||
| 473 | CancelIo(m_hPipe); | ||
| 474 | DisconnectNamedPipe(m_hPipe); | ||
| 475 | } | ||
| 476 | else | ||
| 477 | { | ||
| 478 | CloseHandle(m_hPipe); | ||
| 479 | m_hPipe = NULL; | ||
| 480 | } | ||
| 481 | m_fConnected = false; | ||
| 482 | } | ||
| 483 | |||
| 484 | return dwRet; | ||
| 485 | } | ||
| 486 | |||
| 487 | // | ||
| 488 | // Called only by the server process. | ||
| 489 | // Given a request, invoke the MSI API and return the response. | ||
| 490 | // This is implemented in RemoteMsi.cpp. | ||
| 491 | // | ||
| 492 | void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp); | ||
| 493 | |||
| 494 | // | ||
| 495 | // Called only by the client process. | ||
| 496 | // Send request data over the pipe. | ||
| 497 | // | ||
| 498 | DWORD SendRequest(RequestId id, const RequestData* pRequest) | ||
| 499 | { | ||
| 500 | DWORD dwRet = WriteRequestData(id, pRequest); | ||
| 501 | |||
| 502 | if (dwRet != 0) | ||
| 503 | { | ||
| 504 | m_fConnected = false; | ||
| 505 | CloseHandle(m_hPipe); | ||
| 506 | m_hPipe = NULL; | ||
| 507 | } | ||
| 508 | |||
| 509 | return dwRet; | ||
| 510 | } | ||
| 511 | |||
| 512 | // | ||
| 513 | // Called only by the server process. | ||
| 514 | // Just send a response over the pipe. | ||
| 515 | // | ||
| 516 | DWORD SendResponse(RequestId id, const RequestData* pResp) | ||
| 517 | { | ||
| 518 | DWORD dwRet = WriteRequestData(id, pResp); | ||
| 519 | |||
| 520 | if (dwRet != 0) | ||
| 521 | { | ||
| 522 | DisconnectNamedPipe(m_hPipe); | ||
| 523 | m_fConnected = false; | ||
| 524 | } | ||
| 525 | |||
| 526 | return dwRet; | ||
| 527 | } | ||
| 528 | |||
| 529 | // | ||
| 530 | // Called either by the client or server process. | ||
| 531 | // Writes data to the pipe for a request or response. | ||
| 532 | // | ||
| 533 | DWORD WriteRequestData(RequestId id, const RequestData* pReq) | ||
| 534 | { | ||
| 535 | DWORD dwRet = 0; | ||
| 536 | |||
| 537 | RequestData req = *pReq; // Make a copy because the const data can't be changed. | ||
| 538 | |||
| 539 | dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId)); | ||
| 540 | if (dwRet != 0) | ||
| 541 | { | ||
| 542 | return dwRet; | ||
| 543 | } | ||
| 544 | |||
| 545 | BYTE* sValues[MAX_REQUEST_FIELDS] = {0}; | ||
| 546 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
| 547 | { | ||
| 548 | if (req.fields[i].vt == VT_LPWSTR) | ||
| 549 | { | ||
| 550 | sValues[i] = (BYTE*) req.fields[i].szValue; | ||
| 551 | req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue); | ||
| 552 | } | ||
| 553 | else if (req.fields[i].vt == VT_STREAM) | ||
| 554 | { | ||
| 555 | sValues[i] = req.fields[i].sValue; | ||
| 556 | req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue; | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData)); | ||
| 561 | if (dwRet != 0) | ||
| 562 | { | ||
| 563 | return dwRet; | ||
| 564 | } | ||
| 565 | |||
| 566 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
| 567 | { | ||
| 568 | if (sValues[i] != NULL) | ||
| 569 | { | ||
| 570 | DWORD cbValue; | ||
| 571 | if (req.fields[i].vt == VT_LPWSTR) | ||
| 572 | { | ||
| 573 | cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR); | ||
| 574 | } | ||
| 575 | else | ||
| 576 | { | ||
| 577 | cbValue = req.fields[i].cbValue; | ||
| 578 | } | ||
| 579 | |||
| 580 | dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue); | ||
| 581 | if (dwRet != 0) | ||
| 582 | { | ||
| 583 | break; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | return dwRet; | ||
| 589 | } | ||
| 590 | |||
| 591 | // | ||
| 592 | // Called when writing a request or response. Writes data to | ||
| 593 | // the pipe, allowing interruption by the stop event if on the server. | ||
| 594 | // | ||
| 595 | DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite) | ||
| 596 | { | ||
| 597 | DWORD dwRet = 0; | ||
| 598 | DWORD dwTotalBytesWritten = 0; | ||
| 599 | |||
| 600 | while (dwRet == 0 && dwTotalBytesWritten < cbWrite) | ||
| 601 | { | ||
| 602 | DWORD dwBytesWrittenThisTime; | ||
| 603 | ResetEvent(m_overlapped.hEvent); | ||
| 604 | if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped)) | ||
| 605 | { | ||
| 606 | dwRet = GetLastError(); | ||
| 607 | if (dwRet == ERROR_IO_PENDING) | ||
| 608 | { | ||
| 609 | if (m_fServer) | ||
| 610 | { | ||
| 611 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
| 612 | dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
| 613 | } | ||
| 614 | else | ||
| 615 | { | ||
| 616 | dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); | ||
| 617 | } | ||
| 618 | |||
| 619 | if (dwRet == WAIT_OBJECT_0) | ||
| 620 | { | ||
| 621 | if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE)) | ||
| 622 | { | ||
| 623 | dwRet = GetLastError(); | ||
| 624 | } | ||
| 625 | } | ||
| 626 | else if (dwRet == WAIT_FAILED) | ||
| 627 | { | ||
| 628 | dwRet = GetLastError(); | ||
| 629 | } | ||
| 630 | else | ||
| 631 | { | ||
| 632 | dwRet = ERROR_OPERATION_ABORTED; | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | dwTotalBytesWritten += dwBytesWrittenThisTime; | ||
| 638 | } | ||
| 639 | |||
| 640 | return dwRet; | ||
| 641 | } | ||
| 642 | |||
| 643 | // | ||
| 644 | // Called either by the client or server process. | ||
| 645 | // Reads data from the pipe for a request or response. | ||
| 646 | // | ||
| 647 | DWORD ReadRequestData(RequestData* pReq) | ||
| 648 | { | ||
| 649 | DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData)); | ||
| 650 | |||
| 651 | if (dwRet == 0) | ||
| 652 | { | ||
| 653 | DWORD cbData = 0; | ||
| 654 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
| 655 | { | ||
| 656 | if (pReq->fields[i].vt == VT_LPWSTR) | ||
| 657 | { | ||
| 658 | cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); | ||
| 659 | } | ||
| 660 | else if (pReq->fields[i].vt == VT_STREAM) | ||
| 661 | { | ||
| 662 | cbData += pReq->fields[i].cbValue; | ||
| 663 | } | ||
| 664 | } | ||
| 665 | |||
| 666 | if (cbData > 0) | ||
| 667 | { | ||
| 668 | if (!CheckRequestDataBuf(cbData)) | ||
| 669 | { | ||
| 670 | return ERROR_OUTOFMEMORY; | ||
| 671 | } | ||
| 672 | |||
| 673 | dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData); | ||
| 674 | if (dwRet == 0) | ||
| 675 | { | ||
| 676 | DWORD dwOffset = 0; | ||
| 677 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
| 678 | { | ||
| 679 | if (pReq->fields[i].vt == VT_LPWSTR) | ||
| 680 | { | ||
| 681 | LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset); | ||
| 682 | dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); | ||
| 683 | pReq->fields[i].szValue = szTemp; | ||
| 684 | } | ||
| 685 | else if (pReq->fields[i].vt == VT_STREAM) | ||
| 686 | { | ||
| 687 | BYTE* sTemp = m_pBufReceive + dwOffset; | ||
| 688 | dwOffset += pReq->fields[i].cbValue; | ||
| 689 | pReq->fields[i].sValue = sTemp; | ||
| 690 | } | ||
| 691 | } | ||
| 692 | } | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | return dwRet; | ||
| 697 | } | ||
| 698 | |||
| 699 | // | ||
| 700 | // Called only by the client process. | ||
| 701 | // Wait for a response on the pipe. If no response is received before the timeout, | ||
| 702 | // then give up and close the connection. | ||
| 703 | // | ||
| 704 | DWORD ReceiveResponse(RequestId id, RequestData* pResp) | ||
| 705 | { | ||
| 706 | RequestId responseId; | ||
| 707 | DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId)); | ||
| 708 | if (dwRet == 0 && responseId != id) | ||
| 709 | { | ||
| 710 | dwRet = ERROR_OPERATION_ABORTED; | ||
| 711 | } | ||
| 712 | |||
| 713 | if (dwRet == 0) | ||
| 714 | { | ||
| 715 | dwRet = this->ReadRequestData(pResp); | ||
| 716 | } | ||
| 717 | |||
| 718 | return dwRet; | ||
| 719 | } | ||
| 720 | |||
| 721 | // | ||
| 722 | // Called only by the server process's receive thread. | ||
| 723 | // Try to complete and verify an asynchronous connection operation. | ||
| 724 | // | ||
| 725 | DWORD CompleteConnection() | ||
| 726 | { | ||
| 727 | DWORD dwRet = 0; | ||
| 728 | if (m_fConnecting) | ||
| 729 | { | ||
| 730 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
| 731 | DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
| 732 | |||
| 733 | if (dwWaitRes == WAIT_OBJECT_0) | ||
| 734 | { | ||
| 735 | m_fConnecting = false; | ||
| 736 | |||
| 737 | DWORD dwUnused; | ||
| 738 | if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE)) | ||
| 739 | { | ||
| 740 | m_fConnected = true; | ||
| 741 | } | ||
| 742 | else | ||
| 743 | { | ||
| 744 | dwRet = GetLastError(); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | else if (dwWaitRes == WAIT_FAILED) | ||
| 748 | { | ||
| 749 | CancelIo(m_hPipe); | ||
| 750 | dwRet = GetLastError(); | ||
| 751 | } | ||
| 752 | else | ||
| 753 | { | ||
| 754 | CancelIo(m_hPipe); | ||
| 755 | dwRet = ERROR_OPERATION_ABORTED; | ||
| 756 | } | ||
| 757 | } | ||
| 758 | return dwRet; | ||
| 759 | } | ||
| 760 | |||
| 761 | // | ||
| 762 | // Called only by the server process. | ||
| 763 | // Creates a named pipe instance and begins asynchronously waiting | ||
| 764 | // for a connection from the client process. | ||
| 765 | // | ||
| 766 | DWORD ConnectPipeServer() | ||
| 767 | { | ||
| 768 | DWORD dwRet = 0; | ||
| 769 | const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes | ||
| 770 | m_hPipe = CreateNamedPipe( | ||
| 771 | m_szPipeName, | ||
| 772 | PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, | ||
| 773 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, | ||
| 774 | 1, BUFSIZE, BUFSIZE, 0, NULL); | ||
| 775 | if (m_hPipe == INVALID_HANDLE_VALUE) | ||
| 776 | { | ||
| 777 | m_hPipe = NULL; | ||
| 778 | dwRet = GetLastError(); | ||
| 779 | } | ||
| 780 | else if (ConnectNamedPipe(m_hPipe, &m_overlapped)) | ||
| 781 | { | ||
| 782 | m_fConnected = true; | ||
| 783 | } | ||
| 784 | else | ||
| 785 | { | ||
| 786 | dwRet = GetLastError(); | ||
| 787 | |||
| 788 | if (dwRet == ERROR_PIPE_BUSY) | ||
| 789 | { | ||
| 790 | // All pipe instances are busy, so wait for a maximum of 20 seconds | ||
| 791 | dwRet = 0; | ||
| 792 | if (WaitNamedPipe(m_szPipeName, 20000)) | ||
| 793 | { | ||
| 794 | m_fConnected = true; | ||
| 795 | } | ||
| 796 | else | ||
| 797 | { | ||
| 798 | dwRet = GetLastError(); | ||
| 799 | } | ||
| 800 | } | ||
| 801 | |||
| 802 | if (dwRet == ERROR_IO_PENDING) | ||
| 803 | { | ||
| 804 | dwRet = 0; | ||
| 805 | m_fConnecting = true; | ||
| 806 | } | ||
| 807 | } | ||
| 808 | return dwRet; | ||
| 809 | } | ||
| 810 | |||
| 811 | // | ||
| 812 | // Called only by the client process. | ||
| 813 | // Attemps to open a connection to an existing named pipe instance | ||
| 814 | // which should have already been created by the server process. | ||
| 815 | // | ||
| 816 | DWORD ConnectPipeClient() | ||
| 817 | { | ||
| 818 | DWORD dwRet = 0; | ||
| 819 | m_hPipe = CreateFile( | ||
| 820 | m_szPipeName, GENERIC_READ | GENERIC_WRITE, | ||
| 821 | 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); | ||
| 822 | if (m_hPipe != INVALID_HANDLE_VALUE) | ||
| 823 | { | ||
| 824 | m_fConnected = true; | ||
| 825 | } | ||
| 826 | else | ||
| 827 | { | ||
| 828 | m_hPipe = NULL; | ||
| 829 | dwRet = GetLastError(); | ||
| 830 | } | ||
| 831 | return dwRet; | ||
| 832 | } | ||
| 833 | |||
| 834 | // | ||
| 835 | // Ensures that the request buffer is large enough to hold a request, | ||
| 836 | // reallocating the buffer if necessary. | ||
| 837 | // It will also reduce the buffer size if the previous allocation was very large. | ||
| 838 | // | ||
| 839 | BOOL CheckRequestDataBuf(DWORD cbBuf) | ||
| 840 | { | ||
| 841 | if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive)) | ||
| 842 | { | ||
| 843 | if (m_pBufReceive != NULL) | ||
| 844 | { | ||
| 845 | SecureZeroMemory(m_pBufReceive, m_cbBufReceive); | ||
| 846 | delete[] m_pBufReceive; | ||
| 847 | } | ||
| 848 | m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf); | ||
| 849 | m_pBufReceive = new BYTE[m_cbBufReceive]; | ||
| 850 | if (m_pBufReceive == NULL) | ||
| 851 | { | ||
| 852 | m_cbBufReceive = 0; | ||
| 853 | } | ||
| 854 | } | ||
| 855 | return m_pBufReceive != NULL; | ||
| 856 | } | ||
| 857 | |||
| 858 | private: | ||
| 859 | |||
| 860 | // Name of this instance. | ||
| 861 | const wchar_t* m_szName; | ||
| 862 | |||
| 863 | // "\\.\pipe\name" | ||
| 864 | wchar_t* m_szPipeName; | ||
| 865 | |||
| 866 | // Handle to the pipe instance. | ||
| 867 | HANDLE m_hPipe; | ||
| 868 | |||
| 869 | // Handle to the thread that receives requests. | ||
| 870 | HANDLE m_hReceiveThread; | ||
| 871 | |||
| 872 | // Handle to the event used to signal the receive thread to exit. | ||
| 873 | HANDLE m_hReceiveStopEvent; | ||
| 874 | |||
| 875 | // All pipe I/O is done in overlapped mode to avoid unintentional blocking. | ||
| 876 | OVERLAPPED m_overlapped; | ||
| 877 | |||
| 878 | // Dynamically-resized buffer for receiving requests. | ||
| 879 | BYTE* m_pBufReceive; | ||
| 880 | |||
| 881 | // Current size of the receive request buffer. | ||
| 882 | DWORD m_cbBufReceive; | ||
| 883 | |||
| 884 | // Dynamically-resized buffer for sending requests. | ||
| 885 | wchar_t* m_pBufSend; | ||
| 886 | |||
| 887 | // Current size of the send request buffer. | ||
| 888 | DWORD m_cbBufSend; | ||
| 889 | |||
| 890 | // True if this is the server process, false if this is the client process. | ||
| 891 | const bool m_fServer; | ||
| 892 | |||
| 893 | // True if an asynchronous connection operation is currently in progress. | ||
| 894 | bool m_fConnecting; | ||
| 895 | |||
| 896 | // True if the pipe is currently connected. | ||
| 897 | bool m_fConnected; | ||
| 898 | }; | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp new file mode 100644 index 00000000..06319f1e --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp | |||
| @@ -0,0 +1,363 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | #include "EntryPoints.h" | ||
| 5 | #include "SfxUtil.h" | ||
| 6 | |||
| 7 | #define MANAGED_CAs_OUT_OF_PROC 1 | ||
| 8 | |||
| 9 | HMODULE g_hModule; | ||
| 10 | bool g_fRunningOutOfProc = false; | ||
| 11 | |||
| 12 | RemoteMsiSession* g_pRemote = NULL; | ||
| 13 | |||
| 14 | // Prototypes for local functions. | ||
| 15 | // See the function definitions for comments. | ||
| 16 | |||
| 17 | bool InvokeManagedCustomAction(MSIHANDLE hSession, | ||
| 18 | _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Entry-point for the CA DLL when re-launched as a separate process; | ||
| 22 | /// connects the comm channel for remote MSI APIs, then invokes the | ||
| 23 | /// managed custom action entry-point. | ||
| 24 | /// </summary> | ||
| 25 | /// <remarks> | ||
| 26 | /// Do not change the parameters or calling-convention: RUNDLL32 | ||
| 27 | /// requires this exact signature. | ||
| 28 | /// </remarks> | ||
| 29 | extern "C" | ||
| 30 | void __stdcall InvokeManagedCustomActionOutOfProc( | ||
| 31 | __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) | ||
| 32 | { | ||
| 33 | UNREFERENCED_PARAMETER(hwnd); | ||
| 34 | UNREFERENCED_PARAMETER(hinst); | ||
| 35 | UNREFERENCED_PARAMETER(nCmdShow); | ||
| 36 | |||
| 37 | g_fRunningOutOfProc = true; | ||
| 38 | |||
| 39 | const wchar_t* szSessionName = szCmdLine; | ||
| 40 | MSIHANDLE hSession; | ||
| 41 | const wchar_t* szEntryPoint; | ||
| 42 | |||
| 43 | int i; | ||
| 44 | for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
| 45 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
| 46 | hSession = _wtoi(szCmdLine + i); | ||
| 47 | |||
| 48 | for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
| 49 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
| 50 | szEntryPoint = szCmdLine + i; | ||
| 51 | |||
| 52 | g_pRemote = new RemoteMsiSession(szSessionName, false); | ||
| 53 | g_pRemote->Connect(); | ||
| 54 | |||
| 55 | int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); | ||
| 56 | |||
| 57 | RemoteMsiSession::RequestData requestData; | ||
| 58 | SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); | ||
| 59 | requestData.fields[0].vt = VT_I4; | ||
| 60 | requestData.fields[0].iValue = ret; | ||
| 61 | g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); | ||
| 62 | delete g_pRemote; | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Re-launch this CA DLL as a separate process, and setup a comm channel | ||
| 67 | /// for remote MSI API calls back to this process. | ||
| 68 | /// </summary> | ||
| 69 | int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) | ||
| 70 | { | ||
| 71 | wchar_t szSessionName[100] = {0}; | ||
| 72 | swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); | ||
| 73 | |||
| 74 | RemoteMsiSession remote(szSessionName, true); | ||
| 75 | |||
| 76 | DWORD ret = remote.Connect(); | ||
| 77 | if (ret != 0) | ||
| 78 | { | ||
| 79 | Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); | ||
| 80 | return ERROR_INSTALL_FAILURE; | ||
| 81 | } | ||
| 82 | |||
| 83 | ret = remote.ProcessRequests(); | ||
| 84 | if (ret != 0) | ||
| 85 | { | ||
| 86 | Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); | ||
| 87 | return ERROR_INSTALL_FAILURE; | ||
| 88 | } | ||
| 89 | |||
| 90 | wchar_t szModule[MAX_PATH] = {0}; | ||
| 91 | GetModuleFileName(g_hModule, szModule, MAX_PATH); | ||
| 92 | |||
| 93 | const wchar_t* rundll32 = L"rundll32.exe"; | ||
| 94 | wchar_t szRunDll32Path[MAX_PATH] = {0}; | ||
| 95 | GetSystemDirectory(szRunDll32Path, MAX_PATH); | ||
| 96 | wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); | ||
| 97 | wcscat_s(szRunDll32Path, MAX_PATH, rundll32); | ||
| 98 | |||
| 99 | const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; | ||
| 100 | wchar_t szCommandLine[1024] = {0}; | ||
| 101 | swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", | ||
| 102 | rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); | ||
| 103 | |||
| 104 | STARTUPINFO si; | ||
| 105 | SecureZeroMemory(&si, sizeof(STARTUPINFO)); | ||
| 106 | si.cb = sizeof(STARTUPINFO); | ||
| 107 | |||
| 108 | PROCESS_INFORMATION pi; | ||
| 109 | SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); | ||
| 110 | |||
| 111 | if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, | ||
| 112 | 0, NULL, NULL, &si, &pi)) | ||
| 113 | { | ||
| 114 | DWORD err = GetLastError(); | ||
| 115 | Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); | ||
| 116 | return ERROR_INSTALL_FAILURE; | ||
| 117 | } | ||
| 118 | |||
| 119 | DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); | ||
| 120 | if (dwWait != WAIT_OBJECT_0) | ||
| 121 | { | ||
| 122 | DWORD err = GetLastError(); | ||
| 123 | Log(hSession, L"Failed to wait for CA process. Error code: %d", err); | ||
| 124 | return ERROR_INSTALL_FAILURE; | ||
| 125 | } | ||
| 126 | |||
| 127 | DWORD dwExitCode; | ||
| 128 | BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); | ||
| 129 | if (!bRet) | ||
| 130 | { | ||
| 131 | DWORD err = GetLastError(); | ||
| 132 | Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); | ||
| 133 | return ERROR_INSTALL_FAILURE; | ||
| 134 | } | ||
| 135 | else if (dwExitCode != 0) | ||
| 136 | { | ||
| 137 | Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); | ||
| 138 | return ERROR_INSTALL_FAILURE; | ||
| 139 | } | ||
| 140 | |||
| 141 | CloseHandle(pi.hThread); | ||
| 142 | CloseHandle(pi.hProcess); | ||
| 143 | |||
| 144 | remote.WaitExitCode(); | ||
| 145 | return remote.ExitCode; | ||
| 146 | } | ||
| 147 | |||
| 148 | /// <summary> | ||
| 149 | /// Entrypoint for the managed CA proxy (RemotableNativeMethods) to | ||
| 150 | /// call MSI APIs remotely. | ||
| 151 | /// </summary> | ||
| 152 | void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) | ||
| 153 | { | ||
| 154 | if (g_fRunningOutOfProc) | ||
| 155 | { | ||
| 156 | g_pRemote->SendRequest(id, pRequest, ppResponse); | ||
| 157 | } | ||
| 158 | else | ||
| 159 | { | ||
| 160 | *ppResponse = NULL; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | /// <summary> | ||
| 165 | /// Invokes a managed custom action from native code by | ||
| 166 | /// extracting the package to a temporary working directory | ||
| 167 | /// then hosting the CLR and locating and calling the entrypoint. | ||
| 168 | /// </summary> | ||
| 169 | /// <param name="hSession">Handle to the installation session. | ||
| 170 | /// Passed to custom action entrypoints by the installer engine.</param> | ||
| 171 | /// <param name="szWorkingDir">Directory containing the CA binaries | ||
| 172 | /// and the CustomAction.config file defining the entrypoints. | ||
| 173 | /// This may be NULL, in which case the current module must have | ||
| 174 | /// a concatenated cabinet containing those files, which will be | ||
| 175 | /// extracted to a temporary directory.</param> | ||
| 176 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
| 177 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
| 178 | /// string, or a simple name that maps to a full entrypoint definition | ||
| 179 | /// in CustomAction.config.</param> | ||
| 180 | /// <returns>The value returned by the managed custom action method, | ||
| 181 | /// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns> | ||
| 182 | int InvokeCustomAction(MSIHANDLE hSession, | ||
| 183 | const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) | ||
| 184 | { | ||
| 185 | #ifdef MANAGED_CAs_OUT_OF_PROC | ||
| 186 | if (!g_fRunningOutOfProc && szWorkingDir == NULL) | ||
| 187 | { | ||
| 188 | return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); | ||
| 189 | } | ||
| 190 | #endif | ||
| 191 | |||
| 192 | wchar_t szTempDir[MAX_PATH]; | ||
| 193 | bool fDeleteTemp = false; | ||
| 194 | if (szWorkingDir == NULL) | ||
| 195 | { | ||
| 196 | if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) | ||
| 197 | { | ||
| 198 | return ERROR_INSTALL_FAILURE; | ||
| 199 | } | ||
| 200 | szWorkingDir = szTempDir; | ||
| 201 | fDeleteTemp = true; | ||
| 202 | } | ||
| 203 | |||
| 204 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
| 205 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); | ||
| 206 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); | ||
| 207 | |||
| 208 | const wchar_t* szConfigFile = szConfigFilePath; | ||
| 209 | if (!::PathFileExists(szConfigFilePath)) | ||
| 210 | { | ||
| 211 | szConfigFile = NULL; | ||
| 212 | } | ||
| 213 | |||
| 214 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
| 215 | StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); | ||
| 216 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
| 217 | |||
| 218 | int iResult = ERROR_INSTALL_FAILURE; | ||
| 219 | ICorRuntimeHost* pHost; | ||
| 220 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) | ||
| 221 | { | ||
| 222 | _AppDomain* pAppDomain; | ||
| 223 | if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, | ||
| 224 | szConfigFile, &pAppDomain)) | ||
| 225 | { | ||
| 226 | if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) | ||
| 227 | { | ||
| 228 | iResult = ERROR_INSTALL_FAILURE; | ||
| 229 | } | ||
| 230 | HRESULT hr = pHost->UnloadDomain(pAppDomain); | ||
| 231 | if (FAILED(hr)) | ||
| 232 | { | ||
| 233 | Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); | ||
| 234 | } | ||
| 235 | pAppDomain->Release(); | ||
| 236 | } | ||
| 237 | |||
| 238 | pHost->Stop(); | ||
| 239 | pHost->Release(); | ||
| 240 | } | ||
| 241 | |||
| 242 | if (fDeleteTemp) | ||
| 243 | { | ||
| 244 | DeleteDirectory(szTempDir); | ||
| 245 | } | ||
| 246 | return iResult; | ||
| 247 | } | ||
| 248 | |||
| 249 | /// <summary> | ||
| 250 | /// Called by the system when the DLL is loaded. | ||
| 251 | /// Saves the module handle for later use. | ||
| 252 | /// </summary> | ||
| 253 | BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) | ||
| 254 | { | ||
| 255 | UNREFERENCED_PARAMETER(pReserved); | ||
| 256 | |||
| 257 | switch (dwReason) | ||
| 258 | { | ||
| 259 | case DLL_PROCESS_ATTACH: | ||
| 260 | g_hModule = hModule; | ||
| 261 | break; | ||
| 262 | case DLL_THREAD_ATTACH: | ||
| 263 | case DLL_THREAD_DETACH: | ||
| 264 | case DLL_PROCESS_DETACH: | ||
| 265 | break; | ||
| 266 | } | ||
| 267 | return TRUE; | ||
| 268 | } | ||
| 269 | |||
| 270 | /// <summary> | ||
| 271 | /// Loads and invokes the managed portion of the proxy. | ||
| 272 | /// </summary> | ||
| 273 | /// <param name="hSession">Handle to the installer session, | ||
| 274 | /// used for logging errors and to be passed on to the custom action.</param> | ||
| 275 | /// <param name="pAppDomain">AppDomain which has its application | ||
| 276 | /// base set to the CA working directory.</param> | ||
| 277 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
| 278 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
| 279 | /// string, or a simple name that maps to a full entrypoint definition | ||
| 280 | /// in CustomAction.config.</param> | ||
| 281 | /// <param name="piResult">Return value of the invoked custom | ||
| 282 | /// action method.</param> | ||
| 283 | /// <returns>True if the managed proxy was invoked successfully, | ||
| 284 | /// false if there was some error. Note the custom action itself may | ||
| 285 | /// return an error via piResult while this method still returns true | ||
| 286 | /// since the invocation was successful.</returns> | ||
| 287 | bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
| 288 | const wchar_t* szEntryPoint, int* piResult) | ||
| 289 | { | ||
| 290 | VARIANT vResult; | ||
| 291 | ::VariantInit(&vResult); | ||
| 292 | |||
| 293 | const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); | ||
| 294 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
| 295 | const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; | ||
| 296 | const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); | ||
| 297 | |||
| 298 | _MethodInfo* pCAInvokeMethod; | ||
| 299 | if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, | ||
| 300 | szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) | ||
| 301 | { | ||
| 302 | return false; | ||
| 303 | } | ||
| 304 | |||
| 305 | HRESULT hr; | ||
| 306 | VARIANT vNull; | ||
| 307 | vNull.vt = VT_EMPTY; | ||
| 308 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
| 309 | VARIANT vSessionHandle; | ||
| 310 | vSessionHandle.vt = VT_I4; | ||
| 311 | vSessionHandle.intVal = hSession; | ||
| 312 | LONG index = 0; | ||
| 313 | hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
| 314 | if (FAILED(hr)) goto LExit; | ||
| 315 | VARIANT vEntryPoint; | ||
| 316 | vEntryPoint.vt = VT_BSTR; | ||
| 317 | vEntryPoint.bstrVal = SysAllocString(szEntryPoint); | ||
| 318 | if (vEntryPoint.bstrVal == NULL) | ||
| 319 | { | ||
| 320 | hr = E_OUTOFMEMORY; | ||
| 321 | goto LExit; | ||
| 322 | } | ||
| 323 | index = 1; | ||
| 324 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
| 325 | if (FAILED(hr)) goto LExit; | ||
| 326 | VARIANT vRemotingFunctionPtr; | ||
| 327 | #pragma warning(push) | ||
| 328 | #pragma warning(disable:4127) // conditional expression is constant | ||
| 329 | if (f64bit) | ||
| 330 | #pragma warning(pop) | ||
| 331 | { | ||
| 332 | vRemotingFunctionPtr.vt = VT_I8; | ||
| 333 | vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
| 334 | } | ||
| 335 | else | ||
| 336 | { | ||
| 337 | vRemotingFunctionPtr.vt = VT_I4; | ||
| 338 | #pragma warning(push) | ||
| 339 | #pragma warning(disable:4302) // truncation | ||
| 340 | #pragma warning(disable:4311) // pointer truncation | ||
| 341 | vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
| 342 | #pragma warning(pop) | ||
| 343 | } | ||
| 344 | index = 2; | ||
| 345 | hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); | ||
| 346 | if (FAILED(hr)) goto LExit; | ||
| 347 | |||
| 348 | hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); | ||
| 349 | |||
| 350 | LExit: | ||
| 351 | SafeArrayDestroy(saArgs); | ||
| 352 | pCAInvokeMethod->Release(); | ||
| 353 | |||
| 354 | if (FAILED(hr)) | ||
| 355 | { | ||
| 356 | Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); | ||
| 357 | return false; | ||
| 358 | } | ||
| 359 | |||
| 360 | *piResult = vResult.intVal; | ||
| 361 | return true; | ||
| 362 | } | ||
| 363 | |||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.rc b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc new file mode 100644 index 00000000..4d78194b --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc | |||
| @@ -0,0 +1,10 @@ | |||
| 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 | #define VER_DLL | ||
| 4 | #define VER_LANG_NEUTRAL | ||
| 5 | #define VER_ORIGINAL_FILENAME "SfxCA.dll" | ||
| 6 | #define VER_INTERNAL_NAME "SfxCA" | ||
| 7 | #define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action" | ||
| 8 | |||
| 9 | // Additional resources here | ||
| 10 | |||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj new file mode 100644 index 00000000..aeaaa776 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| 4 | <ItemGroup Label="ProjectConfigurations"> | ||
| 5 | <ProjectConfiguration Include="Debug|Win32"> | ||
| 6 | <Configuration>Debug</Configuration> | ||
| 7 | <Platform>Win32</Platform> | ||
| 8 | </ProjectConfiguration> | ||
| 9 | <ProjectConfiguration Include="Release|Win32"> | ||
| 10 | <Configuration>Release</Configuration> | ||
| 11 | <Platform>Win32</Platform> | ||
| 12 | </ProjectConfiguration> | ||
| 13 | <ProjectConfiguration Include="Debug|x64"> | ||
| 14 | <Configuration>Debug</Configuration> | ||
| 15 | <Platform>x64</Platform> | ||
| 16 | </ProjectConfiguration> | ||
| 17 | <ProjectConfiguration Include="Release|x64"> | ||
| 18 | <Configuration>Release</Configuration> | ||
| 19 | <Platform>x64</Platform> | ||
| 20 | </ProjectConfiguration> | ||
| 21 | </ItemGroup> | ||
| 22 | |||
| 23 | <PropertyGroup Label="Globals"> | ||
| 24 | <ProjectGuid>{55D5BA28-D427-4F53-80C2-FE9EF23C1553}</ProjectGuid> | ||
| 25 | <ConfigurationType>DynamicLibrary</ConfigurationType> | ||
| 26 | <TargetName>SfxCA</TargetName> | ||
| 27 | <PlatformToolset>v142</PlatformToolset> | ||
| 28 | <CharacterSet>Unicode</CharacterSet> | ||
| 29 | <ProjectModuleDefinitionFile>EntryPoints.def</ProjectModuleDefinitionFile> | ||
| 30 | </PropertyGroup> | ||
| 31 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||
| 32 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||
| 33 | <PropertyGroup> | ||
| 34 | <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries> | ||
| 35 | </PropertyGroup> | ||
| 36 | <ItemGroup> | ||
| 37 | <ClCompile Include="ClrHost.cpp" /> | ||
| 38 | <ClCompile Include="Extract.cpp" /> | ||
| 39 | <ClCompile Include="precomp.cpp"> | ||
| 40 | <PrecompiledHeader>Create</PrecompiledHeader> | ||
| 41 | </ClCompile> | ||
| 42 | <ClCompile Include="RemoteMsi.cpp" /> | ||
| 43 | <ClCompile Include="SfxCA.cpp" /> | ||
| 44 | <ClCompile Include="SfxUtil.cpp" /> | ||
| 45 | <ClCompile Include="EmbeddedUI.cpp" /> | ||
| 46 | </ItemGroup> | ||
| 47 | <ItemGroup> | ||
| 48 | <ClInclude Include="precomp.h" /> | ||
| 49 | <ClInclude Include="EntryPoints.h" /> | ||
| 50 | <ClInclude Include="RemoteMsiSession.h" /> | ||
| 51 | <ClInclude Include="SfxUtil.h" /> | ||
| 52 | </ItemGroup> | ||
| 53 | <ItemGroup> | ||
| 54 | <None Include="EntryPoints.def" /> | ||
| 55 | <None Include="packages.config" /> | ||
| 56 | </ItemGroup> | ||
| 57 | <ItemGroup> | ||
| 58 | <ResourceCompile Include="SfxCA.rc" /> | ||
| 59 | </ItemGroup> | ||
| 60 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||
| 61 | <Import Project="..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" /> | ||
| 62 | <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | ||
| 63 | <PropertyGroup> | ||
| 64 | <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | ||
| 65 | </PropertyGroup> | ||
| 66 | <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" /> | ||
| 67 | </Target> | ||
| 68 | </Project> \ No newline at end of file | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters new file mode 100644 index 00000000..a5ebf693 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| 3 | <ItemGroup> | ||
| 4 | <ClCompile Include="ClrHost.cpp"> | ||
| 5 | <Filter>Source Files</Filter> | ||
| 6 | </ClCompile> | ||
| 7 | <ClCompile Include="EmbeddedUI.cpp"> | ||
| 8 | <Filter>Source Files</Filter> | ||
| 9 | </ClCompile> | ||
| 10 | <ClCompile Include="Extract.cpp"> | ||
| 11 | <Filter>Source Files</Filter> | ||
| 12 | </ClCompile> | ||
| 13 | <ClCompile Include="RemoteMsi.cpp"> | ||
| 14 | <Filter>Source Files</Filter> | ||
| 15 | </ClCompile> | ||
| 16 | <ClCompile Include="SfxCA.cpp"> | ||
| 17 | <Filter>Source Files</Filter> | ||
| 18 | </ClCompile> | ||
| 19 | <ClCompile Include="SfxUtil.cpp"> | ||
| 20 | <Filter>Source Files</Filter> | ||
| 21 | </ClCompile> | ||
| 22 | <ClCompile Include="precomp.cpp"> | ||
| 23 | <Filter>Source Files</Filter> | ||
| 24 | </ClCompile> | ||
| 25 | </ItemGroup> | ||
| 26 | <ItemGroup> | ||
| 27 | <ClInclude Include="EntryPoints.h"> | ||
| 28 | <Filter>Header Files</Filter> | ||
| 29 | </ClInclude> | ||
| 30 | <ClInclude Include="precomp.h"> | ||
| 31 | <Filter>Header Files</Filter> | ||
| 32 | </ClInclude> | ||
| 33 | <ClInclude Include="RemoteMsiSession.h"> | ||
| 34 | <Filter>Header Files</Filter> | ||
| 35 | </ClInclude> | ||
| 36 | <ClInclude Include="SfxUtil.h"> | ||
| 37 | <Filter>Header Files</Filter> | ||
| 38 | </ClInclude> | ||
| 39 | </ItemGroup> | ||
| 40 | <ItemGroup> | ||
| 41 | <Filter Include="Resource Files"> | ||
| 42 | <UniqueIdentifier>{81c92f68-18c2-4cd4-a588-5c3616860dd9}</UniqueIdentifier> | ||
| 43 | </Filter> | ||
| 44 | <Filter Include="Header Files"> | ||
| 45 | <UniqueIdentifier>{6cdc30ee-e14d-4679-b92e-3e080535e53b}</UniqueIdentifier> | ||
| 46 | </Filter> | ||
| 47 | <Filter Include="Source Files"> | ||
| 48 | <UniqueIdentifier>{1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d}</UniqueIdentifier> | ||
| 49 | </Filter> | ||
| 50 | </ItemGroup> | ||
| 51 | <ItemGroup> | ||
| 52 | <ResourceCompile Include="SfxCA.rc"> | ||
| 53 | <Filter>Resource Files</Filter> | ||
| 54 | </ResourceCompile> | ||
| 55 | </ItemGroup> | ||
| 56 | <ItemGroup> | ||
| 57 | <None Include="EntryPoints.def"> | ||
| 58 | <Filter>Resource Files</Filter> | ||
| 59 | </None> | ||
| 60 | <None Include="packages.config" /> | ||
| 61 | </ItemGroup> | ||
| 62 | </Project> \ No newline at end of file | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp new file mode 100644 index 00000000..1bf2c5b2 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp | |||
| @@ -0,0 +1,209 @@ | |||
| 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 | #include "precomp.h" | ||
| 4 | #include "SfxUtil.h" | ||
| 5 | |||
| 6 | /// <summary> | ||
| 7 | /// Writes a formatted message to the MSI log. | ||
| 8 | /// Does out-of-proc MSI calls if necessary. | ||
| 9 | /// </summary> | ||
| 10 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) | ||
| 11 | { | ||
| 12 | const int LOG_BUFSIZE = 4096; | ||
| 13 | wchar_t szBuf[LOG_BUFSIZE]; | ||
| 14 | va_list args; | ||
| 15 | va_start(args, szMessage); | ||
| 16 | StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); | ||
| 17 | |||
| 18 | if (!g_fRunningOutOfProc || NULL == g_pRemote) | ||
| 19 | { | ||
| 20 | MSIHANDLE hRec = MsiCreateRecord(1); | ||
| 21 | MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); | ||
| 22 | MsiRecordSetString(hRec, 1, szBuf); | ||
| 23 | MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); | ||
| 24 | MsiCloseHandle(hRec); | ||
| 25 | } | ||
| 26 | else | ||
| 27 | { | ||
| 28 | // Logging is the only remote-MSI operation done from unmanaged code. | ||
| 29 | // It's not very convenient here because part of the infrastructure | ||
| 30 | // for remote MSI APIs is on the managed side. | ||
| 31 | |||
| 32 | RemoteMsiSession::RequestData req; | ||
| 33 | RemoteMsiSession::RequestData* pResp = NULL; | ||
| 34 | SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); | ||
| 35 | |||
| 36 | req.fields[0].vt = VT_UI4; | ||
| 37 | req.fields[0].uiValue = 1; | ||
| 38 | g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); | ||
| 39 | MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; | ||
| 40 | |||
| 41 | req.fields[0].vt = VT_I4; | ||
| 42 | req.fields[0].iValue = (int) hRec; | ||
| 43 | req.fields[1].vt = VT_UI4; | ||
| 44 | req.fields[1].uiValue = 0; | ||
| 45 | req.fields[2].vt = VT_LPWSTR; | ||
| 46 | req.fields[2].szValue = L"SFXCA: [1]"; | ||
| 47 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); | ||
| 48 | |||
| 49 | req.fields[0].vt = VT_I4; | ||
| 50 | req.fields[0].iValue = (int) hRec; | ||
| 51 | req.fields[1].vt = VT_UI4; | ||
| 52 | req.fields[1].uiValue = 1; | ||
| 53 | req.fields[2].vt = VT_LPWSTR; | ||
| 54 | req.fields[2].szValue = szBuf; | ||
| 55 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); | ||
| 56 | |||
| 57 | req.fields[0].vt = VT_I4; | ||
| 58 | req.fields[0].iValue = (int) hSession; | ||
| 59 | req.fields[1].vt = VT_I4; | ||
| 60 | req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; | ||
| 61 | req.fields[2].vt = VT_I4; | ||
| 62 | req.fields[2].iValue = (int) hRec; | ||
| 63 | g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); | ||
| 64 | |||
| 65 | req.fields[0].vt = VT_I4; | ||
| 66 | req.fields[0].iValue = (int) hRec; | ||
| 67 | req.fields[1].vt = VT_EMPTY; | ||
| 68 | req.fields[2].vt = VT_EMPTY; | ||
| 69 | g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Deletes a directory, including all files and subdirectories. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="szDir">Path to the directory to delete, | ||
| 77 | /// not including a trailing backslash.</param> | ||
| 78 | /// <returns>True if the directory was successfully deleted, or false | ||
| 79 | /// if the deletion failed (most likely because some files were locked). | ||
| 80 | /// </returns> | ||
| 81 | bool DeleteDirectory(const wchar_t* szDir) | ||
| 82 | { | ||
| 83 | size_t cchDir = wcslen(szDir); | ||
| 84 | size_t cchPathBuf = cchDir + 3 + MAX_PATH; | ||
| 85 | wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); | ||
| 86 | if (szPath == NULL) return false; | ||
| 87 | StringCchCopy(szPath, cchPathBuf, szDir); | ||
| 88 | StringCchCat(szPath, cchPathBuf, L"\\*"); | ||
| 89 | WIN32_FIND_DATA fd; | ||
| 90 | HANDLE hSearch = FindFirstFile(szPath, &fd); | ||
| 91 | while (hSearch != INVALID_HANDLE_VALUE) | ||
| 92 | { | ||
| 93 | StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); | ||
| 94 | if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) | ||
| 95 | { | ||
| 96 | if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) | ||
| 97 | { | ||
| 98 | DeleteDirectory(szPath); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | DeleteFile(szPath); | ||
| 104 | } | ||
| 105 | if (!FindNextFile(hSearch, &fd)) | ||
| 106 | { | ||
| 107 | FindClose(hSearch); | ||
| 108 | hSearch = INVALID_HANDLE_VALUE; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | return RemoveDirectory(szDir) != 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | bool DirectoryExists(const wchar_t* szDir) | ||
| 115 | { | ||
| 116 | if (szDir != NULL) | ||
| 117 | { | ||
| 118 | DWORD dwAttrs = GetFileAttributes(szDir); | ||
| 119 | if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) | ||
| 120 | { | ||
| 121 | return true; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | /// <summary> | ||
| 128 | /// Extracts a cabinet that is concatenated to a module | ||
| 129 | /// to a new temporary directory. | ||
| 130 | /// </summary> | ||
| 131 | /// <param name="hSession">Handle to the installer session, | ||
| 132 | /// used just for logging.</param> | ||
| 133 | /// <param name="hModule">Module that has the concatenated cabinet.</param> | ||
| 134 | /// <param name="szTempDir">Buffer for returning the path of the | ||
| 135 | /// created temp directory.</param> | ||
| 136 | /// <param name="cchTempDirBuf">Size in characters of the buffer. | ||
| 137 | /// <returns>True if the files were extracted, or false if the | ||
| 138 | /// buffer was too small or the directory could not be created | ||
| 139 | /// or the extraction failed for some other reason.</returns> | ||
| 140 | __success(return != false) | ||
| 141 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, | ||
| 142 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) | ||
| 143 | { | ||
| 144 | wchar_t szModule[MAX_PATH]; | ||
| 145 | DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); | ||
| 146 | if (cchCopied == 0) | ||
| 147 | { | ||
| 148 | Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | else if (cchCopied == MAX_PATH - 1) | ||
| 152 | { | ||
| 153 | Log(hSession, L"Failed to get module path -- path is too long."); | ||
| 154 | return false; | ||
| 155 | } | ||
| 156 | |||
| 157 | if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) | ||
| 158 | { | ||
| 159 | Log(hSession, L"Temp directory buffer is NULL or too small."); | ||
| 160 | return false; | ||
| 161 | } | ||
| 162 | StringCchCopy(szTempDir, cchTempDirBuf, szModule); | ||
| 163 | StringCchCat(szTempDir, cchTempDirBuf, L"-"); | ||
| 164 | |||
| 165 | DWORD cchTempDir = (DWORD) wcslen(szTempDir); | ||
| 166 | for (int i = 0; DirectoryExists(szTempDir); i++) | ||
| 167 | { | ||
| 168 | swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); | ||
| 169 | } | ||
| 170 | |||
| 171 | if (!CreateDirectory(szTempDir, NULL)) | ||
| 172 | { | ||
| 173 | cchCopied = GetTempPath(cchTempDirBuf, szTempDir); | ||
| 174 | if (cchCopied == 0 || cchCopied >= cchTempDirBuf) | ||
| 175 | { | ||
| 176 | Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError()); | ||
| 177 | return false; | ||
| 178 | } | ||
| 179 | |||
| 180 | wchar_t* szModuleName = wcsrchr(szModule, L'\\'); | ||
| 181 | if (szModuleName == NULL) szModuleName = szModule; | ||
| 182 | else szModuleName = szModuleName + 1; | ||
| 183 | StringCchCat(szTempDir, cchTempDirBuf, szModuleName); | ||
| 184 | StringCchCat(szTempDir, cchTempDirBuf, L"-"); | ||
| 185 | |||
| 186 | cchTempDir = (DWORD) wcslen(szTempDir); | ||
| 187 | for (int i = 0; DirectoryExists(szTempDir); i++) | ||
| 188 | { | ||
| 189 | swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); | ||
| 190 | } | ||
| 191 | |||
| 192 | if (!CreateDirectory(szTempDir, NULL)) | ||
| 193 | { | ||
| 194 | Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError()); | ||
| 195 | return false; | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); | ||
| 200 | int err = ExtractCabinet(szModule, szTempDir); | ||
| 201 | if (err != 0) | ||
| 202 | { | ||
| 203 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); | ||
| 204 | DeleteDirectory(szTempDir); | ||
| 205 | return false; | ||
| 206 | } | ||
| 207 | return true; | ||
| 208 | } | ||
| 209 | |||
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.h b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h new file mode 100644 index 00000000..af12d8dd --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h | |||
| @@ -0,0 +1,31 @@ | |||
| 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 | #include "RemoteMsiSession.h" | ||
| 4 | |||
| 5 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); | ||
| 6 | |||
| 7 | int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); | ||
| 8 | |||
| 9 | bool DeleteDirectory(const wchar_t* szDir); | ||
| 10 | |||
| 11 | __success(return != false) | ||
| 12 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, | ||
| 13 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); | ||
| 14 | |||
| 15 | bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, | ||
| 16 | const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); | ||
| 17 | |||
| 18 | bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, | ||
| 19 | const wchar_t* szName, const wchar_t* szAppBase, | ||
| 20 | const wchar_t* szConfigFile, _AppDomain** ppAppDomain); | ||
| 21 | |||
| 22 | bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
| 23 | const wchar_t* szAssembly, const wchar_t* szClass, | ||
| 24 | const wchar_t* szMethod, _MethodInfo** ppCAMethod); | ||
| 25 | |||
| 26 | extern HMODULE g_hModule; | ||
| 27 | extern bool g_fRunningOutOfProc; | ||
| 28 | |||
| 29 | extern RemoteMsiSession* g_pRemote; | ||
| 30 | |||
| 31 | |||
diff --git a/src/samples/Dtf/Tools/SfxCA/packages.config b/src/samples/Dtf/Tools/SfxCA/packages.config new file mode 100644 index 00000000..1ffaa8df --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/packages.config | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <packages> | ||
| 3 | <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" /> | ||
| 4 | </packages> \ No newline at end of file | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.cpp b/src/samples/Dtf/Tools/SfxCA/precomp.cpp new file mode 100644 index 00000000..ce82c1d7 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/precomp.cpp | |||
| @@ -0,0 +1,3 @@ | |||
| 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 | #include "precomp.h" \ No newline at end of file | ||
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.h b/src/samples/Dtf/Tools/SfxCA/precomp.h new file mode 100644 index 00000000..48d4f011 --- /dev/null +++ b/src/samples/Dtf/Tools/SfxCA/precomp.h | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | #pragma once | ||
| 2 | // 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. | ||
| 3 | |||
| 4 | |||
| 5 | #include <windows.h> | ||
| 6 | #include <msiquery.h> | ||
| 7 | #include <strsafe.h> | ||
| 8 | #include <mscoree.h> | ||
| 9 | #include <io.h> | ||
| 10 | #include <fcntl.h> | ||
| 11 | #include <share.h> | ||
| 12 | #include <shlwapi.h> | ||
| 13 | #include <sys/stat.h> | ||
| 14 | #include <malloc.h> | ||
| 15 | #include <fdi.h> | ||
| 16 | #include <msiquery.h> | ||
| 17 | #import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent", "CorReportEvent") | ||
| 18 | using namespace mscorlib; | ||
diff --git a/src/samples/Dtf/Tools/Tools.proj b/src/samples/Dtf/Tools/Tools.proj new file mode 100644 index 00000000..751247dc --- /dev/null +++ b/src/samples/Dtf/Tools/Tools.proj | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version='1.0' encoding='utf-8'?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | |||
| 5 | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | ||
| 6 | <ItemGroup> | ||
| 7 | <ProjectReference Include="MakeSfxCA\MakeSfxCA.csproj" /> | ||
| 8 | <ProjectReference Include="SfxCA\SfxCA.vcxproj" /> | ||
| 9 | <ProjectReference Include="SfxCA\SfxCA.vcxproj"> | ||
| 10 | <Properties>Platform=x64</Properties> | ||
| 11 | </ProjectReference> | ||
| 12 | </ItemGroup> | ||
| 13 | |||
| 14 | <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\Traversal.targets" /> | ||
| 15 | </Project> | ||
