/// <summary> /// Reflects on an input CA module to locate custom action entry-points. /// </summary> /// <param name="module">Assembly module with CA entry-points.</param> /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns> private static IDictionary <string, string> FindEntryPoints(string module) { log.WriteLine("Searching for custom action entry points " + "in {0}", Path.GetFileName(module)); Dictionary <string, string> entryPoints = new Dictionary <string, string>(); Assembly assembly = Assembly.ReflectionOnlyLoadFrom(module); foreach (Type type in assembly.GetExportedTypes()) { foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) { string entryPointName = MakeSfxCA.GetEntryPoint(method); if (entryPointName != null) { string 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); }
/// <summary> /// Packages up all the inputs to the output location. /// </summary> /// <exception cref="Exception">Various exceptions are thrown /// if things go wrong.</exception> public static void Build(string output, string sfxdll, IList <string> 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); } string customActionAssembly = inputs[0]; if (!File.Exists(customActionAssembly)) { throw new FileNotFoundException(customActionAssembly); } inputs = MakeSfxCA.SplitList(inputs); IDictionary <string, string> inputsMap = MakeSfxCA.GetPackFileMap(inputs); bool foundWIAssembly = false; foreach (string input in inputsMap.Keys) { if (String.Compare(input, MakeSfxCA.RequiredWIAssembly, StringComparison.OrdinalIgnoreCase) == 0) { foundWIAssembly = true; } } if (!foundWIAssembly) { throw new ArgumentException(MakeSfxCA.RequiredWIAssembly + " 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)); IDictionary <string, string> entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); string 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); } string 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); }
/// <summary> /// Writes a modified version of SfxCA.dll to the output stream, /// with the template entry-points mapped to the CA entry-points. /// </summary> /// <remarks> /// 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. /// </remarks> private static void WriteEntryModule( string sfxdll, Stream outputStream, IDictionary <string, string> entryPoints, string uiClass) { log.WriteLine("Modifying SfxCA.dll stub"); byte[] fileBytes; using (FileStream 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; byte[] emptyBytes = new byte[0]; int 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)); } string[] slotSort = new string[slotCount]; for (int i = 0; i < slotCount - entryPoints.Count; i++) { slotSort[i] = String.Empty; } entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); Array.Sort <string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); for (int i = 0; ; i++) { string templateName = String.Format(ENTRYPOINT_FORMAT, i); byte[] templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); byte[] templateUniBytes = Encoding.Unicode.GetBytes(templateName); int nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); if (nameOffset < 0) { break; } int pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); if (pathOffset < 0) { break; } string entryPointName = slotSort[i]; string 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)); } byte[] replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); byte[] 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 (string export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) { byte[] exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); int exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); if (exportOffset < 0) { throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); } byte[] 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)); } byte[] templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); byte[] replaceBytes = Encoding.Unicode.GetBytes(uiClass); // Fill in the embedded UI implementor class so the proxy knows which one to load. int replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); if (replaceOffset >= 0) { MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); } } outputStream.Write(fileBytes, 0, fileBytes.Length); }
/// <summary> /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. /// </summary> /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param> /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param> /// <remarks> /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them /// to the list of input files if found. /// </remarks> private static void ResolveDependentAssemblies(IDictionary <string, string> inputsMap, 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 (string inputFile in inputsMap.Values) { string inputName = Path.GetFileNameWithoutExtension(inputFile); string 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. inputsMap.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); } else { 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); }; }