123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101 |
- // 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 Ionic.Zip
- {
- #if !NO_SFX
- /// <summary>
- /// An enum that provides the different self-extractor flavors
- /// </summary>
- public 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>
- public 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 Ionic.Zip { public 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>
- public 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>
- public 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
- }
|