| // 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 |
| /// <summary> |
| /// An enum that provides the different self-extractor flavors |
| /// </summary> |
| internal enum SelfExtractorFlavor |
| { |
| /// <summary> |
| /// A self-extracting zip archive that runs from the console or |
| /// command line. |
| /// </summary> |
| ConsoleApplication = 0, |
| |
| /// <summary> |
| /// A self-extracting zip archive that presents a graphical user |
| /// interface when it is executed. |
| /// </summary> |
| WinFormsApplication, |
| } |
| |
| /// <summary> |
| /// The options for generating a self-extracting archive. |
| /// </summary> |
| internal class SelfExtractorSaveOptions |
| { |
| /// <summary> |
| /// The type of SFX to create. |
| /// </summary> |
| public SelfExtractorFlavor Flavor |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The command to run after extraction. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// This is optional. Leave it empty (<c>null</c> in C# or <c>Nothing</c> in |
| /// VB) to run no command after extraction. |
| /// </para> |
| /// |
| /// <para> |
| /// 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: <c>program.exe arg1 |
| /// arg2</c>. 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. |
| /// </para> |
| /// |
| /// <para> |
| /// 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>"c:\project files\program name.exe" arg1 arg2</c>. |
| /// </para> |
| /// |
| /// <para> |
| /// If the flavor of the SFX is <c>SelfExtractorFlavor.ConsoleApplication</c>, |
| /// 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 <c>true</c>, you may not |
| /// see error messages, if a problem occurs. |
| /// </para> |
| /// |
| /// <para> |
| /// If the flavor of the SFX is |
| /// <c>SelfExtractorFlavor.WinFormsApplication</c>, 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. |
| /// </para> |
| /// |
| /// <para> |
| /// You can specify environment variables within this string, with a format like |
| /// <c>%NAME%</c>. The value of these variables will be expanded at the time |
| /// the SFX is run. Example: <c>%WINDIR%\system32\xcopy.exe</c> may expand at |
| /// runtime to <c>c:\Windows\System32\xcopy.exe</c>. |
| /// </para> |
| /// |
| /// <para> |
| /// By combining this with the <c>RemoveUnpackedFilesAfterExecute</c> |
| /// 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 <c>Flavor</c> to |
| /// <c>SelfExtractorFlavor.ConsoleApplication</c>, and set <c>Quiet</c> |
| /// 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 <c>DefaultExtractDirectory</c>. |
| /// </para> |
| /// |
| /// <para> |
| /// If you set <c>Flavor</c> to |
| /// <c>SelfExtractorFlavor.WinFormsApplication</c>, and set <c>Quiet</c> 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. |
| /// </para> |
| /// |
| /// </remarks> |
| public String PostExtractCommandLine |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The default extract directory the user will see when |
| /// running the self-extracting archive. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// Passing null (or Nothing in VB) here will cause the Self Extractor to use |
| /// the the user's personal directory (<see |
| /// cref="Environment.SpecialFolder.Personal"/>) for the default extract |
| /// location. |
| /// </para> |
| /// |
| /// <para> |
| /// This is only a default location. The actual extract location will be |
| /// settable on the command line when the SFX is executed. |
| /// </para> |
| /// |
| /// <para> |
| /// You can specify environment variables within this string, |
| /// with <c>%NAME%</c>. The value of these variables will be |
| /// expanded at the time the SFX is run. Example: |
| /// <c>%USERPROFILE%\Documents\unpack</c> may expand at runtime to |
| /// <c>c:\users\melvin\Documents\unpack</c>. |
| /// </para> |
| /// </remarks> |
| public String DefaultExtractDirectory |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The name of an .ico file in the filesystem to use for the application icon |
| /// for the generated SFX. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// 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 <c>/win32icon</c> |
| /// option to the csc.exe compiler when constructing the SFX file. |
| /// </para> |
| /// </remarks> |
| /// |
| public string IconFile |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// Whether the ConsoleApplication SFX will be quiet during extraction. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// This option affects the way the generated SFX runs. By default it is |
| /// false. When you set it to true,... |
| /// </para> |
| /// |
| /// <list type="table"> |
| /// <listheader> |
| /// <term>Flavor</term> |
| /// <description>Behavior</description> |
| /// </listheader> |
| /// |
| /// <item> |
| /// <term><c>ConsoleApplication</c></term> |
| /// <description><para>no messages will be emitted during successful |
| /// operation.</para> <para> 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. </para> |
| /// </description> |
| /// </item> |
| /// |
| /// <item> |
| /// <term><c>WinFormsApplication</c></term> |
| /// <description>the SFX extracts automatically when the application |
| /// is launched, with no additional user input. |
| /// </description> |
| /// </item> |
| /// |
| /// </list> |
| /// |
| /// <para> |
| /// When you set it to false,... |
| /// </para> |
| /// |
| /// <list type="table"> |
| /// <listheader> |
| /// <term>Flavor</term> |
| /// <description>Behavior</description> |
| /// </listheader> |
| /// |
| /// <item> |
| /// <term><c>ConsoleApplication</c></term> |
| /// <description><para>the extractor will emit a |
| /// message to the console for each entry extracted.</para> |
| /// <para> |
| /// 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. |
| /// </para> |
| /// </description> |
| /// </item> |
| /// |
| /// <item> |
| /// <term><c>WinFormsApplication</c></term> |
| /// <description>the SFX presents a forms UI and allows the user to select |
| /// options before extracting. |
| /// </description> |
| /// </item> |
| /// |
| /// </list> |
| /// |
| /// </remarks> |
| public bool Quiet |
| { |
| get; |
| set; |
| } |
| |
| |
| /// <summary> |
| /// Specify what the self-extractor will do when extracting an entry |
| /// would overwrite an existing file. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// The default behavvior is to Throw. |
| /// </para> |
| /// </remarks> |
| public Ionic.Zip.ExtractExistingFileAction ExtractExistingFile |
| { |
| get; |
| set; |
| } |
| |
| |
| /// <summary> |
| /// Whether to remove the files that have been unpacked, after executing the |
| /// PostExtractCommandLine. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// If true, and if there is a <see |
| /// cref="SelfExtractorSaveOptions.PostExtractCommandLine"> |
| /// PostExtractCommandLine</see>, 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. |
| /// </para> |
| /// |
| /// <para> |
| /// Setting this flag, and setting <c>Flavor</c> to |
| /// <c>SelfExtractorFlavor.ConsoleApplication</c>, and setting <c>Quiet</c> 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 <c>DefaultExtractDirectory</c>. |
| /// </para> |
| /// |
| /// </remarks> |
| public bool RemoveUnpackedFilesAfterExecute |
| { |
| get; |
| set; |
| } |
| |
| |
| /// <summary> |
| /// The file version number to embed into the generated EXE. It will show up, for |
| /// example, during a mouseover in Windows Explorer. |
| /// </summary> |
| /// |
| public Version FileVersion |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The product version to embed into the generated EXE. It will show up, for |
| /// example, during a mouseover in Windows Explorer. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// 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. |
| /// </remarks> |
| /// |
| public String ProductVersion |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The copyright notice, if any, to embed into the generated EXE. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// 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". |
| /// </remarks> |
| /// |
| public String Copyright |
| { |
| get; |
| set; |
| } |
| |
| |
| /// <summary> |
| /// The description to embed into the generated EXE. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// 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. |
| /// </remarks> |
| /// |
| public String Description |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The product name to embed into the generated EXE. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// Use any arbitrary string. This text will be displayed |
| /// while viewing properties of the EXE file in |
| /// Windows Explorer. |
| /// </remarks> |
| /// |
| public String ProductName |
| { |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// The title to display in the Window of a GUI SFX, while it extracts. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// 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. |
| /// </para> |
| /// |
| /// <para> |
| /// This property has an effect only when producing a Self-extractor |
| /// of flavor <c>SelfExtractorFlavor.WinFormsApplication</c>. |
| /// </para> |
| /// </remarks> |
| /// |
| public String SfxExeWindowTitle |
| { |
| // workitem 12608 |
| get; |
| set; |
| } |
| |
| /// <summary> |
| /// Additional options for the csc.exe compiler, when producing the SFX |
| /// EXE. |
| /// </summary> |
| /// <exclude/> |
| public string AdditionalCompilerSwitches |
| { |
| get; set; |
| } |
| } |
| |
| |
| |
| |
| partial class ZipFile |
| { |
| class ExtractorSettings |
| { |
| public SelfExtractorFlavor Flavor; |
| public List<string> ReferencedAssemblies; |
| public List<string> CopyThroughResources; |
| public List<string> ResourcesToCompile; |
| } |
| |
| |
| private static ExtractorSettings[] SettingsList = { |
| new ExtractorSettings() { |
| Flavor = SelfExtractorFlavor.WinFormsApplication, |
| ReferencedAssemblies= new List<string>{ |
| "System.dll", "System.Windows.Forms.dll", "System.Drawing.dll"}, |
| CopyThroughResources = new List<string>{ |
| "Ionic.Zip.WinFormsSelfExtractorStub.resources", |
| "Ionic.Zip.Forms.PasswordDialog.resources", |
| "Ionic.Zip.Forms.ZipContentsDialog.resources"}, |
| ResourcesToCompile = new List<string>{ |
| "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<string> { "System.dll", }, |
| CopyThroughResources = null, |
| ResourcesToCompile = new List<string>{"CommandLineSelfExtractorStub.cs"} |
| } |
| }; |
| |
| |
| |
| //string _defaultExtractLocation; |
| //string _postExtractCmdLine; |
| // string _SetDefaultLocationCode = |
| // "namespace OfficeOpenXml.Packaging.Ionic.Zip { internal partial class WinFormsSelfExtractorStub { partial void _SetDefaultExtractLocation() {" + |
| // " txtExtractDirectory.Text = \"@@VALUE\"; } }}"; |
| |
| |
| |
| /// <summary> |
| /// Saves the ZipFile instance to a self-extracting zip archive. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// |
| /// <para> |
| /// 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. |
| /// </para> |
| /// |
| /// <para> |
| /// There are two "flavors" of self-extracting archive. The |
| /// <c>WinFormsApplication</c> 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 <c>ConsoleApplication</c>. 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. |
| /// </para> |
| /// |
| /// <para> |
| /// 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 <see cref="ZipFile.TempFileFolder"/>, which defaults to <see |
| /// cref="System.IO.Path.GetTempPath"/>. These temporary files are |
| /// removed upon successful completion of this method. |
| /// </para> |
| /// |
| /// <para> |
| /// When a user runs the WinForms SFX, the user's personal directory (<see |
| /// cref="Environment.SpecialFolder.Personal">Environment.SpecialFolder.Personal</see>) |
| /// will be used as the default extract location. If you want to set the |
| /// default extract location, you should use the other overload of |
| /// <c>SaveSelfExtractor()</c>/ 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. |
| /// </para> |
| /// |
| /// <para> |
| /// NB: This method is not available in the version of DotNetZip build for |
| /// the .NET Compact Framework, nor in the "Reduced" DotNetZip library. |
| /// </para> |
| /// |
| /// </remarks> |
| /// |
| /// <example> |
| /// <code> |
| /// 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); |
| /// } |
| /// </code> |
| /// <code lang="VB"> |
| /// 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 |
| /// </code> |
| /// </example> |
| /// |
| /// <param name="exeToGenerate"> |
| /// a pathname, possibly fully qualified, to be created. Typically it |
| /// will end in an .exe extension.</param> |
| /// <param name="flavor"> |
| /// Indicates whether a Winforms or Console self-extractor is |
| /// desired. </param> |
| internal void SaveSelfExtractor(string exeToGenerate, SelfExtractorFlavor flavor) |
| { |
| SelfExtractorSaveOptions options = new SelfExtractorSaveOptions(); |
| options.Flavor = flavor; |
| SaveSelfExtractor(exeToGenerate, options); |
| } |
| |
| |
| |
| /// <summary> |
| /// Saves the ZipFile instance to a self-extracting zip archive, using |
| /// the specified save options. |
| /// </summary> |
| /// |
| /// <remarks> |
| /// <para> |
| /// 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 <see cref="SaveSelfExtractor(string , SelfExtractorFlavor)"/> for more |
| /// details. |
| /// </para> |
| /// |
| /// <para> |
| /// 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. |
| /// </para> |
| /// |
| /// </remarks> |
| /// |
| /// <example> |
| /// 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. |
| /// <code> |
| /// 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); |
| /// } |
| /// </code> |
| /// <code lang="VB"> |
| /// 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 |
| /// </code> |
| /// </example> |
| /// |
| /// <param name="exeToGenerate">The name of the EXE to generate.</param> |
| /// <param name="options">provides the options for creating the |
| /// Self-extracting archive.</param> |
| 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<string,string>() { { "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 |
| } |