From 3f583916719eeef598d10a5d4e14ef14f008243b Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 11 May 2021 07:36:37 -0700 Subject: Merge Dtf --- src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs | 711 ++++++++++++++++ src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj | 33 + .../Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest | 20 + src/samples/Dtf/Tools/MakeSfxCA/app.config | 10 + src/samples/Dtf/Tools/SfxCA/ClrHost.cpp | 262 ++++++ src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp | 281 +++++++ src/samples/Dtf/Tools/SfxCA/EntryPoints.def | 140 ++++ src/samples/Dtf/Tools/SfxCA/EntryPoints.h | 162 ++++ src/samples/Dtf/Tools/SfxCA/Extract.cpp | 282 +++++++ src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp | 629 +++++++++++++++ src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h | 898 +++++++++++++++++++++ src/samples/Dtf/Tools/SfxCA/SfxCA.cpp | 363 +++++++++ src/samples/Dtf/Tools/SfxCA/SfxCA.rc | 10 + src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj | 68 ++ src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters | 62 ++ src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp | 209 +++++ src/samples/Dtf/Tools/SfxCA/SfxUtil.h | 31 + src/samples/Dtf/Tools/SfxCA/packages.config | 4 + src/samples/Dtf/Tools/SfxCA/precomp.cpp | 3 + src/samples/Dtf/Tools/SfxCA/precomp.h | 18 + src/samples/Dtf/Tools/Tools.proj | 15 + 21 files changed, 4211 insertions(+) create mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs create mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj create mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest create mode 100644 src/samples/Dtf/Tools/MakeSfxCA/app.config create mode 100644 src/samples/Dtf/Tools/SfxCA/ClrHost.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/EntryPoints.def create mode 100644 src/samples/Dtf/Tools/SfxCA/EntryPoints.h create mode 100644 src/samples/Dtf/Tools/SfxCA/Extract.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.rc create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/SfxUtil.h create mode 100644 src/samples/Dtf/Tools/SfxCA/packages.config create mode 100644 src/samples/Dtf/Tools/SfxCA/precomp.cpp create mode 100644 src/samples/Dtf/Tools/SfxCA/precomp.h create mode 100644 src/samples/Dtf/Tools/Tools.proj (limited to 'src/samples/Dtf/Tools') 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Tools.MakeSfxCA +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Security; + using System.Text; + using System.Reflection; + using Compression; + using Compression.Cab; + using Resources; + using ResourceCollection = Resources.ResourceCollection; + + /// + /// Command-line tool for building self-extracting custom action packages. + /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's + /// entry-points and file version to look like the CA module. + /// + public static class MakeSfxCA + { + private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; + + private static TextWriter log; + + /// + /// Prints usage text for the tool. + /// + /// Console text writer. + public static void Usage(TextWriter w) + { + w.WriteLine("Deployment Tools Foundation custom action packager version {0}", + Assembly.GetExecutingAssembly().GetName().Version); + w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); + w.WriteLine(); + w.WriteLine("Usage: MakeSfxCA SfxCA.dll [support files ...]"); + w.WriteLine(); + w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); + w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); + w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); + } + + /// + /// Runs the MakeSfxCA command-line tool. + /// + /// Command-line arguments. + /// 0 on success, nonzero on failure. + public static int Main(string[] args) + { + if (args.Length < 3) + { + Usage(Console.Out); + return 1; + } + + var output = args[0]; + var sfxDll = args[1]; + var inputs = new string[args.Length - 2]; + Array.Copy(args, 2, inputs, 0, inputs.Length); + + try + { + Build(output, sfxDll, inputs, Console.Out); + return 0; + } + catch (ArgumentException ex) + { + Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); + return 1; + } + catch (FileNotFoundException ex) + { + Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); + return 1; + } + catch (Exception ex) + { + Console.Error.WriteLine("Error: Unexpected error: " + ex); + return 1; + } + } + + /// + /// Packages up all the inputs to the output location. + /// + /// Various exceptions are thrown + /// if things go wrong. + public static void Build(string output, string sfxDll, IList inputs, TextWriter log) + { + MakeSfxCA.log = log; + + if (string.IsNullOrEmpty(output)) + { + throw new ArgumentNullException("output"); + } + + if (string.IsNullOrEmpty(sfxDll)) + { + throw new ArgumentNullException("sfxDll"); + } + + if (inputs == null || inputs.Count == 0) + { + throw new ArgumentNullException("inputs"); + } + + if (!File.Exists(sfxDll)) + { + throw new FileNotFoundException(sfxDll); + } + + var customActionAssembly = inputs[0]; + if (!File.Exists(customActionAssembly)) + { + throw new FileNotFoundException(customActionAssembly); + } + + inputs = MakeSfxCA.SplitList(inputs); + + var inputsMap = MakeSfxCA.GetPackFileMap(inputs); + + var foundWIAssembly = false; + foreach (var input in inputsMap.Keys) + { + if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, + StringComparison.OrdinalIgnoreCase) == 0) + { + foundWIAssembly = true; + } + } + + if (!foundWIAssembly) + { + throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + + " must be included in the list of support files. " + + "If using the MSBuild targets, make sure the assembly reference " + + "has the Private (Copy Local) flag set."); + } + + MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); + + var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); + var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); + + if (entryPoints.Count == 0 && uiClass == null) + { + throw new ArgumentException( + "No CA or UI entry points found in module: " + customActionAssembly); + } + else if (entryPoints.Count > 0 && uiClass != null) + { + throw new NotSupportedException( + "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); + } + + var dir = Path.GetDirectoryName(output); + if (dir.Length > 0 && !Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + using (Stream outputStream = File.Create(output)) + { + MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); + } + + MakeSfxCA.CopyVersionResource(customActionAssembly, output); + + MakeSfxCA.PackInputFiles(output, inputsMap); + + log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); + } + + /// + /// Splits any list items delimited by semicolons into separate items. + /// + /// Read-only input list. + /// New list with resulting split items. + private static IList SplitList(IList list) + { + var newList = new List(list.Count); + + foreach (var item in list) + { + if (!string.IsNullOrEmpty(item)) + { + foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + newList.Add(splitItem); + } + } + } + + return newList; + } + + /// + /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. + /// + /// List of input files which include non-GAC dependent assemblies. + /// Directory to auto-locate additional dependent assemblies. + /// + /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them + /// to the list of input files if found. + /// + private static void ResolveDependentAssemblies(IDictionary inputFiles, string inputDir) + { + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) + { + AssemblyName resolveName = new AssemblyName(args.Name); + Assembly assembly = null; + + // First, try to find the assembly in the list of input files. + foreach (var inputFile in inputFiles.Values) + { + var inputName = Path.GetFileNameWithoutExtension(inputFile); + var inputExtension = Path.GetExtension(inputFile); + if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && + (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || + string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) + { + assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); + + if (assembly != null) + { + break; + } + } + } + + // Second, try to find the assembly in the input directory. + if (assembly == null && inputDir != null) + { + string assemblyPath = null; + if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) + { + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; + } + else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) + { + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; + } + + if (assemblyPath != null) + { + assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); + + if (assembly != null) + { + // Add this detected dependency to the list of files to be packed. + inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); + } + } + } + + // Third, try to load the assembly from the GAC. + if (assembly == null) + { + try + { + assembly = Assembly.ReflectionOnlyLoad(args.Name); + } + catch (FileNotFoundException) + { + } + } + + if (assembly != null) + { + if (string.Equals(assembly.GetName().ToString(), resolveName.ToString())) + { + log.WriteLine(" Loaded dependent assembly: " + assembly.Location); + return assembly; + } + + log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); + log.WriteLine(" Loaded assembly : " + assembly.GetName()); + log.WriteLine(" Reference assembly: " + resolveName); + } + else + { + log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); + } + + return null; + }; + } + + /// + /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. + /// + /// Path of the assembly file to laod. + /// Loaded assembly, or null if the load failed. + private static Assembly TryLoadDependentAssembly(string assemblyPath) + { + Assembly assembly = null; + try + { + assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); + } + catch (IOException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + catch (BadImageFormatException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + catch (SecurityException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + + return assembly; + } + + /// + /// Searches the types in the input assembly for a type that implements IEmbeddedUI. + /// + /// + /// + private static string FindEmbeddedUIClass(string module) + { + log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); + + string uiClass = null; + + var assembly = Assembly.ReflectionOnlyLoadFrom(module); + + foreach (var type in assembly.GetExportedTypes()) + { + if (!type.IsAbstract) + { + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") + { + if (uiClass == null) + { + uiClass = assembly.GetName().Name + "!" + type.FullName; + } + else + { + throw new ArgumentException("Multiple IEmbeddedUI implementations found."); + } + } + } + } + } + + return uiClass; + } + + /// + /// Reflects on an input CA module to locate custom action entry-points. + /// + /// Assembly module with CA entry-points. + /// Mapping from entry-point names to assembly!class.method paths. + private static IDictionary FindEntryPoints(string module) + { + log.WriteLine("Searching for custom action entry points " + + "in {0}", Path.GetFileName(module)); + + var entryPoints = new Dictionary(); + + var assembly = Assembly.ReflectionOnlyLoadFrom(module); + + foreach (var type in assembly.GetExportedTypes()) + { + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + var entryPointName = MakeSfxCA.GetEntryPoint(method); + if (entryPointName != null) + { + var entryPointPath = string.Format( + "{0}!{1}.{2}", + Path.GetFileNameWithoutExtension(module), + type.FullName, + method.Name); + entryPoints.Add(entryPointName, entryPointPath); + + log.WriteLine(" {0}={1}", entryPointName, entryPointPath); + } + } + } + + return entryPoints; + } + + /// + /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. + /// + /// A public static method. + /// Entrypoint name for the method as specified by the custom action attribute or just the method name, + /// or null if the method is not a custom action method. + private static string GetEntryPoint(MethodInfo method) + { + IList attributes; + try + { + attributes = CustomAttributeData.GetCustomAttributes(method); + } + catch (FileLoadException) + { + // Already logged load failures in the assembly-resolve-handler. + return null; + } + + foreach (CustomAttributeData attribute in attributes) + { + if (attribute.ToString().StartsWith( + "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", + StringComparison.Ordinal)) + { + string entryPointName = null; + foreach (var argument in attribute.ConstructorArguments) + { + // The entry point name is the first positional argument, if specified. + entryPointName = (string) argument.Value; + break; + } + + if (string.IsNullOrEmpty(entryPointName)) + { + entryPointName = method.Name; + } + + return entryPointName; + } + } + + return null; + } + + /// + /// Counts the number of template entrypoints in SfxCA.dll. + /// + /// + /// Depending on the requirements, SfxCA.dll might be built with + /// more entrypoints than the default. + /// + private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) + { + for (var count = 0; ; count++) + { + var templateName = string.Format(entryPointFormat, count); + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); + + var nameOffset = FindBytes(fileBytes, templateAsciiBytes); + if (nameOffset < 0) + { + return count; + } + } + } + + /// + /// Writes a modified version of SfxCA.dll to the output stream, + /// with the template entry-points mapped to the CA entry-points. + /// + /// + /// To avoid having to recompile SfxCA.dll for every different set of CAs, + /// this method looks for a preset number of template entry-points in the + /// binary file and overwrites their entrypoint name and string data with + /// CA-specific values. + /// + private static void WriteEntryModule( + string sfxDll, Stream outputStream, IDictionary entryPoints, string uiClass) + { + log.WriteLine("Modifying SfxCA.dll stub"); + + byte[] fileBytes; + using (var readStream = File.OpenRead(sfxDll)) + { + fileBytes = new byte[(int) readStream.Length]; + readStream.Read(fileBytes, 0, fileBytes.Length); + } + + const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; + const int MAX_ENTRYPOINT_NAME = 72; + const int MAX_ENTRYPOINT_PATH = 160; + //var emptyBytes = new byte[0]; + + var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); + + if (slotCount == 0) + { + throw new ArgumentException("Invalid SfxCA.dll file."); + } + + if (entryPoints.Count > slotCount) + { + throw new ArgumentException(string.Format( + "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + + "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", + entryPoints.Count, slotCount)); + } + + var slotSort = new string[slotCount]; + for (var i = 0; i < slotCount - entryPoints.Count; i++) + { + slotSort[i] = string.Empty; + } + + entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); + Array.Sort(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); + + for (var i = 0; ; i++) + { + var templateName = string.Format(ENTRYPOINT_FORMAT, i); + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); + var templateUniBytes = Encoding.Unicode.GetBytes(templateName); + + var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); + if (nameOffset < 0) + { + break; + } + + var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); + if (pathOffset < 0) + { + break; + } + + var entryPointName = slotSort[i]; + var entryPointPath = entryPointName.Length > 0 ? + entryPoints[entryPointName] : string.Empty; + + if (entryPointName.Length > MAX_ENTRYPOINT_NAME) + { + throw new ArgumentException(string.Format( + "Entry point name exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_NAME, + entryPointName)); + } + + if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) + { + throw new ArgumentException(string.Format( + "Entry point path exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_PATH, + entryPointPath)); + } + + var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); + var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); + + MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); + MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); + } + + if (entryPoints.Count == 0 && uiClass != null) + { + // Remove the zzz prefix from exported EmbeddedUI entry-points. + foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) + { + var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); + + var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); + if (exportOffset < 0) + { + throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); + } + + var replaceNameBytes = Encoding.ASCII.GetBytes(export); + MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); + } + + if (uiClass.Length > MAX_ENTRYPOINT_PATH) + { + throw new ArgumentException(string.Format( + "UI class full name exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_PATH, + uiClass)); + } + + var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); + var replaceBytes = Encoding.Unicode.GetBytes(uiClass); + + // Fill in the embedded UI implementor class so the proxy knows which one to load. + var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); + if (replaceOffset >= 0) + { + MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); + } + } + + outputStream.Write(fileBytes, 0, fileBytes.Length); + } + + /// + /// Searches for a sub-array of bytes within a larger array of bytes. + /// + private static int FindBytes(byte[] source, byte[] find) + { + for (var i = 0; i < source.Length; i++) + { + int j; + for (j = 0; j < find.Length; j++) + { + if (source[i + j] != find[j]) + { + break; + } + } + + if (j == find.Length) + { + return i; + } + } + + return -1; + } + + /// + /// Replaces a range of bytes with new bytes, padding any extra part + /// of the range with zeroes. + /// + private static void ReplaceBytes( + byte[] source, int offset, int length, byte[] replace) + { + for (var i = 0; i < length; i++) + { + if (i < replace.Length) + { + source[offset + i] = replace[i]; + } + else + { + source[offset + i] = 0; + } + } + } + + /// + /// Print the name of one file as it is being packed into the cab. + /// + private static void PackProgress(object source, ArchiveProgressEventArgs e) + { + if (e.ProgressType == ArchiveProgressType.StartFile && log != null) + { + log.WriteLine(" {0}", e.CurrentFileName); + } + } + + /// + /// Gets a mapping from filenames as they will be in the cab to filenames + /// as they are currently on disk. + /// + /// + /// By default, all files will be placed in the root of the cab. But inputs may + /// optionally include an alternate inside-cab file path before an equals sign. + /// + private static IDictionary GetPackFileMap(IList inputs) + { + var fileMap = new Dictionary(); + foreach (var inputFile in inputs) + { + if (inputFile.IndexOf('=') > 0) + { + var parse = inputFile.Split('='); + if (!fileMap.ContainsKey(parse[0])) + { + fileMap.Add(parse[0], parse[1]); + } + } + else + { + var fileName = Path.GetFileName(inputFile); + if (!fileMap.ContainsKey(fileName)) + { + fileMap.Add(fileName, inputFile); + } + } + } + return fileMap; + } + + /// + /// Packs the input files into a cab that is appended to the + /// output SfxCA.dll. + /// + private static void PackInputFiles(string outputFile, IDictionary fileMap) + { + log.WriteLine("Packaging files"); + + var cabInfo = new CabInfo(outputFile); + cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); + } + + /// + /// Copies the version resource information from the CA module to + /// the CA package. This gives the package the file version and + /// description of the CA module, instead of the version and + /// description of SfxCA.dll. + /// + private static void CopyVersionResource(string sourceFile, string destFile) + { + log.WriteLine("Copying file version info from {0} to {1}", + sourceFile, destFile); + + var rc = new ResourceCollection(); + rc.Find(sourceFile, ResourceType.Version); + rc.Load(sourceFile); + rc.Save(destFile); + } + } +} 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 @@ + + + + + + netcoreapp3.1;net461 + Exe + WixToolset.Dtf.Tools.MakeSfxCA + MakeSfxCA + embedded + app.config + MakeSfxCA.exe.manifest + Major + win-x86 + + + + + + + + + + + + + + + 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}. + + + + 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 @@ + + + + + + + WiX Toolset Compiler + + + + + + + + + + true + + + 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 @@ + + + + + + + + + + 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 @@ +// 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. + +#include "precomp.h" + +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); + +//--------------------------------------------------------------------- +// CLR HOSTING +//--------------------------------------------------------------------- + +/// +/// Binds to the CLR after determining the appropriate version. +/// +/// Handle to the installer session, +/// used just for logging. +/// Specific version of the CLR to load. +/// If null, then the config file and/or primary assembly are +/// used to determine the version. +/// XML .config file which may contain +/// a startup section to direct which version of the CLR to use. +/// May be NULL. +/// Assembly to be used to determine +/// the version of the CLR in the absence of other configuration. +/// May be NULL. +/// Returned runtime host interface. +/// True if the CLR was loaded successfully, false if +/// there was some error. +/// +/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or +/// does not contain any version configuration, the CLR will not be loaded. +/// +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost) +{ + typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, + LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, + LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, + LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); + typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, + DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv); + + HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll"); + if (hmodMscoree == NULL) + { + Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action " + L"requires the .NET Framework to be installed.", GetLastError()); + return false; + } + PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo) + GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo"); + PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx) + GetProcAddress(hmodMscoree, "CorBindToRuntimeEx"); + if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL) + { + Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action " + L"requires the .NET Framework to be installed.", GetLastError()); + FreeLibrary(hmodMscoree); + return false; + } + + wchar_t szClrVersion[20]; + HRESULT hr; + + if (szVersion != NULL && szVersion[0] != L'\0') + { + wcsncpy_s(szClrVersion, 20, szVersion, 20); + } + else + { + wchar_t szVersionDir[MAX_PATH]; + hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL, + szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL); + if (FAILED(hr)) + { + Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr); + Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or " + L"that there is a matching supportedRuntime element in CustomAction.config. " + L"If you are binding to .NET 4 or greater add " + L"useLegacyV2RuntimeActivationPolicy=true to the element."); + FreeLibrary(hmodMscoree); + return false; + } + } + + Log(hSession, L"Binding to CLR version %s", szClrVersion); + + ICorRuntimeHost* pHost; + hr = pCorBindToRuntimeEx(szClrVersion, NULL, + STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, + CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost); + if (FAILED(hr)) + { + Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr); + FreeLibrary(hmodMscoree); + return false; + } + hr = pHost->Start(); + if (FAILED(hr)) + { + Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr); + pHost->Release(); + FreeLibrary(hmodMscoree); + return false; + } + *ppHost = pHost; + FreeLibrary(hmodMscoree); + return true; +} + +/// +/// Creates a new CLR application domain. +/// +/// Handle to the installer session, +/// used just for logging +/// Interface to the runtime host where the +/// app domain will be created. +/// Name of the app domain to create. +/// Application base directory path, where +/// the app domain will look first to load its assemblies. +/// Optional XML .config file containing any +/// configuration for thae app domain. +/// Returned app domain interface. +/// True if the app domain was created successfully, false if +/// there was some error. +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, + const wchar_t* szName, const wchar_t* szAppBase, + const wchar_t* szConfigFile, _AppDomain** ppAppDomain) +{ + IUnknown* punkAppDomainSetup = NULL; + IAppDomainSetup* pAppDomainSetup = NULL; + HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup); + if (SUCCEEDED(hr)) + { + hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup); + punkAppDomainSetup->Release(); + } + if (FAILED(hr)) + { + Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr); + return false; + } + + const wchar_t* szUrlPrefix = L"file:///"; + size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase); + wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t)); + if (szApplicationBase == NULL) hr = E_OUTOFMEMORY; + else + { + StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix); + StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase); + BSTR bstrApplicationBase = SysAllocString(szApplicationBase); + if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase); + SysFreeString(bstrApplicationBase); + } + } + + if (SUCCEEDED(hr) && szConfigFile != NULL) + { + BSTR bstrConfigFile = SysAllocString(szConfigFile); + if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile); + SysFreeString(bstrConfigFile); + } + } + + if (FAILED(hr)) + { + Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr); + pAppDomainSetup->Release(); + return false; + } + + IUnknown* punkAppDomain; + hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain); + pAppDomainSetup->Release(); + if (SUCCEEDED(hr)) + { + hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain); + punkAppDomain->Release(); + } + + if (FAILED(hr)) + { + Log(hSession, L"Failed to create app domain. Error code 0x%X", hr); + return false; + } + + return true; +} + +/// +/// Locates a specific method in a specific class and assembly. +/// +/// Handle to the installer session, +/// used just for logging +/// Application domain in which to +/// load assemblies. +/// Display name of the assembly +/// containing the method. +/// Fully-qualified name of the class +/// containing the method. +/// Name of the method. +/// Returned method interface. +/// True if the method was located, otherwise false. +/// Only public static methods are searched. Method +/// parameter types are not considered; if there are multiple +/// matching methods with different parameters, an error results. +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szAssembly, const wchar_t* szClass, + const wchar_t* szMethod, _MethodInfo** ppMethod) +{ + HRESULT hr; + _Assembly* pAssembly = NULL; + BSTR bstrAssemblyName = SysAllocString(szAssembly); + if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly); + SysFreeString(bstrAssemblyName); + } + if (FAILED(hr)) + { + Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr); + return false; + } + + _Type* pType = NULL; + BSTR bstrClass = SysAllocString(szClass); + if (bstrClass == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAssembly->GetType_2(bstrClass, &pType); + SysFreeString(bstrClass); + } + pAssembly->Release(); + if (FAILED(hr) || pType == NULL) + { + Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr); + return false; + } + + BSTR bstrMethod = SysAllocString(szMethod); + if (bstrMethod == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pType->GetMethod_2(bstrMethod, + (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod); + SysFreeString(bstrMethod); + } + pType->Release(); + if (FAILED(hr) || *ppMethod == NULL) + { + Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr); + return false; + } + return true; +} 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 @@ +// 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. + +#include "precomp.h" +#include "SfxUtil.h" + +// Globals for keeping track of things across UI messages. +static const wchar_t* g_szWorkingDir; +static ICorRuntimeHost* g_pClrHost; +static _AppDomain* g_pAppDomain; +static _MethodInfo* g_pProcessMessageMethod; +static _MethodInfo* g_pShutdownMethod; + +// Reserve extra space for strings to be replaced at build time. +#define NULLSPACE \ +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" \ +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" \ +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" \ +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +// Prototypes for local functions. +// See the function definitions for comments. + +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, + const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); + +/// +/// First entry-point for the UI DLL when loaded and called by MSI. +/// Extracts the payload, hosts the CLR, and invokes the managed +/// initialize method. +/// +/// Handle to the installer session, +/// used for logging errors and to be passed on to the managed initialize method. +/// Path the directory where resources from the MsiEmbeddedUI table +/// have been extracted, and where additional payload from this package will be extracted. +/// MSI install UI level passed to and returned from +/// the managed initialize method. +extern "C" +UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) +{ + // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. + UINT uiResult = INSTALLUILEVEL_BASIC; + + const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; + + g_szWorkingDir = szResourcePath; + + wchar_t szModule[MAX_PATH]; + DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); + if (cchCopied == 0) + { + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); + return uiResult; + } + else if (cchCopied == MAX_PATH - 1) + { + Log(hSession, L"Failed to get module path -- path is too long."); + return uiResult; + } + + Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); + int err = ExtractCabinet(szModule, g_szWorkingDir); + if (err != 0) + { + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); + Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " + L"any file contained in the embedded UI package."); + return uiResult; + } + + wchar_t szConfigFilePath[MAX_PATH + 20]; + StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); + + const wchar_t* szConfigFile = szConfigFilePath; + if (!PathFileExists(szConfigFilePath)) + { + szConfigFile = NULL; + } + + wchar_t szWIAssembly[MAX_PATH + 50]; + StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); + + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) + { + if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, + szConfigFile, &g_pAppDomain)) + { + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; + const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; + const wchar_t* szInitMethod = L"Initialize"; + const wchar_t* szProcessMessageMethod = L"ProcessMessage"; + const wchar_t* szShutdownMethod = L"Shutdown"; + + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && + GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szShutdownMethod, &g_pShutdownMethod)) + { + _MethodInfo* pInitMethod; + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szInitMethod, &pInitMethod)) + { + bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); + pInitMethod->Release(); + if (invokeSuccess) + { + if (uiResult == 0) + { + return ERROR_SUCCESS; + } + else if (uiResult == ERROR_INSTALL_USEREXIT) + { + // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. + // So return success here and then IDCANCEL on the next progress message. + uiResult = 0; + *pdwInternalUILevel = INSTALLUILEVEL_NONE; + Log(hSession, L"Initialization canceled by user."); + } + } + } + } + + g_pProcessMessageMethod->Release(); + g_pProcessMessageMethod = NULL; + g_pShutdownMethod->Release(); + g_pShutdownMethod = NULL; + + g_pClrHost->UnloadDomain(g_pAppDomain); + g_pAppDomain->Release(); + g_pAppDomain = NULL; + } + g_pClrHost->Stop(); + g_pClrHost->Release(); + g_pClrHost = NULL; + } + + return uiResult; +} + +/// +/// Entry-point for UI progress messages received from the MSI engine during an active installation. +/// Forwards the progress messages to the managed handler method and returns its result. +/// +extern "C" +INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) +{ + if (g_pProcessMessageMethod == NULL) + { + // Initialization was canceled. + return IDCANCEL; + } + + VARIANT vResult; + VariantInit(&vResult); + + VARIANT vNull; + vNull.vt = VT_EMPTY; + + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); + VARIANT vMessageType; + vMessageType.vt = VT_I4; + vMessageType.lVal = (LONG) uiMessageType; + LONG index = 0; + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); + if (FAILED(hr)) goto LExit; + VARIANT vRecord; + vRecord.vt = VT_I4; + vRecord.lVal = (LONG) hRecord; + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vRecord); + if (FAILED(hr)) goto LExit; + + hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + if (SUCCEEDED(hr)) + { + return vResult.intVal; + } + else + { + return -1; + } +} + +/// +/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. +/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. +/// +extern "C" +DWORD __stdcall ShutdownEmbeddedUI() +{ + if (g_pShutdownMethod != NULL) + { + VARIANT vNull; + vNull.vt = VT_EMPTY; + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); + g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); + SafeArrayDestroy(saArgs); + + g_pClrHost->UnloadDomain(g_pAppDomain); + g_pAppDomain->Release(); + g_pClrHost->Stop(); + g_pClrHost->Release(); + } + + return 0; +} + +/// +/// Loads and invokes the managed portion of the proxy. +/// +/// Managed initialize method to be invoked. +/// Handle to the installer session, +/// used for logging errors and to be passed on to the managed initialize method. +/// Name of the UI class to be loaded. +/// This must be of the form: AssemblyName!Namespace.Class +/// MSI install UI level passed to and returned from +/// the managed initialize method. +/// Return value of the invoked initialize method. +/// True if the managed proxy was invoked successfully, or an +/// error code if there was some error. Note the initialize method itself may +/// return an error via puiResult while this method still returns true +/// since the invocation was successful. +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) +{ + VARIANT vResult; + VariantInit(&vResult); + + VARIANT vNull; + vNull.vt = VT_EMPTY; + + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); + VARIANT vSessionHandle; + vSessionHandle.vt = VT_I4; + vSessionHandle.lVal = (LONG) hSession; + LONG index = 0; + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); + if (FAILED(hr)) goto LExit; + VARIANT vEntryPoint; + vEntryPoint.vt = VT_BSTR; + vEntryPoint.bstrVal = SysAllocString(szClassName); + if (vEntryPoint.bstrVal == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); + if (FAILED(hr)) goto LExit; + VARIANT vUILevel; + vUILevel.vt = VT_I4; + vUILevel.ulVal = *pdwInternalUILevel; + index = 2; + hr = SafeArrayPutElement(saArgs, &index, &vUILevel); + if (FAILED(hr)) goto LExit; + + hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + if (SUCCEEDED(hr)) + { + *puiResult = (UINT) vResult.lVal; + if ((*puiResult & 0xFFFF) == 0) + { + // Due to interop limitations, the successful resulting UILevel is returned + // as the high-word of the return value instead of via a ref parameter. + *pdwInternalUILevel = *puiResult >> 16; + *puiResult = 0; + } + return true; + } + else + { + Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); + return false; + } +} 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 @@ +; 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. + + +LIBRARY "SfxCA" + +EXPORTS + +CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 +CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 +CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 +CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 +CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 +CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 +CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 +CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 +CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 +CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 +CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 +CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 +CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 +CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 +CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 +CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 +CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 +CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 +CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 +CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 +CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 +CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 +CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 +CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 +CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 +CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 +CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 +CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 +CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 +CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 +CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 +CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 +CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 +CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 +CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 +CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 +CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 +CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 +CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 +CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 +CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 +CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 +CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 +CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 +CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 +CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 +CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 +CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 +CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 +CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 +CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 +CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 +CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 +CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 +CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 +CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 +CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 +CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 +CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 +CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 +CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 +CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 +CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 +CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 +CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 +CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 +CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 +CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 +CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 +CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 +CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 +CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 +CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 +CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 +CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 +CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 +CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 +CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 +CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 +CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 +CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 +CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 +CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 +CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 +CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 +CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 +CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 +CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 +CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 +CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 +CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 +CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 +CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 +CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 +CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 +CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 +CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 +CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 +CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 +CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 +CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 +CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 +CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 +CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 +CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 +CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 +CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 +CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 +CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 +CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 +CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 +CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 +CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 +CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 +CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 +CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 +CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 +CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 +CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 +CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 +CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 +CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 +CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 +CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 +CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 +CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 +CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 +CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 + +zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc +zzzInitializeEmbeddedUI=InitializeEmbeddedUI +zzzEmbeddedUIHandler=EmbeddedUIHandler +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 @@ +// 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. + +int InvokeCustomAction(MSIHANDLE hSession, + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint); + +/// +/// Macro for defining and exporting a custom action entrypoint. +/// +/// Name of the entrypoint as exported from +/// the DLL. +/// Path to the managed custom action method, +/// in the form: "AssemblyName!Namespace.Class.Method" +/// +/// To prevent the exported name from being decorated, add +/// /EXPORT:name to the linker options for every entrypoint. +/// +#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \ + name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); } + +// TEMPLATE ENTRYPOINTS +// To be edited by the MakeSfxCA tool. + +#define NULLSPACE \ +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" \ +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" \ +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" \ +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \ + CustomActionEntryPoint##id##, \ + L"CustomActionEntryPoint" sid NULLSPACE) + +TEMPLATE_CA_ENTRYPOINT(000,L"000"); +TEMPLATE_CA_ENTRYPOINT(001,L"001"); +TEMPLATE_CA_ENTRYPOINT(002,L"002"); +TEMPLATE_CA_ENTRYPOINT(003,L"003"); +TEMPLATE_CA_ENTRYPOINT(004,L"004"); +TEMPLATE_CA_ENTRYPOINT(005,L"005"); +TEMPLATE_CA_ENTRYPOINT(006,L"006"); +TEMPLATE_CA_ENTRYPOINT(007,L"007"); +TEMPLATE_CA_ENTRYPOINT(008,L"008"); +TEMPLATE_CA_ENTRYPOINT(009,L"009"); +TEMPLATE_CA_ENTRYPOINT(010,L"010"); +TEMPLATE_CA_ENTRYPOINT(011,L"011"); +TEMPLATE_CA_ENTRYPOINT(012,L"012"); +TEMPLATE_CA_ENTRYPOINT(013,L"013"); +TEMPLATE_CA_ENTRYPOINT(014,L"014"); +TEMPLATE_CA_ENTRYPOINT(015,L"015"); +TEMPLATE_CA_ENTRYPOINT(016,L"016"); +TEMPLATE_CA_ENTRYPOINT(017,L"017"); +TEMPLATE_CA_ENTRYPOINT(018,L"018"); +TEMPLATE_CA_ENTRYPOINT(019,L"019"); +TEMPLATE_CA_ENTRYPOINT(020,L"020"); +TEMPLATE_CA_ENTRYPOINT(021,L"021"); +TEMPLATE_CA_ENTRYPOINT(022,L"022"); +TEMPLATE_CA_ENTRYPOINT(023,L"023"); +TEMPLATE_CA_ENTRYPOINT(024,L"024"); +TEMPLATE_CA_ENTRYPOINT(025,L"025"); +TEMPLATE_CA_ENTRYPOINT(026,L"026"); +TEMPLATE_CA_ENTRYPOINT(027,L"027"); +TEMPLATE_CA_ENTRYPOINT(028,L"028"); +TEMPLATE_CA_ENTRYPOINT(029,L"029"); +TEMPLATE_CA_ENTRYPOINT(030,L"030"); +TEMPLATE_CA_ENTRYPOINT(031,L"031"); +TEMPLATE_CA_ENTRYPOINT(032,L"032"); +TEMPLATE_CA_ENTRYPOINT(033,L"033"); +TEMPLATE_CA_ENTRYPOINT(034,L"034"); +TEMPLATE_CA_ENTRYPOINT(035,L"035"); +TEMPLATE_CA_ENTRYPOINT(036,L"036"); +TEMPLATE_CA_ENTRYPOINT(037,L"037"); +TEMPLATE_CA_ENTRYPOINT(038,L"038"); +TEMPLATE_CA_ENTRYPOINT(039,L"039"); +TEMPLATE_CA_ENTRYPOINT(040,L"040"); +TEMPLATE_CA_ENTRYPOINT(041,L"041"); +TEMPLATE_CA_ENTRYPOINT(042,L"042"); +TEMPLATE_CA_ENTRYPOINT(043,L"043"); +TEMPLATE_CA_ENTRYPOINT(044,L"044"); +TEMPLATE_CA_ENTRYPOINT(045,L"045"); +TEMPLATE_CA_ENTRYPOINT(046,L"046"); +TEMPLATE_CA_ENTRYPOINT(047,L"047"); +TEMPLATE_CA_ENTRYPOINT(048,L"048"); +TEMPLATE_CA_ENTRYPOINT(049,L"049"); +TEMPLATE_CA_ENTRYPOINT(050,L"050"); +TEMPLATE_CA_ENTRYPOINT(051,L"051"); +TEMPLATE_CA_ENTRYPOINT(052,L"052"); +TEMPLATE_CA_ENTRYPOINT(053,L"053"); +TEMPLATE_CA_ENTRYPOINT(054,L"054"); +TEMPLATE_CA_ENTRYPOINT(055,L"055"); +TEMPLATE_CA_ENTRYPOINT(056,L"056"); +TEMPLATE_CA_ENTRYPOINT(057,L"057"); +TEMPLATE_CA_ENTRYPOINT(058,L"058"); +TEMPLATE_CA_ENTRYPOINT(059,L"059"); +TEMPLATE_CA_ENTRYPOINT(060,L"060"); +TEMPLATE_CA_ENTRYPOINT(061,L"061"); +TEMPLATE_CA_ENTRYPOINT(062,L"062"); +TEMPLATE_CA_ENTRYPOINT(063,L"063"); +TEMPLATE_CA_ENTRYPOINT(064,L"064"); +TEMPLATE_CA_ENTRYPOINT(065,L"065"); +TEMPLATE_CA_ENTRYPOINT(066,L"066"); +TEMPLATE_CA_ENTRYPOINT(067,L"067"); +TEMPLATE_CA_ENTRYPOINT(068,L"068"); +TEMPLATE_CA_ENTRYPOINT(069,L"069"); +TEMPLATE_CA_ENTRYPOINT(070,L"070"); +TEMPLATE_CA_ENTRYPOINT(071,L"071"); +TEMPLATE_CA_ENTRYPOINT(072,L"072"); +TEMPLATE_CA_ENTRYPOINT(073,L"073"); +TEMPLATE_CA_ENTRYPOINT(074,L"074"); +TEMPLATE_CA_ENTRYPOINT(075,L"075"); +TEMPLATE_CA_ENTRYPOINT(076,L"076"); +TEMPLATE_CA_ENTRYPOINT(077,L"077"); +TEMPLATE_CA_ENTRYPOINT(078,L"078"); +TEMPLATE_CA_ENTRYPOINT(079,L"079"); +TEMPLATE_CA_ENTRYPOINT(080,L"080"); +TEMPLATE_CA_ENTRYPOINT(081,L"081"); +TEMPLATE_CA_ENTRYPOINT(082,L"082"); +TEMPLATE_CA_ENTRYPOINT(083,L"083"); +TEMPLATE_CA_ENTRYPOINT(084,L"084"); +TEMPLATE_CA_ENTRYPOINT(085,L"085"); +TEMPLATE_CA_ENTRYPOINT(086,L"086"); +TEMPLATE_CA_ENTRYPOINT(087,L"087"); +TEMPLATE_CA_ENTRYPOINT(088,L"088"); +TEMPLATE_CA_ENTRYPOINT(089,L"089"); +TEMPLATE_CA_ENTRYPOINT(090,L"090"); +TEMPLATE_CA_ENTRYPOINT(091,L"091"); +TEMPLATE_CA_ENTRYPOINT(092,L"092"); +TEMPLATE_CA_ENTRYPOINT(093,L"093"); +TEMPLATE_CA_ENTRYPOINT(094,L"094"); +TEMPLATE_CA_ENTRYPOINT(095,L"095"); +TEMPLATE_CA_ENTRYPOINT(096,L"096"); +TEMPLATE_CA_ENTRYPOINT(097,L"097"); +TEMPLATE_CA_ENTRYPOINT(098,L"098"); +TEMPLATE_CA_ENTRYPOINT(099,L"099"); +TEMPLATE_CA_ENTRYPOINT(100,L"100"); +TEMPLATE_CA_ENTRYPOINT(101,L"101"); +TEMPLATE_CA_ENTRYPOINT(102,L"102"); +TEMPLATE_CA_ENTRYPOINT(103,L"103"); +TEMPLATE_CA_ENTRYPOINT(104,L"104"); +TEMPLATE_CA_ENTRYPOINT(105,L"105"); +TEMPLATE_CA_ENTRYPOINT(106,L"106"); +TEMPLATE_CA_ENTRYPOINT(107,L"107"); +TEMPLATE_CA_ENTRYPOINT(108,L"108"); +TEMPLATE_CA_ENTRYPOINT(109,L"109"); +TEMPLATE_CA_ENTRYPOINT(110,L"110"); +TEMPLATE_CA_ENTRYPOINT(111,L"111"); +TEMPLATE_CA_ENTRYPOINT(112,L"112"); +TEMPLATE_CA_ENTRYPOINT(113,L"113"); +TEMPLATE_CA_ENTRYPOINT(114,L"114"); +TEMPLATE_CA_ENTRYPOINT(115,L"115"); +TEMPLATE_CA_ENTRYPOINT(116,L"116"); +TEMPLATE_CA_ENTRYPOINT(117,L"117"); +TEMPLATE_CA_ENTRYPOINT(118,L"118"); +TEMPLATE_CA_ENTRYPOINT(119,L"119"); +TEMPLATE_CA_ENTRYPOINT(120,L"120"); +TEMPLATE_CA_ENTRYPOINT(121,L"121"); +TEMPLATE_CA_ENTRYPOINT(122,L"122"); +TEMPLATE_CA_ENTRYPOINT(123,L"123"); +TEMPLATE_CA_ENTRYPOINT(124,L"124"); +TEMPLATE_CA_ENTRYPOINT(125,L"125"); +TEMPLATE_CA_ENTRYPOINT(126,L"126"); +TEMPLATE_CA_ENTRYPOINT(127,L"127"); + +// 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 @@ +// 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. + +#include "precomp.h" + +//--------------------------------------------------------------------- +// CABINET EXTRACTION +//--------------------------------------------------------------------- + +// Globals make this code unsuited for multhreaded use, +// but FDI doesn't provide any other way to pass context. + +// Handle to the FDI (cab extraction) engine. Need access to this in a callback. +static HFDI g_hfdi; + +// FDI is not unicode-aware, so avoid passing these paths through the callbacks. +static const wchar_t* g_szExtractDir; +static const wchar_t* g_szCabFile; + +// Offset into the source file where the cabinet really starts. +// Used to trick FDI into extracting from a concatenated cabinet. +static int g_lCabOffset; + +// Use the secure CRT version of _wsopen if available. +#ifdef __GOT_SECURE_LIB__ +#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) +#else +#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) +#endif + +/// +/// FDI callback to open a cabinet file. +/// +/// Name of the file to be opened. This parameter +/// is ignored since with our limited use this method is only ever called +/// to open the main cabinet file. +/// Type of operations allowed. +/// Permission setting. +/// Integer file handle, or -1 if the file could not be opened. +/// +/// To support reading from a cabinet that is concatenated onto +/// another file, this function first searches for the offset of the cabinet, +/// then saves that offset for use in recalculating later seeks. +/// +static FNOPEN(CabOpen) +{ + UNREFERENCED_PARAMETER(pszFile); + int hf; + _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); + if (hf != -1) + { + FDICABINETINFO cabInfo; + int length = _lseek(hf, 0, SEEK_END); + for(int offset = 0; offset < length; offset += 256) + { + if (_lseek(hf, offset, SEEK_SET) != offset) break; + if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) + { + g_lCabOffset = offset; + _lseek(hf, offset, SEEK_SET); + return hf; + } + } + _close(hf); + } + return -1; +} + +/// +/// FDI callback to seek within a file. +/// +/// File handle. +/// Seek distance +/// Whether to seek relative to the +/// beginning, current position, or end of the file. +/// Resultant position within the cabinet. +/// +/// To support reading from a cabinet that is concatenated onto +/// another file, this function recalculates seeks based on the +/// offset that was determined when the cabinet was opened. +/// +static FNSEEK(CabSeek) +{ + if (seektype == SEEK_SET) dist += g_lCabOffset; + int pos = _lseek((int) hf, dist, seektype); + pos -= g_lCabOffset; + return pos; +} + +/// +/// Ensures a directory and its parent directory path exists. +/// +/// Directory path, not including file name. +/// 0 if the directory exists or was successfully created, else nonzero. +/// +/// This function modifies characters in szDirPath, but always restores them +/// regardless of error condition. +/// +static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) +{ + int ret = 0; + if (!::CreateDirectoryW(szDirPath, NULL)) + { + UINT err = ::GetLastError(); + if (err != ERROR_ALREADY_EXISTS) + { + // Directory creation failed for some reason other than already existing. + // Try to create the parent directory first. + wchar_t* szLastSlash = NULL; + for (wchar_t* sz = szDirPath; *sz; sz++) + { + if (*sz == L'\\') + { + szLastSlash = sz; + } + } + if (szLastSlash) + { + // Temporarily take one directory off the path and recurse. + *szLastSlash = L'\0'; + ret = EnsureDirectoryExists(szDirPath); + *szLastSlash = L'\\'; + + // Try to create the directory if all parents are created. + if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) + { + err = ::GetLastError(); + if (err != ERROR_ALREADY_EXISTS) + { + ret = -1; + } + } + } + else + { + ret = -1; + } + } + } + return ret; +} + +/// +/// Ensures a file's directory and its parent directory path exists. +/// +/// Path including file name. +/// 0 if the file's directory exists or was successfully created, else nonzero. +/// +/// This function modifies characters in szFilePath, but always restores them +/// regardless of error condition. +/// +static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) +{ + int ret = 0; + wchar_t* szLastSlash = NULL; + for (wchar_t* sz = szFilePath; *sz; sz++) + { + if (*sz == L'\\') + { + szLastSlash = sz; + } + } + if (szLastSlash) + { + *szLastSlash = L'\0'; + ret = EnsureDirectoryExists(szFilePath); + *szLastSlash = L'\\'; + } + return ret; +} + +/// +/// FDI callback for handling files in the cabinet. +/// +/// Type of notification. +/// Structure containing data about the notification. +/// +/// Refer to fdi.h for more comments on this notification callback. +/// +static FNFDINOTIFY(CabNotification) +{ + // fdintCOPY_FILE: + // Called for each file that *starts* in the current cabinet, giving + // the client the opportunity to request that the file be copied or + // skipped. + // Entry: + // pfdin->psz1 = file name in cabinet + // pfdin->cb = uncompressed size of file + // pfdin->date = file date + // pfdin->time = file time + // pfdin->attribs = file attributes + // pfdin->iFolder = file's folder index + // Exit-Success: + // Return non-zero file handle for destination file; FDI writes + // data to this file use the PFNWRITE function supplied to FDICreate, + // and then calls fdintCLOSE_FILE_INFO to close the file and set + // the date, time, and attributes. + // Exit-Failure: + // Returns 0 => Skip file, do not copy + // Returns -1 => Abort FDICopy() call + if (fdint == fdintCOPY_FILE) + { + size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); + size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; + wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); + if (szFilePath == NULL) return -1; + StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); + StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); + MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, + szFilePath + cchFilePath - cchFile, (int) cchFile + 1); + int hf = -1; + if (EnsureFileDirectoryExists(szFilePath) == 0) + { + _wsopen__s(hf, szFilePath, + _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, + _SH_DENYWR, _S_IREAD | _S_IWRITE); + } + return hf; + } + + // fdintCLOSE_FILE_INFO: + // Called after all of the data has been written to a target file. + // This function must close the file and set the file date, time, + // and attributes. + // Entry: + // pfdin->psz1 = file name in cabinet + // pfdin->hf = file handle + // pfdin->date = file date + // pfdin->time = file time + // pfdin->attribs = file attributes + // pfdin->iFolder = file's folder index + // pfdin->cb = Run After Extract (0 - don't run, 1 Run) + // Exit-Success: + // Returns TRUE + // Exit-Failure: + // Returns FALSE, or -1 to abort + else if (fdint == fdintCLOSE_FILE_INFO) + { + _close((int) pfdin->hf); + return TRUE; + } + return 0; +} + +/// +/// Extracts all contents of a cabinet file to a directory. +/// +/// Path to the cabinet file to be extracted. +/// The cabinet may actually start at some offset within the file, +/// as long as that offset is a multiple of 256. +/// Directory where files are to be extracted. +/// This directory must already exist, but should be empty. +/// 0 if the cabinet was extracted successfully, +/// or an error code if any error occurred. +/// +/// The extraction will not overwrite any files in the destination +/// directory; extraction will be interrupted and fail if any files +/// with the same name already exist. +/// +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) +{ + ERF erf; + // Most of the FDI callbacks can be handled by existing CRT I/O functions. + // For our functionality we only need to handle the open and seek callbacks. + HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, + (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, + CabSeek, cpu80386, &erf); + if (hfdi != NULL) + { + g_hfdi = hfdi; + g_szCabFile = szCabFile; + g_szExtractDir = szExtractDir; + char szEmpty[1] = {0}; + if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) + { + FDIDestroy(hfdi); + return 0; + } + FDIDestroy(hfdi); + } + + return erf.erfOper; +} 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 @@ +// 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. + +#include "precomp.h" +#include "RemoteMsiSession.h" + + +// +// Ensures that the request buffer is large enough to hold a request, +// reallocating the buffer if necessary. +// It will also reduce the buffer size if the previous allocation was very large. +// +static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired) +{ + // It will also reduce the buffer size if the previous allocation was very large. + if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf)) + { + if (*pszBuf != NULL) + { + SecureZeroMemory(*pszBuf, *pcchBuf); + delete[] *pszBuf; + } + + *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired); + *pszBuf = new wchar_t[*pcchBuf]; + + if (*pszBuf == NULL) + { + return ERROR_OUTOFMEMORY; + } + } + + return ERROR_SUCCESS; +} + +typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); +typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); +typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); +typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); +typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); +typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); +typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +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); + +UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + int out1; + UINT ret = (UINT) func(in1, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + int in2 = pReq->fields[1].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int out1; + UINT ret = (UINT) func(in1, in2, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, in3, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int in4 = pReq->fields[3].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, in3, in4, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int out1, out2; + UINT ret = (UINT) func(in1, in2, &out1, &out2); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = out2; + } + return ret; +} + +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) +{ + int in1 = pReq->fields[0].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +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) +{ + int in1 = pReq->fields[0].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + MSIDBERROR ret = func(in1, szBuf, &cchValue); + if (ret == MSIDBERROR_MOREDATA) + { + if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue)) + { + ret = func(in1, szBuf, &cchValue); + } + } + if (ret != MSIDBERROR_MOREDATA) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +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) +{ + int in1 = pReq->fields[0].iValue; + int in2 = pReq->fields[1].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +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) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +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) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int in4 = pReq->fields[3].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + int out2, out3; + UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = out2; + pResp->fields[3].vt = VT_I4; + pResp->fields[3].iValue = out3; + } + return ret; +} + +void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp) +{ + SecureZeroMemory(pResp, sizeof(RequestData)); + + UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024); + + if (0 == ret) + { + switch (id) + { + case RemoteMsiSession::EndSession: + { + this->ExitCode = pReq->fields[0].iValue; + } + break; + case RemoteMsiSession::MsiCloseHandle: + { + MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiCloseHandle(h); + } + break; + case RemoteMsiSession::MsiProcessMessage: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; + ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord); + } + break; + case RemoteMsiSession::MsiGetProperty: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiSetProperty: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szName = pReq->fields[1].szValue; + const wchar_t* szValue = pReq->fields[2].szValue; + ret = ::MsiSetProperty(hInstall, szName, szValue); + } + break; + case RemoteMsiSession::MsiCreateRecord: + { + UINT cParams = pReq->fields[0].uiValue; + ret = ::MsiCreateRecord(cParams); + } + break; + case RemoteMsiSession::MsiRecordGetFieldCount: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiRecordGetFieldCount(hRecord); + } + break; + case RemoteMsiSession::MsiRecordGetInteger: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordGetInteger(hRecord, iField); + } + break; + case RemoteMsiSession::MsiRecordSetInteger: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + int iValue = pReq->fields[2].iValue; + ret = ::MsiRecordSetInteger(hRecord, iField, iValue); + } + break; + case RemoteMsiSession::MsiRecordGetString: + { + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiRecordSetString: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + const wchar_t* szValue = pReq->fields[2].szValue; + ret = ::MsiRecordSetString(hRecord, iField, szValue); + } + break; + case RemoteMsiSession::MsiRecordClearData: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiRecordClearData(hRecord); + } + break; + case RemoteMsiSession::MsiRecordIsNull: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordIsNull(hRecord, iField); + } + break; + case RemoteMsiSession::MsiFormatRecord: + { + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiGetActiveDatabase: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = (UINT) ::MsiGetActiveDatabase(hInstall); + } + break; + case RemoteMsiSession::MsiDatabaseOpenView: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp); + } + break; + case RemoteMsiSession::MsiViewExecute: + { + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue; + ret = ::MsiViewExecute(hView, hRecord); + } + break; + case RemoteMsiSession::MsiViewFetch: + { + ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp); + } + break; + case RemoteMsiSession::MsiViewModify: + { + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; + MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; + ret = ::MsiViewModify(hView, eModifyMode, hRecord); + } + break; + case RemoteMsiSession::MsiViewGetError: + { + ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiViewGetColumnInfo: + { + ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp); + } + break; + case RemoteMsiSession::MsiDatabaseGetPrimaryKeys: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp); + } + break; + case RemoteMsiSession::MsiDatabaseIsTablePersistent: + { + MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szTable = pReq->fields[1].szValue; + ret = ::MsiDatabaseIsTablePersistent(hDb, szTable); + } + break; + case RemoteMsiSession::MsiDoAction: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szAction = pReq->fields[1].szValue; + ret = ::MsiDoAction(hInstall, szAction); + } + break; + case RemoteMsiSession::MsiEnumComponentCosts: + { + ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiEvaluateCondition: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szCondition = pReq->fields[1].szValue; + ret = ::MsiEvaluateCondition(hInstall, szCondition); + } + break; + case RemoteMsiSession::MsiGetComponentState: + { + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureCost: + { + ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureState: + { + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureValidStates: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetLanguage: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiGetLanguage(hInstall); + } + break; + case RemoteMsiSession::MsiGetLastErrorRecord: + { + ret = ::MsiGetLastErrorRecord(); + } + break; + case RemoteMsiSession::MsiGetMode: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue; + ret = ::MsiGetMode(hInstall, iRunMode); + } + break; + case RemoteMsiSession::MsiGetSourcePath: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiGetSummaryInformation: + { + ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetTargetPath: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiRecordDataSize: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordDataSize(hRecord, iField); + } + break; + case RemoteMsiSession::MsiRecordReadStream: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + DWORD cbRead = (DWORD) pReq->fields[2].uiValue; + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2); + if (ret == 0) + { + ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead); + if (ret == 0) + { + pResp->fields[1].vt = VT_STREAM; + pResp->fields[1].szValue = m_pBufSend; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].uiValue = (UINT) cbRead; + } + } + } + break; + case RemoteMsiSession::MsiRecordSetStream: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + const wchar_t* szFilePath = pReq->fields[2].szValue; + ret = ::MsiRecordSetStream(hRecord, iField, szFilePath); + } + break; + case RemoteMsiSession::MsiSequence: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szTable = pReq->fields[1].szValue; + UINT iSequenceMode = pReq->fields[2].uiValue; + ret = ::MsiSequence(hRecord, szTable, iSequenceMode); + } + break; + case RemoteMsiSession::MsiSetComponentState: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szComponent = pReq->fields[1].szValue; + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; + ret = ::MsiSetComponentState(hInstall, szComponent, iState); + } + break; + case RemoteMsiSession::MsiSetFeatureAttributes: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFeature = pReq->fields[1].szValue; + DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue; + ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs); + } + break; + case RemoteMsiSession::MsiSetFeatureState: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFeature = pReq->fields[1].szValue; + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; + ret = ::MsiSetFeatureState(hInstall, szFeature, iState); + } + break; + case RemoteMsiSession::MsiSetInstallLevel: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + int iInstallLevel = pReq->fields[1].iValue; + ret = ::MsiSetInstallLevel(hInstall, iInstallLevel); + } + break; + case RemoteMsiSession::MsiSetMode: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue; + BOOL fState = (BOOL) pReq->fields[2].iValue; + ret = ::MsiSetMode(hInstall, iRunMode, fState); + } + break; + case RemoteMsiSession::MsiSetTargetPath: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFolder = pReq->fields[1].szValue; + const wchar_t* szFolderPath = pReq->fields[2].szValue; + ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath); + } + break; + case RemoteMsiSession::MsiSummaryInfoGetProperty: + { + MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue; + UINT uiProperty = pReq->fields[1].uiValue; + UINT uiDataType; + int iValue; + FILETIME ftValue; + m_pBufSend[0] = L'\0'; + DWORD cchValue = m_cbBufSend; + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue); + if (ret == 0) + { + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_UI4; + pResp->fields[1].uiValue = uiDataType; + + switch (uiDataType) + { + case VT_I2: + case VT_I4: + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = iValue; + break; + case VT_FILETIME: + pResp->fields[2].vt = VT_UI4; + pResp->fields[2].iValue = ftValue.dwHighDateTime; + pResp->fields[3].vt = VT_UI4; + pResp->fields[3].iValue = ftValue.dwLowDateTime; + break; + case VT_LPSTR: + pResp->fields[2].vt = VT_LPWSTR; + pResp->fields[2].szValue = m_pBufSend; + break; + } + } + } + break; + case RemoteMsiSession::MsiVerifyDiskSpace: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiVerifyDiskSpace(hInstall); + } + break; + + default: + { + ret = ERROR_INVALID_FUNCTION; + } + break; + } + } + + pResp->fields[0].vt = VT_UI4; + pResp->fields[0].uiValue = ret; +} 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 @@ +// 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. + +#define LARGE_BUFFER_THRESHOLD 65536 // bytes +#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts + +/////////////////////////////////////////////////////////////////////////////////////// +// RemoteMsiSession // +////////////////////// +// +// Allows accessing MSI APIs from another process using named pipes. +// +class RemoteMsiSession +{ +public: + + // This enumeration MUST stay in sync with the + // managed equivalent in RemotableNativeMethods.cs! + enum RequestId + { + EndSession = 0, + MsiCloseHandle, + MsiCreateRecord, + MsiDatabaseGetPrimaryKeys, + MsiDatabaseIsTablePersistent, + MsiDatabaseOpenView, + MsiDoAction, + MsiEnumComponentCosts, + MsiEvaluateCondition, + MsiFormatRecord, + MsiGetActiveDatabase, + MsiGetComponentState, + MsiGetFeatureCost, + MsiGetFeatureState, + MsiGetFeatureValidStates, + MsiGetLanguage, + MsiGetLastErrorRecord, + MsiGetMode, + MsiGetProperty, + MsiGetSourcePath, + MsiGetSummaryInformation, + MsiGetTargetPath, + MsiProcessMessage, + MsiRecordClearData, + MsiRecordDataSize, + MsiRecordGetFieldCount, + MsiRecordGetInteger, + MsiRecordGetString, + MsiRecordIsNull, + MsiRecordReadStream, + MsiRecordSetInteger, + MsiRecordSetStream, + MsiRecordSetString, + MsiSequence, + MsiSetComponentState, + MsiSetFeatureAttributes, + MsiSetFeatureState, + MsiSetInstallLevel, + MsiSetMode, + MsiSetProperty, + MsiSetTargetPath, + MsiSummaryInfoGetProperty, + MsiVerifyDiskSpace, + MsiViewExecute, + MsiViewFetch, + MsiViewGetError, + MsiViewGetColumnInfo, + MsiViewModify, + }; + + static const int MAX_REQUEST_FIELDS = 4; + + // Used to pass data back and forth for remote API calls, + // including in & out params & return values. + // Only strings and ints are supported. + struct RequestData + { + struct + { + VARENUM vt; + union { + int iValue; + UINT uiValue; + DWORD cchValue; + LPWSTR szValue; + BYTE* sValue; + DWORD cbValue; + }; + } fields[MAX_REQUEST_FIELDS]; + }; + +public: + + // This value is set from the single data parameter in the EndSession request. + // It saves the exit code of the out-of-proc custom action. + int ExitCode; + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession constructor + // + // Creates a new remote session instance, for use either by the server + // or client process. + // + // szName - Identifies the session instance being remoted. The server and + // the client must use the same name. The name should be unique + // enough to avoid conflicting with other instances on the system. + // + // fServer - True if the calling process is the server process, false if the + // calling process is the client process. + // + RemoteMsiSession(const wchar_t* szName, bool fServer=true) + : m_fServer(fServer), + m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"), + m_szPipeName(NULL), + m_hPipe(NULL), + m_fConnecting(false), + m_fConnected(false), + m_hReceiveThread(NULL), + m_hReceiveStopEvent(NULL), + m_pBufReceive(NULL), + m_cbBufReceive(0), + m_pBufSend(NULL), + m_cbBufSend(0), + ExitCode(ERROR_INSTALL_FAILURE) + { + SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED)); + m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession destructor + // + // Closes any open handles and frees any allocated memory. + // + ~RemoteMsiSession() + { + WaitExitCode(); + if (m_hPipe != NULL) + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + if (m_overlapped.hEvent != NULL) + { + CloseHandle(m_overlapped.hEvent); + m_overlapped.hEvent = NULL; + } + if (m_szPipeName != NULL) + { + delete[] m_szPipeName; + m_szPipeName = NULL; + } + if (m_pBufReceive != NULL) + { + SecureZeroMemory(m_pBufReceive, m_cbBufReceive); + delete[] m_pBufReceive; + m_pBufReceive = NULL; + } + if (m_pBufSend != NULL) + { + SecureZeroMemory(m_pBufSend, m_cbBufSend); + delete[] m_pBufSend; + m_pBufSend = NULL; + } + m_fConnecting = false; + m_fConnected = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::WaitExitCode() + // + // Waits for the server processing thread to complete. + // + void WaitExitCode() + { + if (m_hReceiveThread != NULL) + { + SetEvent(m_hReceiveStopEvent); + WaitForSingleObject(m_hReceiveThread, INFINITE); + CloseHandle(m_hReceiveThread); + m_hReceiveThread = NULL; + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::Connect() + // + // Connects the inter-process communication channel. + // (Currently implemented as a named pipe.) + // + // This method must be called first by the server process, then by the client + // process. The method does not block; the server will asynchronously wait + // for the client process to make the connection. + // + // Returns: 0 on success, Win32 error code on failure. + // + virtual DWORD Connect() + { + const wchar_t* szPipePrefix = L"\\\\.\\pipe\\"; + size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1; + m_szPipeName = new wchar_t[cchPipeNameBuf]; + + if (m_szPipeName == NULL) + { + return ERROR_OUTOFMEMORY; + } + else + { + wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix); + wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName); + + if (m_fServer) + { + return this->ConnectPipeServer(); + } + else + { + return this->ConnectPipeClient(); + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::IsConnected() + // + // Checks if the server process and client process are currently connected. + // + virtual bool IsConnected() const + { + return m_fConnected; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::ProcessRequests() + // + // For use by the service process. Watches for requests in the input buffer and calls + // the callback for each one. + // + // This method does not block; it spawns a separate thread to do the work. + // + // Returns: 0 on success, Win32 error code on failure. + // + virtual DWORD ProcessRequests() + { + return this->StartProcessingReqests(); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::SendRequest() + // + // For use by the client process. Sends a request to the server and + // synchronously waits on a response, up to the timeout value. + // + // id - ID code of the MSI API call being requested. + // + // pRequest - Pointer to a data structure containing request parameters. + // + // ppResponse - [OUT] Pointer to a location that receives the response parameters. + // + // Returns: 0 on success, Win32 error code on failure. + // Returns WAIT_TIMEOUT if no response was received in time. + // + virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse) + { + if (m_fServer) + { + return ERROR_INVALID_OPERATION; + } + + if (!m_fConnected) + { + *ppResponse = NULL; + return 0; + } + + DWORD dwRet = this->SendRequest(id, pRequest); + if (dwRet != 0) + { + return dwRet; + } + + if (id != EndSession) + { + static RequestData response; + if (ppResponse != NULL) + { + *ppResponse = &response; + } + + return this->ReceiveResponse(id, &response); + } + else + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + m_fConnected = false; + return 0; + } + } + +private: + + // + // Do not allow assignment. + // + RemoteMsiSession& operator=(const RemoteMsiSession&); + + // + // Called only by the server process. + // Create a new thread to handle receiving requests. + // + DWORD StartProcessingReqests() + { + if (!m_fServer || m_hReceiveStopEvent != NULL) + { + return ERROR_INVALID_OPERATION; + } + + DWORD dwRet = 0; + + m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (m_hReceiveStopEvent == NULL) + { + dwRet = GetLastError(); + } + else + { + if (m_hReceiveThread != NULL) + { + CloseHandle(m_hReceiveThread); + } + + m_hReceiveThread = CreateThread(NULL, 0, + RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL); + + if (m_hReceiveThread == NULL) + { + dwRet = GetLastError(); + CloseHandle(m_hReceiveStopEvent); + m_hReceiveStopEvent = NULL; + } + } + + return dwRet; + } + + // + // Called only by the watcher process. + // First verify the connection is complete. Then continually read and parse messages, + // invoke the callback, and send the replies. + // + static DWORD WINAPI ProcessRequestsThreadStatic(void* pv) + { + return reinterpret_cast(pv)->ProcessRequestsThread(); + } + + DWORD ProcessRequestsThread() + { + DWORD dwRet; + + dwRet = CompleteConnection(); + if (dwRet != 0) + { + if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0; + } + + while (m_fConnected) + { + RequestId id; + RequestData req; + dwRet = ReceiveRequest(&id, &req); + if (dwRet != 0) + { + if (dwRet == ERROR_OPERATION_ABORTED || + dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA) + { + dwRet = 0; + } + } + else + { + RequestData resp; + ProcessRequest(id, &req, &resp); + + if (id == EndSession) + { + break; + } + + dwRet = SendResponse(id, &resp); + if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA) + { + dwRet = 0; + } + } + } + + CloseHandle(m_hReceiveStopEvent); + m_hReceiveStopEvent = NULL; + return dwRet; + } + + // + // Called only by the server process's receive thread. + // Read one request into a RequestData object. + // + DWORD ReceiveRequest(RequestId* pId, RequestData* pReq) + { + DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId)); + + if (dwRet == 0) + { + dwRet = this->ReadRequestData(pReq); + } + + return dwRet; + } + + // + // Called by the server process's receive thread or the client's request call + // to read the response. Read data from the pipe, allowing interruption by the + // stop event if on the server. + // + DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead) + { + DWORD dwRet = 0; + DWORD dwTotalBytesRead = 0; + + while (dwRet == 0 && dwTotalBytesRead < cbRead) + { + DWORD dwBytesReadThisTime; + ResetEvent(m_overlapped.hEvent); + if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped)) + { + dwRet = GetLastError(); + if (dwRet == ERROR_IO_PENDING) + { + if (m_fServer) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + } + else + { + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); + } + + if (dwRet == WAIT_OBJECT_0) + { + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE)) + { + dwRet = GetLastError(); + } + } + else if (dwRet == WAIT_FAILED) + { + dwRet = GetLastError(); + } + else + { + dwRet = ERROR_OPERATION_ABORTED; + } + } + } + + dwTotalBytesRead += dwBytesReadThisTime; + } + + if (dwRet != 0) + { + if (m_fServer) + { + CancelIo(m_hPipe); + DisconnectNamedPipe(m_hPipe); + } + else + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + m_fConnected = false; + } + + return dwRet; + } + + // + // Called only by the server process. + // Given a request, invoke the MSI API and return the response. + // This is implemented in RemoteMsi.cpp. + // + void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp); + + // + // Called only by the client process. + // Send request data over the pipe. + // + DWORD SendRequest(RequestId id, const RequestData* pRequest) + { + DWORD dwRet = WriteRequestData(id, pRequest); + + if (dwRet != 0) + { + m_fConnected = false; + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + + return dwRet; + } + + // + // Called only by the server process. + // Just send a response over the pipe. + // + DWORD SendResponse(RequestId id, const RequestData* pResp) + { + DWORD dwRet = WriteRequestData(id, pResp); + + if (dwRet != 0) + { + DisconnectNamedPipe(m_hPipe); + m_fConnected = false; + } + + return dwRet; + } + + // + // Called either by the client or server process. + // Writes data to the pipe for a request or response. + // + DWORD WriteRequestData(RequestId id, const RequestData* pReq) + { + DWORD dwRet = 0; + + RequestData req = *pReq; // Make a copy because the const data can't be changed. + + dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId)); + if (dwRet != 0) + { + return dwRet; + } + + BYTE* sValues[MAX_REQUEST_FIELDS] = {0}; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (req.fields[i].vt == VT_LPWSTR) + { + sValues[i] = (BYTE*) req.fields[i].szValue; + req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue); + } + else if (req.fields[i].vt == VT_STREAM) + { + sValues[i] = req.fields[i].sValue; + req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue; + } + } + + dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData)); + if (dwRet != 0) + { + return dwRet; + } + + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (sValues[i] != NULL) + { + DWORD cbValue; + if (req.fields[i].vt == VT_LPWSTR) + { + cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR); + } + else + { + cbValue = req.fields[i].cbValue; + } + + dwRet = this->WritePipe(const_cast (sValues[i]), cbValue); + if (dwRet != 0) + { + break; + } + } + } + + return dwRet; + } + + // + // Called when writing a request or response. Writes data to + // the pipe, allowing interruption by the stop event if on the server. + // + DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite) + { + DWORD dwRet = 0; + DWORD dwTotalBytesWritten = 0; + + while (dwRet == 0 && dwTotalBytesWritten < cbWrite) + { + DWORD dwBytesWrittenThisTime; + ResetEvent(m_overlapped.hEvent); + if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped)) + { + dwRet = GetLastError(); + if (dwRet == ERROR_IO_PENDING) + { + if (m_fServer) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + } + else + { + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); + } + + if (dwRet == WAIT_OBJECT_0) + { + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE)) + { + dwRet = GetLastError(); + } + } + else if (dwRet == WAIT_FAILED) + { + dwRet = GetLastError(); + } + else + { + dwRet = ERROR_OPERATION_ABORTED; + } + } + } + + dwTotalBytesWritten += dwBytesWrittenThisTime; + } + + return dwRet; + } + + // + // Called either by the client or server process. + // Reads data from the pipe for a request or response. + // + DWORD ReadRequestData(RequestData* pReq) + { + DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData)); + + if (dwRet == 0) + { + DWORD cbData = 0; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (pReq->fields[i].vt == VT_LPWSTR) + { + cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); + } + else if (pReq->fields[i].vt == VT_STREAM) + { + cbData += pReq->fields[i].cbValue; + } + } + + if (cbData > 0) + { + if (!CheckRequestDataBuf(cbData)) + { + return ERROR_OUTOFMEMORY; + } + + dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData); + if (dwRet == 0) + { + DWORD dwOffset = 0; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (pReq->fields[i].vt == VT_LPWSTR) + { + LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset); + dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); + pReq->fields[i].szValue = szTemp; + } + else if (pReq->fields[i].vt == VT_STREAM) + { + BYTE* sTemp = m_pBufReceive + dwOffset; + dwOffset += pReq->fields[i].cbValue; + pReq->fields[i].sValue = sTemp; + } + } + } + } + } + + return dwRet; + } + + // + // Called only by the client process. + // Wait for a response on the pipe. If no response is received before the timeout, + // then give up and close the connection. + // + DWORD ReceiveResponse(RequestId id, RequestData* pResp) + { + RequestId responseId; + DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId)); + if (dwRet == 0 && responseId != id) + { + dwRet = ERROR_OPERATION_ABORTED; + } + + if (dwRet == 0) + { + dwRet = this->ReadRequestData(pResp); + } + + return dwRet; + } + + // + // Called only by the server process's receive thread. + // Try to complete and verify an asynchronous connection operation. + // + DWORD CompleteConnection() + { + DWORD dwRet = 0; + if (m_fConnecting) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + + if (dwWaitRes == WAIT_OBJECT_0) + { + m_fConnecting = false; + + DWORD dwUnused; + if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + } + } + else if (dwWaitRes == WAIT_FAILED) + { + CancelIo(m_hPipe); + dwRet = GetLastError(); + } + else + { + CancelIo(m_hPipe); + dwRet = ERROR_OPERATION_ABORTED; + } + } + return dwRet; + } + + // + // Called only by the server process. + // Creates a named pipe instance and begins asynchronously waiting + // for a connection from the client process. + // + DWORD ConnectPipeServer() + { + DWORD dwRet = 0; + const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes + m_hPipe = CreateNamedPipe( + m_szPipeName, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, BUFSIZE, BUFSIZE, 0, NULL); + if (m_hPipe == INVALID_HANDLE_VALUE) + { + m_hPipe = NULL; + dwRet = GetLastError(); + } + else if (ConnectNamedPipe(m_hPipe, &m_overlapped)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + + if (dwRet == ERROR_PIPE_BUSY) + { + // All pipe instances are busy, so wait for a maximum of 20 seconds + dwRet = 0; + if (WaitNamedPipe(m_szPipeName, 20000)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + } + } + + if (dwRet == ERROR_IO_PENDING) + { + dwRet = 0; + m_fConnecting = true; + } + } + return dwRet; + } + + // + // Called only by the client process. + // Attemps to open a connection to an existing named pipe instance + // which should have already been created by the server process. + // + DWORD ConnectPipeClient() + { + DWORD dwRet = 0; + m_hPipe = CreateFile( + m_szPipeName, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (m_hPipe != INVALID_HANDLE_VALUE) + { + m_fConnected = true; + } + else + { + m_hPipe = NULL; + dwRet = GetLastError(); + } + return dwRet; + } + + // + // Ensures that the request buffer is large enough to hold a request, + // reallocating the buffer if necessary. + // It will also reduce the buffer size if the previous allocation was very large. + // + BOOL CheckRequestDataBuf(DWORD cbBuf) + { + if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive)) + { + if (m_pBufReceive != NULL) + { + SecureZeroMemory(m_pBufReceive, m_cbBufReceive); + delete[] m_pBufReceive; + } + m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf); + m_pBufReceive = new BYTE[m_cbBufReceive]; + if (m_pBufReceive == NULL) + { + m_cbBufReceive = 0; + } + } + return m_pBufReceive != NULL; + } + +private: + + // Name of this instance. + const wchar_t* m_szName; + + // "\\.\pipe\name" + wchar_t* m_szPipeName; + + // Handle to the pipe instance. + HANDLE m_hPipe; + + // Handle to the thread that receives requests. + HANDLE m_hReceiveThread; + + // Handle to the event used to signal the receive thread to exit. + HANDLE m_hReceiveStopEvent; + + // All pipe I/O is done in overlapped mode to avoid unintentional blocking. + OVERLAPPED m_overlapped; + + // Dynamically-resized buffer for receiving requests. + BYTE* m_pBufReceive; + + // Current size of the receive request buffer. + DWORD m_cbBufReceive; + + // Dynamically-resized buffer for sending requests. + wchar_t* m_pBufSend; + + // Current size of the send request buffer. + DWORD m_cbBufSend; + + // True if this is the server process, false if this is the client process. + const bool m_fServer; + + // True if an asynchronous connection operation is currently in progress. + bool m_fConnecting; + + // True if the pipe is currently connected. + bool m_fConnected; +}; 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 @@ +// 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. + +#include "precomp.h" +#include "EntryPoints.h" +#include "SfxUtil.h" + +#define MANAGED_CAs_OUT_OF_PROC 1 + +HMODULE g_hModule; +bool g_fRunningOutOfProc = false; + +RemoteMsiSession* g_pRemote = NULL; + +// Prototypes for local functions. +// See the function definitions for comments. + +bool InvokeManagedCustomAction(MSIHANDLE hSession, + _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); + +/// +/// Entry-point for the CA DLL when re-launched as a separate process; +/// connects the comm channel for remote MSI APIs, then invokes the +/// managed custom action entry-point. +/// +/// +/// Do not change the parameters or calling-convention: RUNDLL32 +/// requires this exact signature. +/// +extern "C" +void __stdcall InvokeManagedCustomActionOutOfProc( + __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) +{ + UNREFERENCED_PARAMETER(hwnd); + UNREFERENCED_PARAMETER(hinst); + UNREFERENCED_PARAMETER(nCmdShow); + + g_fRunningOutOfProc = true; + + const wchar_t* szSessionName = szCmdLine; + MSIHANDLE hSession; + const wchar_t* szEntryPoint; + + int i; + for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; + hSession = _wtoi(szCmdLine + i); + + for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; + szEntryPoint = szCmdLine + i; + + g_pRemote = new RemoteMsiSession(szSessionName, false); + g_pRemote->Connect(); + + int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); + + RemoteMsiSession::RequestData requestData; + SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); + requestData.fields[0].vt = VT_I4; + requestData.fields[0].iValue = ret; + g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); + delete g_pRemote; +} + +/// +/// Re-launch this CA DLL as a separate process, and setup a comm channel +/// for remote MSI API calls back to this process. +/// +int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) +{ + wchar_t szSessionName[100] = {0}; + swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); + + RemoteMsiSession remote(szSessionName, true); + + DWORD ret = remote.Connect(); + if (ret != 0) + { + Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); + return ERROR_INSTALL_FAILURE; + } + + ret = remote.ProcessRequests(); + if (ret != 0) + { + Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); + return ERROR_INSTALL_FAILURE; + } + + wchar_t szModule[MAX_PATH] = {0}; + GetModuleFileName(g_hModule, szModule, MAX_PATH); + + const wchar_t* rundll32 = L"rundll32.exe"; + wchar_t szRunDll32Path[MAX_PATH] = {0}; + GetSystemDirectory(szRunDll32Path, MAX_PATH); + wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); + wcscat_s(szRunDll32Path, MAX_PATH, rundll32); + + const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; + wchar_t szCommandLine[1024] = {0}; + swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", + rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); + + STARTUPINFO si; + SecureZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + PROCESS_INFORMATION pi; + SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, + 0, NULL, NULL, &si, &pi)) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + + DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); + if (dwWait != WAIT_OBJECT_0) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to wait for CA process. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + + DWORD dwExitCode; + BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); + if (!bRet) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + else if (dwExitCode != 0) + { + Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); + return ERROR_INSTALL_FAILURE; + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + remote.WaitExitCode(); + return remote.ExitCode; +} + +/// +/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to +/// call MSI APIs remotely. +/// +void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) +{ + if (g_fRunningOutOfProc) + { + g_pRemote->SendRequest(id, pRequest, ppResponse); + } + else + { + *ppResponse = NULL; + } +} + +/// +/// Invokes a managed custom action from native code by +/// extracting the package to a temporary working directory +/// then hosting the CLR and locating and calling the entrypoint. +/// +/// Handle to the installation session. +/// Passed to custom action entrypoints by the installer engine. +/// Directory containing the CA binaries +/// and the CustomAction.config file defining the entrypoints. +/// This may be NULL, in which case the current module must have +/// a concatenated cabinet containing those files, which will be +/// extracted to a temporary directory. +/// Name of the CA entrypoint to be invoked. +/// This must be either an explicit "AssemblyName!Namespace.Class.Method" +/// string, or a simple name that maps to a full entrypoint definition +/// in CustomAction.config. +/// The value returned by the managed custom action method, +/// or ERROR_INSTALL_FAILURE if the CA could not be invoked. +int InvokeCustomAction(MSIHANDLE hSession, + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) +{ +#ifdef MANAGED_CAs_OUT_OF_PROC + if (!g_fRunningOutOfProc && szWorkingDir == NULL) + { + return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); + } +#endif + + wchar_t szTempDir[MAX_PATH]; + bool fDeleteTemp = false; + if (szWorkingDir == NULL) + { + if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) + { + return ERROR_INSTALL_FAILURE; + } + szWorkingDir = szTempDir; + fDeleteTemp = true; + } + + wchar_t szConfigFilePath[MAX_PATH + 20]; + StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); + + const wchar_t* szConfigFile = szConfigFilePath; + if (!::PathFileExists(szConfigFilePath)) + { + szConfigFile = NULL; + } + + wchar_t szWIAssembly[MAX_PATH + 50]; + StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); + + int iResult = ERROR_INSTALL_FAILURE; + ICorRuntimeHost* pHost; + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) + { + _AppDomain* pAppDomain; + if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, + szConfigFile, &pAppDomain)) + { + if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) + { + iResult = ERROR_INSTALL_FAILURE; + } + HRESULT hr = pHost->UnloadDomain(pAppDomain); + if (FAILED(hr)) + { + Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); + } + pAppDomain->Release(); + } + + pHost->Stop(); + pHost->Release(); + } + + if (fDeleteTemp) + { + DeleteDirectory(szTempDir); + } + return iResult; +} + +/// +/// Called by the system when the DLL is loaded. +/// Saves the module handle for later use. +/// +BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) +{ + UNREFERENCED_PARAMETER(pReserved); + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +/// +/// Loads and invokes the managed portion of the proxy. +/// +/// Handle to the installer session, +/// used for logging errors and to be passed on to the custom action. +/// AppDomain which has its application +/// base set to the CA working directory. +/// Name of the CA entrypoint to be invoked. +/// This must be either an explicit "AssemblyName!Namespace.Class.Method" +/// string, or a simple name that maps to a full entrypoint definition +/// in CustomAction.config. +/// Return value of the invoked custom +/// action method. +/// True if the managed proxy was invoked successfully, +/// false if there was some error. Note the custom action itself may +/// return an error via piResult while this method still returns true +/// since the invocation was successful. +bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szEntryPoint, int* piResult) +{ + VARIANT vResult; + ::VariantInit(&vResult); + + const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; + const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; + const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); + + _MethodInfo* pCAInvokeMethod; + if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, + szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) + { + return false; + } + + HRESULT hr; + VARIANT vNull; + vNull.vt = VT_EMPTY; + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); + VARIANT vSessionHandle; + vSessionHandle.vt = VT_I4; + vSessionHandle.intVal = hSession; + LONG index = 0; + hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); + if (FAILED(hr)) goto LExit; + VARIANT vEntryPoint; + vEntryPoint.vt = VT_BSTR; + vEntryPoint.bstrVal = SysAllocString(szEntryPoint); + if (vEntryPoint.bstrVal == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); + if (FAILED(hr)) goto LExit; + VARIANT vRemotingFunctionPtr; +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant + if (f64bit) +#pragma warning(pop) + { + vRemotingFunctionPtr.vt = VT_I8; + vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); + } + else + { + vRemotingFunctionPtr.vt = VT_I4; +#pragma warning(push) +#pragma warning(disable:4302) // truncation +#pragma warning(disable:4311) // pointer truncation + vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); +#pragma warning(pop) + } + index = 2; + hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); + if (FAILED(hr)) goto LExit; + + hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + pCAInvokeMethod->Release(); + + if (FAILED(hr)) + { + Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); + return false; + } + + *piResult = vResult.intVal; + return true; +} + 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 @@ +// 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. + +#define VER_DLL +#define VER_LANG_NEUTRAL +#define VER_ORIGINAL_FILENAME "SfxCA.dll" +#define VER_INTERNAL_NAME "SfxCA" +#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action" + +// Additional resources here + 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 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + {55D5BA28-D427-4F53-80C2-FE9EF23C1553} + DynamicLibrary + SfxCA + v142 + Unicode + EntryPoints.def + + + + + msi.lib;cabinet.lib;shlwapi.lib + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + 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}. + + + + \ 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 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {81c92f68-18c2-4cd4-a588-5c3616860dd9} + + + {6cdc30ee-e14d-4679-b92e-3e080535e53b} + + + {1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d} + + + + + Resource Files + + + + + Resource Files + + + + \ 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 @@ +// 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. + +#include "precomp.h" +#include "SfxUtil.h" + +/// +/// Writes a formatted message to the MSI log. +/// Does out-of-proc MSI calls if necessary. +/// +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) +{ + const int LOG_BUFSIZE = 4096; + wchar_t szBuf[LOG_BUFSIZE]; + va_list args; + va_start(args, szMessage); + StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); + + if (!g_fRunningOutOfProc || NULL == g_pRemote) + { + MSIHANDLE hRec = MsiCreateRecord(1); + MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); + MsiRecordSetString(hRec, 1, szBuf); + MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); + MsiCloseHandle(hRec); + } + else + { + // Logging is the only remote-MSI operation done from unmanaged code. + // It's not very convenient here because part of the infrastructure + // for remote MSI APIs is on the managed side. + + RemoteMsiSession::RequestData req; + RemoteMsiSession::RequestData* pResp = NULL; + SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); + + req.fields[0].vt = VT_UI4; + req.fields[0].uiValue = 1; + g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); + MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_UI4; + req.fields[1].uiValue = 0; + req.fields[2].vt = VT_LPWSTR; + req.fields[2].szValue = L"SFXCA: [1]"; + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_UI4; + req.fields[1].uiValue = 1; + req.fields[2].vt = VT_LPWSTR; + req.fields[2].szValue = szBuf; + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hSession; + req.fields[1].vt = VT_I4; + req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; + req.fields[2].vt = VT_I4; + req.fields[2].iValue = (int) hRec; + g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_EMPTY; + req.fields[2].vt = VT_EMPTY; + g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); + } +} + +/// +/// Deletes a directory, including all files and subdirectories. +/// +/// Path to the directory to delete, +/// not including a trailing backslash. +/// True if the directory was successfully deleted, or false +/// if the deletion failed (most likely because some files were locked). +/// +bool DeleteDirectory(const wchar_t* szDir) +{ + size_t cchDir = wcslen(szDir); + size_t cchPathBuf = cchDir + 3 + MAX_PATH; + wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); + if (szPath == NULL) return false; + StringCchCopy(szPath, cchPathBuf, szDir); + StringCchCat(szPath, cchPathBuf, L"\\*"); + WIN32_FIND_DATA fd; + HANDLE hSearch = FindFirstFile(szPath, &fd); + while (hSearch != INVALID_HANDLE_VALUE) + { + StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) + { + DeleteDirectory(szPath); + } + } + else + { + DeleteFile(szPath); + } + if (!FindNextFile(hSearch, &fd)) + { + FindClose(hSearch); + hSearch = INVALID_HANDLE_VALUE; + } + } + return RemoveDirectory(szDir) != 0; +} + +bool DirectoryExists(const wchar_t* szDir) +{ + if (szDir != NULL) + { + DWORD dwAttrs = GetFileAttributes(szDir); + if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + return true; + } + } + return false; +} + +/// +/// Extracts a cabinet that is concatenated to a module +/// to a new temporary directory. +/// +/// Handle to the installer session, +/// used just for logging. +/// Module that has the concatenated cabinet. +/// Buffer for returning the path of the +/// created temp directory. +/// Size in characters of the buffer. +/// True if the files were extracted, or false if the +/// buffer was too small or the directory could not be created +/// or the extraction failed for some other reason. +__success(return != false) +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) +{ + wchar_t szModule[MAX_PATH]; + DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); + if (cchCopied == 0) + { + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); + return false; + } + else if (cchCopied == MAX_PATH - 1) + { + Log(hSession, L"Failed to get module path -- path is too long."); + return false; + } + + if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) + { + Log(hSession, L"Temp directory buffer is NULL or too small."); + return false; + } + StringCchCopy(szTempDir, cchTempDirBuf, szModule); + StringCchCat(szTempDir, cchTempDirBuf, L"-"); + + DWORD cchTempDir = (DWORD) wcslen(szTempDir); + for (int i = 0; DirectoryExists(szTempDir); i++) + { + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); + } + + if (!CreateDirectory(szTempDir, NULL)) + { + cchCopied = GetTempPath(cchTempDirBuf, szTempDir); + if (cchCopied == 0 || cchCopied >= cchTempDirBuf) + { + Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError()); + return false; + } + + wchar_t* szModuleName = wcsrchr(szModule, L'\\'); + if (szModuleName == NULL) szModuleName = szModule; + else szModuleName = szModuleName + 1; + StringCchCat(szTempDir, cchTempDirBuf, szModuleName); + StringCchCat(szTempDir, cchTempDirBuf, L"-"); + + cchTempDir = (DWORD) wcslen(szTempDir); + for (int i = 0; DirectoryExists(szTempDir); i++) + { + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); + } + + if (!CreateDirectory(szTempDir, NULL)) + { + Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError()); + return false; + } + } + + Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); + int err = ExtractCabinet(szModule, szTempDir); + if (err != 0) + { + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); + DeleteDirectory(szTempDir); + return false; + } + return true; +} + 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 @@ +// 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. + +#include "RemoteMsiSession.h" + +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); + +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); + +bool DeleteDirectory(const wchar_t* szDir); + +__success(return != false) +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); + +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); + +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, + const wchar_t* szName, const wchar_t* szAppBase, + const wchar_t* szConfigFile, _AppDomain** ppAppDomain); + +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szAssembly, const wchar_t* szClass, + const wchar_t* szMethod, _MethodInfo** ppCAMethod); + +extern HMODULE g_hModule; +extern bool g_fRunningOutOfProc; + +extern RemoteMsiSession* g_pRemote; + + 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 @@ + + + + \ 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 @@ +// 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. + +#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 @@ +#pragma once +// 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. + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#import raw_interfaces_only rename("ReportEvent", "CorReportEvent") +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 @@ + + + + + + + + + + Platform=x64 + + + + + -- cgit v1.2.3-55-g6feb