// ZipFile.saveSelfExtractor.cs // ------------------------------------------------------------------ // // Copyright (c) 2008-2011 Dino Chiesa. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // // This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // // last saved (in emacs): // Time-stamp: <2011-August-10 19:22:46> // // ------------------------------------------------------------------ // // This is a the source module that implements the stuff for saving to a // self-extracting Zip archive. // // ZipFile is set up as a "partial class" - defined in multiple .cs source modules. // This is one of the source modules for the ZipFile class. // // Here's the design: The self-extracting zip file is just a regular managed EXE // file, with embedded resources. The managed code logic instantiates a ZipFile, and // then extracts each entry. The embedded resources include the zip archive content, // as well as the Zip library itself. The latter is required so that self-extracting // can work on any machine, whether or not it has the DotNetZip library installed on // it. // // What we need to do is create the animal I just described, within a method on the // ZipFile class. This source module provides that capability. The method is // SaveSelfExtractor(). // // The way the method works: it uses the programmatic interface to the csc.exe // compiler, Microsoft.CSharp.CSharpCodeProvider, to compile "boilerplate" // extraction logic into a new assembly. As part of that compile, we embed within // that assembly the zip archive itself, as well as the Zip library. // // Therefore we need to first save to a temporary zip file, then produce the exe. // // There are a few twists. // // The Visual Studio Project structure is a little weird. There are code files // that ARE NOT compiled during a normal build of the VS Solution. They are // marked as embedded resources. These are the various "boilerplate" modules that // are used in the self-extractor. These modules are: WinFormsSelfExtractorStub.cs // WinFormsSelfExtractorStub.Designer.cs CommandLineSelfExtractorStub.cs // PasswordDialog.cs PasswordDialog.Designer.cs // // At design time, if you want to modify the way the GUI looks, you have to // mark those modules to have a "compile" build action. Then tweak em, test, // etc. Then again mark them as "Embedded resource". // // ------------------------------------------------------------------ using System; using System.Reflection; using System.IO; using System.Collections.Generic; namespace OfficeOpenXml.Packaging.Ionic.Zip { #if !NO_SFX /// /// An enum that provides the different self-extractor flavors /// internal enum SelfExtractorFlavor { /// /// A self-extracting zip archive that runs from the console or /// command line. /// ConsoleApplication = 0, /// /// A self-extracting zip archive that presents a graphical user /// interface when it is executed. /// WinFormsApplication, } /// /// The options for generating a self-extracting archive. /// internal class SelfExtractorSaveOptions { /// /// The type of SFX to create. /// public SelfExtractorFlavor Flavor { get; set; } /// /// The command to run after extraction. /// /// /// /// /// This is optional. Leave it empty (null in C# or Nothing in /// VB) to run no command after extraction. /// /// /// /// If it is non-empty, the SFX will execute the command specified in this /// string on the user's machine, and using the extract directory as the /// working directory for the process, after unpacking the archive. The /// program to execute can include a path, if you like. If you want to execute /// a program that accepts arguments, specify the program name, followed by a /// space, and then the arguments for the program, each separated by a space, /// just as you would on a normal command line. Example: program.exe arg1 /// arg2. The string prior to the first space will be taken as the /// program name, and the string following the first space specifies the /// arguments to the program. /// /// /// /// If you want to execute a program that has a space in the name or path of /// the file, surround the program name in double-quotes. The first character /// of the command line should be a double-quote character, and there must be /// a matching double-quote following the end of the program file name. Any /// optional arguments to the program follow that, separated by /// spaces. Example: "c:\project files\program name.exe" arg1 arg2. /// /// /// /// If the flavor of the SFX is SelfExtractorFlavor.ConsoleApplication, /// then the SFX starts a new process, using this string as the post-extract /// command line. The SFX waits for the process to exit. The exit code of /// the post-extract command line is returned as the exit code of the /// command-line self-extractor exe. A non-zero exit code is typically used to /// indicated a failure by the program. In the case of an SFX, a non-zero exit /// code may indicate a failure during extraction, OR, it may indicate a /// failure of the run-after-extract program if specified, OR, it may indicate /// the run-after-extract program could not be fuond. There is no way to /// distinguish these conditions from the calling shell, aside from parsing /// the output of the SFX. If you have Quiet set to true, you may not /// see error messages, if a problem occurs. /// /// /// /// If the flavor of the SFX is /// SelfExtractorFlavor.WinFormsApplication, then the SFX starts a new /// process, using this string as the post-extract command line, and using the /// extract directory as the working directory for the process. The SFX does /// not wait for the command to complete, and does not check the exit code of /// the program. If the run-after-extract program cannot be fuond, a message /// box is displayed indicating that fact. /// /// /// /// You can specify environment variables within this string, with a format like /// %NAME%. The value of these variables will be expanded at the time /// the SFX is run. Example: %WINDIR%\system32\xcopy.exe may expand at /// runtime to c:\Windows\System32\xcopy.exe. /// /// /// /// By combining this with the RemoveUnpackedFilesAfterExecute /// flag, you can create an SFX that extracts itself, runs a file that /// was extracted, then deletes all the files that were extracted. If /// you want it to run "invisibly" then set Flavor to /// SelfExtractorFlavor.ConsoleApplication, and set Quiet /// to true. The user running such an EXE will see a console window /// appear, then disappear quickly. You may also want to specify the /// default extract location, with DefaultExtractDirectory. /// /// /// /// If you set Flavor to /// SelfExtractorFlavor.WinFormsApplication, and set Quiet to /// true, then a GUI with progressbars is displayed, but it is /// "non-interactive" - it accepts no input from the user. Instead the SFX /// just automatically unpacks and exits. /// /// /// public String PostExtractCommandLine { get; set; } /// /// The default extract directory the user will see when /// running the self-extracting archive. /// /// /// /// /// Passing null (or Nothing in VB) here will cause the Self Extractor to use /// the the user's personal directory () for the default extract /// location. /// /// /// /// This is only a default location. The actual extract location will be /// settable on the command line when the SFX is executed. /// /// /// /// You can specify environment variables within this string, /// with %NAME%. The value of these variables will be /// expanded at the time the SFX is run. Example: /// %USERPROFILE%\Documents\unpack may expand at runtime to /// c:\users\melvin\Documents\unpack. /// /// public String DefaultExtractDirectory { get; set; } /// /// The name of an .ico file in the filesystem to use for the application icon /// for the generated SFX. /// /// /// /// /// Normally, DotNetZip will embed an "zipped folder" icon into the generated /// SFX. If you prefer to use a different icon, you can specify it here. It /// should be a .ico file. This file is passed as the /win32icon /// option to the csc.exe compiler when constructing the SFX file. /// /// /// public string IconFile { get; set; } /// /// Whether the ConsoleApplication SFX will be quiet during extraction. /// /// /// /// /// This option affects the way the generated SFX runs. By default it is /// false. When you set it to true,... /// /// /// /// /// Flavor /// Behavior /// /// /// /// ConsoleApplication /// no messages will be emitted during successful /// operation. Double-clicking the SFX in Windows /// Explorer or as an attachment in an email will cause a console /// window to appear briefly, before it disappears. If you run the /// ConsoleApplication SFX from the cmd.exe prompt, it runs as a /// normal console app; by default, because it is quiet, it displays /// no messages to the console. If you pass the -v+ command line /// argument to the Console SFX when you run it, you will get verbose /// messages to the console. /// /// /// /// /// WinFormsApplication /// the SFX extracts automatically when the application /// is launched, with no additional user input. /// /// /// /// /// /// /// When you set it to false,... /// /// /// /// /// Flavor /// Behavior /// /// /// /// ConsoleApplication /// the extractor will emit a /// message to the console for each entry extracted. /// /// When double-clicking to launch the SFX, the console window will /// remain, and the SFX will emit a message for each file as it /// extracts. The messages fly by quickly, they won't be easily /// readable, unless the extracted files are fairly large. /// /// /// /// /// /// WinFormsApplication /// the SFX presents a forms UI and allows the user to select /// options before extracting. /// /// /// /// /// /// public bool Quiet { get; set; } /// /// Specify what the self-extractor will do when extracting an entry /// would overwrite an existing file. /// /// /// /// The default behavvior is to Throw. /// /// public Ionic.Zip.ExtractExistingFileAction ExtractExistingFile { get; set; } /// /// Whether to remove the files that have been unpacked, after executing the /// PostExtractCommandLine. /// /// /// /// /// If true, and if there is a /// PostExtractCommandLine, and if the command runs successfully, /// then the files that the SFX unpacked will be removed, afterwards. If /// the command does not complete successfully (non-zero return code), /// that is interpreted as a failure, and the extracted files will not be /// removed. /// /// /// /// Setting this flag, and setting Flavor to /// SelfExtractorFlavor.ConsoleApplication, and setting Quiet to /// true, results in an SFX that extracts itself, runs a file that was /// extracted, then deletes all the files that were extracted, with no /// intervention by the user. You may also want to specify the default /// extract location, with DefaultExtractDirectory. /// /// /// public bool RemoveUnpackedFilesAfterExecute { get; set; } /// /// The file version number to embed into the generated EXE. It will show up, for /// example, during a mouseover in Windows Explorer. /// /// public Version FileVersion { get; set; } /// /// The product version to embed into the generated EXE. It will show up, for /// example, during a mouseover in Windows Explorer. /// /// /// /// You can use any arbitrary string, but a human-readable version number is /// recommended. For example "v1.2 alpha" or "v4.2 RC2". If you specify nothing, /// then there is no product version embedded into the EXE. /// /// public String ProductVersion { get; set; } /// /// The copyright notice, if any, to embed into the generated EXE. /// /// /// /// It will show up, for example, while viewing properties of the file in /// Windows Explorer. You can use any arbitrary string, but typically you /// want something like "Copyright � Dino Chiesa 2011". /// /// public String Copyright { get; set; } /// /// The description to embed into the generated EXE. /// /// /// /// Use any arbitrary string. This text will be displayed during a /// mouseover in Windows Explorer. If you specify nothing, then the string /// "DotNetZip SFX Archive" is embedded into the EXE as the description. /// /// public String Description { get; set; } /// /// The product name to embed into the generated EXE. /// /// /// /// Use any arbitrary string. This text will be displayed /// while viewing properties of the EXE file in /// Windows Explorer. /// /// public String ProductName { get; set; } /// /// The title to display in the Window of a GUI SFX, while it extracts. /// /// /// /// /// By default the title show in the GUI window of a self-extractor /// is "DotNetZip Self-extractor (http://DotNetZip.codeplex.com/)". /// You can change that by setting this property before saving the SFX. /// /// /// /// This property has an effect only when producing a Self-extractor /// of flavor SelfExtractorFlavor.WinFormsApplication. /// /// /// public String SfxExeWindowTitle { // workitem 12608 get; set; } /// /// Additional options for the csc.exe compiler, when producing the SFX /// EXE. /// /// public string AdditionalCompilerSwitches { get; set; } } partial class ZipFile { class ExtractorSettings { public SelfExtractorFlavor Flavor; public List ReferencedAssemblies; public List CopyThroughResources; public List ResourcesToCompile; } private static ExtractorSettings[] SettingsList = { new ExtractorSettings() { Flavor = SelfExtractorFlavor.WinFormsApplication, ReferencedAssemblies= new List{ "System.dll", "System.Windows.Forms.dll", "System.Drawing.dll"}, CopyThroughResources = new List{ "Ionic.Zip.WinFormsSelfExtractorStub.resources", "Ionic.Zip.Forms.PasswordDialog.resources", "Ionic.Zip.Forms.ZipContentsDialog.resources"}, ResourcesToCompile = new List{ "WinFormsSelfExtractorStub.cs", "WinFormsSelfExtractorStub.Designer.cs", // .Designer.cs? "PasswordDialog.cs", "PasswordDialog.Designer.cs", //.Designer.cs" "ZipContentsDialog.cs", "ZipContentsDialog.Designer.cs", //.Designer.cs" "FolderBrowserDialogEx.cs", } }, new ExtractorSettings() { Flavor = SelfExtractorFlavor.ConsoleApplication, ReferencedAssemblies= new List { "System.dll", }, CopyThroughResources = null, ResourcesToCompile = new List{"CommandLineSelfExtractorStub.cs"} } }; //string _defaultExtractLocation; //string _postExtractCmdLine; // string _SetDefaultLocationCode = // "namespace OfficeOpenXml.Packaging.Ionic.Zip { internal partial class WinFormsSelfExtractorStub { partial void _SetDefaultExtractLocation() {" + // " txtExtractDirectory.Text = \"@@VALUE\"; } }}"; /// /// Saves the ZipFile instance to a self-extracting zip archive. /// /// /// /// /// /// The generated exe image will execute on any machine that has the .NET /// Framework 2.0 installed on it. The generated exe image is also a /// valid ZIP file, readable with DotNetZip or another Zip library or tool /// such as WinZip. /// /// /// /// There are two "flavors" of self-extracting archive. The /// WinFormsApplication version will pop up a GUI and allow the /// user to select a target directory into which to extract. There's also /// a checkbox allowing the user to specify to overwrite existing files, /// and another checkbox to allow the user to request that Explorer be /// opened to see the extracted files after extraction. The other flavor /// is ConsoleApplication. A self-extractor generated with that /// flavor setting will run from the command line. It accepts command-line /// options to set the overwrite behavior, and to specify the target /// extraction directory. /// /// /// /// There are a few temporary files created during the saving to a /// self-extracting zip. These files are created in the directory pointed /// to by , which defaults to . These temporary files are /// removed upon successful completion of this method. /// /// /// /// When a user runs the WinForms SFX, the user's personal directory (Environment.SpecialFolder.Personal) /// will be used as the default extract location. If you want to set the /// default extract location, you should use the other overload of /// SaveSelfExtractor()/ The user who runs the SFX will have the /// opportunity to change the extract directory before extracting. When /// the user runs the Command-Line SFX, the user must explicitly specify /// the directory to which to extract. The .NET Framework 2.0 is required /// on the computer when the self-extracting archive is run. /// /// /// /// NB: This method is not available in the version of DotNetZip build for /// the .NET Compact Framework, nor in the "Reduced" DotNetZip library. /// /// /// /// /// /// /// string DirectoryPath = "c:\\Documents\\Project7"; /// using (ZipFile zip = new ZipFile()) /// { /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)); /// zip.Comment = "This will be embedded into a self-extracting console-based exe"; /// zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication); /// } /// /// /// Dim DirectoryPath As String = "c:\Documents\Project7" /// Using zip As New ZipFile() /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)) /// zip.Comment = "This will be embedded into a self-extracting console-based exe" /// zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication) /// End Using /// /// /// /// /// a pathname, possibly fully qualified, to be created. Typically it /// will end in an .exe extension. /// /// Indicates whether a Winforms or Console self-extractor is /// desired. internal void SaveSelfExtractor(string exeToGenerate, SelfExtractorFlavor flavor) { SelfExtractorSaveOptions options = new SelfExtractorSaveOptions(); options.Flavor = flavor; SaveSelfExtractor(exeToGenerate, options); } /// /// Saves the ZipFile instance to a self-extracting zip archive, using /// the specified save options. /// /// /// /// /// This method saves a self extracting archive, using the specified save /// options. These options include the flavor of the SFX, the default extract /// directory, the icon file, and so on. See the documentation /// for for more /// details. /// /// /// /// The user who runs the SFX will have the opportunity to change the extract /// directory before extracting. If at the time of extraction, the specified /// directory does not exist, the SFX will create the directory before /// extracting the files. /// /// /// /// /// /// This example saves a WinForms-based self-extracting archive EXE that /// will use c:\ExtractHere as the default extract location. The C# code /// shows syntax for .NET 3.0, which uses an object initializer for /// the SelfExtractorOptions object. /// /// string DirectoryPath = "c:\\Documents\\Project7"; /// using (ZipFile zip = new ZipFile()) /// { /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)); /// zip.Comment = "This will be embedded into a self-extracting WinForms-based exe"; /// var options = new SelfExtractorOptions /// { /// Flavor = SelfExtractorFlavor.WinFormsApplication, /// DefaultExtractDirectory = "%USERPROFILE%\\ExtractHere", /// PostExtractCommandLine = ExeToRunAfterExtract, /// SfxExeWindowTitle = "My Custom Window Title", /// RemoveUnpackedFilesAfterExecute = true /// }; /// zip.SaveSelfExtractor("archive.exe", options); /// } /// /// /// Dim DirectoryPath As String = "c:\Documents\Project7" /// Using zip As New ZipFile() /// zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath)) /// zip.Comment = "This will be embedded into a self-extracting console-based exe" /// Dim options As New SelfExtractorOptions() /// options.Flavor = SelfExtractorFlavor.WinFormsApplication /// options.DefaultExtractDirectory = "%USERPROFILE%\\ExtractHere" /// options.PostExtractCommandLine = ExeToRunAfterExtract /// options.SfxExeWindowTitle = "My Custom Window Title" /// options.RemoveUnpackedFilesAfterExecute = True /// zip.SaveSelfExtractor("archive.exe", options) /// End Using /// /// /// /// The name of the EXE to generate. /// provides the options for creating the /// Self-extracting archive. internal void SaveSelfExtractor(string exeToGenerate, SelfExtractorSaveOptions options) { // Save an SFX that is both an EXE and a ZIP. // Check for the case where we are re-saving a zip archive // that was originally instantiated with a stream. In that case, // the _name will be null. If so, we set _writestream to null, // which insures that we'll cons up a new WriteStream (with a filesystem // file backing it) in the Save() method. if (_name == null) _writestream = null; _SavingSfx = true; _name = exeToGenerate; if (Directory.Exists(_name)) throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "exeToGenerate")); _contentsChanged = true; _fileAlreadyExists = File.Exists(_name); _SaveSfxStub(exeToGenerate, options); Save(); _SavingSfx = false; } private static void ExtractResourceToFile(Assembly a, string resourceName, string filename) { int n = 0; byte[] bytes = new byte[1024]; using (Stream instream = a.GetManifestResourceStream(resourceName)) { if (instream == null) throw new ZipException(String.Format("missing resource '{0}'", resourceName)); using (FileStream outstream = File.OpenWrite(filename)) { do { n = instream.Read(bytes, 0, bytes.Length); outstream.Write(bytes, 0, n); } while (n > 0); } } } private void _SaveSfxStub(string exeToGenerate, SelfExtractorSaveOptions options) { string nameOfIconFile = null; string stubExe = null; string unpackedResourceDir = null; string tmpDir = null; try { if (File.Exists(exeToGenerate)) { if (Verbose) StatusMessageTextWriter.WriteLine("The existing file ({0}) will be overwritten.", exeToGenerate); } if (!exeToGenerate.EndsWith(".exe")) { if (Verbose) StatusMessageTextWriter.WriteLine("Warning: The generated self-extracting file will not have an .exe extension."); } // workitem 10553 tmpDir = TempFileFolder ?? Path.GetDirectoryName(exeToGenerate); stubExe = GenerateTempPathname(tmpDir, "exe"); // get the Ionic.Zip assembly Assembly a1 = typeof(ZipFile).Assembly; using (var csharp = new Microsoft.CSharp.CSharpCodeProvider (new Dictionary() { { "CompilerVersion", "v2.0" } })) { // The following is a perfect opportunity for a linq query, but // I cannot use it. DotNetZip needs to run on .NET 2.0, // and using LINQ would break that. Here's what it would look // like: // // var settings = (from x in SettingsList // where x.Flavor == flavor // select x).First(); ExtractorSettings settings = null; foreach (var x in SettingsList) { if (x.Flavor == options.Flavor) { settings = x; break; } } // sanity check; should never happen if (settings == null) throw new BadStateException(String.Format("While saving a Self-Extracting Zip, Cannot find that flavor ({0})?", options.Flavor)); // This is the list of referenced assemblies. Ionic.Zip is // needed here. Also if it is the winforms (gui) extractor, we // need other referenced assemblies, like // System.Windows.Forms.dll, etc. var cp = new System.CodeDom.Compiler.CompilerParameters(); cp.ReferencedAssemblies.Add(a1.Location); if (settings.ReferencedAssemblies != null) foreach (string ra in settings.ReferencedAssemblies) cp.ReferencedAssemblies.Add(ra); cp.GenerateInMemory = false; cp.GenerateExecutable = true; cp.IncludeDebugInformation = false; cp.CompilerOptions = ""; Assembly a2 = Assembly.GetExecutingAssembly(); // Use this to concatenate all the source code resources into a // single module. var sb = new System.Text.StringBuilder(); // In case there are compiler errors later, we allocate a source // file name now. If errors are detected, we'll spool the source // code as well as the errors (in comments) into that filename, // and throw an exception with the filename. Makes it easier to // diagnose. This should be rare; most errors happen only // during devlpmt of DotNetZip itself, but there are rare // occasions when they occur in other cases. string sourceFile = GenerateTempPathname(tmpDir, "cs"); // // debugging: enumerate the resources in this assembly // Console.WriteLine("Resources in this assembly:"); // foreach (string rsrc in a2.GetManifestResourceNames()) // { // Console.WriteLine(rsrc); // } // Console.WriteLine(); // all the source code is embedded in the DLL as a zip file. using (ZipFile zip = ZipFile.Read(a2.GetManifestResourceStream("Ionic.Zip.Resources.ZippedResources.zip"))) { // // debugging: enumerate the files in the embedded zip // Console.WriteLine("Entries in the embbedded zip:"); // foreach (ZipEntry entry in zip) // { // Console.WriteLine(entry.FileName); // } // Console.WriteLine(); unpackedResourceDir = GenerateTempPathname(tmpDir, "tmp"); if (String.IsNullOrEmpty(options.IconFile)) { // Use the ico file that is embedded into the Ionic.Zip // DLL itself. To do this we must unpack the icon to // the filesystem, in order to specify it on the cmdline // of csc.exe. This method will remove the unpacked // file later. System.IO.Directory.CreateDirectory(unpackedResourceDir); ZipEntry e = zip["zippedFile.ico"]; // Must not extract a readonly file - it will be impossible to // delete later. if ((e.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) e.Attributes ^= FileAttributes.ReadOnly; e.Extract(unpackedResourceDir); nameOfIconFile = Path.Combine(unpackedResourceDir, "zippedFile.ico"); cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", nameOfIconFile); } else cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", options.IconFile); cp.OutputAssembly = stubExe; if (options.Flavor == SelfExtractorFlavor.WinFormsApplication) cp.CompilerOptions += " /target:winexe"; if (!String.IsNullOrEmpty(options.AdditionalCompilerSwitches)) cp.CompilerOptions += " " + options.AdditionalCompilerSwitches; if (String.IsNullOrEmpty(cp.CompilerOptions)) cp.CompilerOptions = null; if ((settings.CopyThroughResources != null) && (settings.CopyThroughResources.Count != 0)) { if (!Directory.Exists(unpackedResourceDir)) System.IO.Directory.CreateDirectory(unpackedResourceDir); foreach (string re in settings.CopyThroughResources) { string filename = Path.Combine(unpackedResourceDir, re); ExtractResourceToFile(a2, re, filename); // add the file into the target assembly as an embedded resource cp.EmbeddedResources.Add(filename); } } // add the Ionic.Utils.Zip DLL as an embedded resource cp.EmbeddedResources.Add(a1.Location); // file header sb.Append("// " + Path.GetFileName(sourceFile) + "\n") .Append("// --------------------------------------------\n//\n") .Append("// This SFX source file was generated by DotNetZip ") .Append(ZipFile.LibraryVersion.ToString()) .Append("\n// at ") .Append(System.DateTime.Now.ToString("yyyy MMMM dd HH:mm:ss")) .Append("\n//\n// --------------------------------------------\n\n\n"); // assembly attributes if (!String.IsNullOrEmpty(options.Description)) sb.Append("[assembly: System.Reflection.AssemblyTitle(\"" + options.Description.Replace("\"", "") + "\")]\n"); else sb.Append("[assembly: System.Reflection.AssemblyTitle(\"DotNetZip SFX Archive\")]\n"); if (!String.IsNullOrEmpty(options.ProductVersion)) sb.Append("[assembly: System.Reflection.AssemblyInformationalVersion(\"" + options.ProductVersion.Replace("\"", "") + "\")]\n"); // workitem string copyright = (String.IsNullOrEmpty(options.Copyright)) ? "Extractor: Copyright � Dino Chiesa 2008-2011" : options.Copyright.Replace("\"", ""); if (!String.IsNullOrEmpty(options.ProductName)) sb.Append("[assembly: System.Reflection.AssemblyProduct(\"") .Append(options.ProductName.Replace("\"", "")) .Append("\")]\n"); else sb.Append("[assembly: System.Reflection.AssemblyProduct(\"DotNetZip\")]\n"); sb.Append("[assembly: System.Reflection.AssemblyCopyright(\"" + copyright + "\")]\n") .Append(String.Format("[assembly: System.Reflection.AssemblyVersion(\"{0}\")]\n", ZipFile.LibraryVersion.ToString())); if (options.FileVersion != null) sb.Append(String.Format("[assembly: System.Reflection.AssemblyFileVersion(\"{0}\")]\n", options.FileVersion.ToString())); sb.Append("\n\n\n"); // Set the default extract location if it is available string extractLoc = options.DefaultExtractDirectory; if (extractLoc != null) { // remove double-quotes and replace slash with double-slash. // This, because the value is going to be embedded into a // cs file as a quoted string, and it needs to be escaped. extractLoc = extractLoc.Replace("\"", "").Replace("\\", "\\\\"); } string postExCmdLine = options.PostExtractCommandLine; if (postExCmdLine != null) { postExCmdLine = postExCmdLine.Replace("\\", "\\\\"); postExCmdLine = postExCmdLine.Replace("\"", "\\\""); } foreach (string rc in settings.ResourcesToCompile) { using (Stream s = zip[rc].OpenReader()) { if (s == null) throw new ZipException(String.Format("missing resource '{0}'", rc)); using (StreamReader sr = new StreamReader(s)) { while (sr.Peek() >= 0) { string line = sr.ReadLine(); if (extractLoc != null) line = line.Replace("@@EXTRACTLOCATION", extractLoc); line = line.Replace("@@REMOVE_AFTER_EXECUTE", options.RemoveUnpackedFilesAfterExecute.ToString()); line = line.Replace("@@QUIET", options.Quiet.ToString()); if (!String.IsNullOrEmpty(options.SfxExeWindowTitle)) line = line.Replace("@@SFX_EXE_WINDOW_TITLE", options.SfxExeWindowTitle); line = line.Replace("@@EXTRACT_EXISTING_FILE", ((int)options.ExtractExistingFile).ToString()); if (postExCmdLine != null) line = line.Replace("@@POST_UNPACK_CMD_LINE", postExCmdLine); sb.Append(line).Append("\n"); } } sb.Append("\n\n"); } } } string LiteralSource = sb.ToString(); #if DEBUGSFX // for debugging only string sourceModule = GenerateTempPathname(tmpDir, "cs"); using (StreamWriter sw = File.CreateText(sourceModule)) { sw.Write(LiteralSource); } Console.WriteLine("source: {0}", sourceModule); #endif var cr = csharp.CompileAssemblyFromSource(cp, LiteralSource); if (cr == null) throw new SfxGenerationException("Cannot compile the extraction logic!"); if (Verbose) foreach (string output in cr.Output) StatusMessageTextWriter.WriteLine(output); if (cr.Errors.Count != 0) { using (TextWriter tw = new StreamWriter(sourceFile)) { // first, the source we compiled tw.Write(LiteralSource); // now, append the compile errors tw.Write("\n\n\n// ------------------------------------------------------------------\n"); tw.Write("// Errors during compilation: \n//\n"); string p = Path.GetFileName(sourceFile); foreach (System.CodeDom.Compiler.CompilerError error in cr.Errors) { tw.Write(String.Format("// {0}({1},{2}): {3} {4}: {5}\n//\n", p, // 0 error.Line, // 1 error.Column, // 2 error.IsWarning ? "Warning" : "error", // 3 error.ErrorNumber, // 4 error.ErrorText)); // 5 } } throw new SfxGenerationException(String.Format("Errors compiling the extraction logic! {0}", sourceFile)); } OnSaveEvent(ZipProgressEventType.Saving_AfterCompileSelfExtractor); // Now, copy the resulting EXE image to the _writestream. // Because this stub exe is being saved first, the effect will be to // concatenate the exe and the zip data together. using (System.IO.Stream input = System.IO.File.OpenRead(stubExe)) { byte[] buffer = new byte[4000]; int n = 1; while (n != 0) { n = input.Read(buffer, 0, buffer.Length); if (n != 0) WriteStream.Write(buffer, 0, n); } } } OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive); } finally { try { if (Directory.Exists(unpackedResourceDir)) { try { Directory.Delete(unpackedResourceDir, true); } catch (System.IO.IOException exc1) { StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1); } } if (File.Exists(stubExe)) { try { File.Delete(stubExe); } catch (System.IO.IOException exc1) { StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1); } } } catch (System.IO.IOException) { } } return; } internal static string GenerateTempPathname(string dir, string extension) { string candidate = null; String AppName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; do { // workitem 13475 string uuid = System.Guid.NewGuid().ToString(); string Name = String.Format("{0}-{1}-{2}.{3}", AppName, System.DateTime.Now.ToString("yyyyMMMdd-HHmmss"), uuid, extension); candidate = System.IO.Path.Combine(dir, Name); } while (System.IO.File.Exists(candidate) || System.IO.Directory.Exists(candidate)); // The candidate path does not exist as a file or directory. // It can now be created, as a file or directory. return candidate; } } #endif }