// 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-03 16:42:15>
|
//
|
// ------------------------------------------------------------------
|
//
|
// 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 HH.WMS.Utils.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 HH.WMS.Utils.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>{
|
"HH.WMS.Utils.Ionic.Zip.WinFormsSelfExtractorStub.resources",
|
"HH.WMS.Utils.Ionic.Zip.Forms.PasswordDialog.resources",
|
"HH.WMS.Utils.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 HH.WMS.Utils.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 HH.WMS.Utils.Ionic.Zip assembly
|
Assembly a1 = typeof(ZipFile).Assembly;
|
|
using (var csharp = new Microsoft.CSharp.CSharpCodeProvider())
|
{
|
// The following is a perfect opportunity for a linq query, but
|
// I cannot use it. The generated SFX 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. HH.WMS.Utils.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("HH.WMS.Utils.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 HH.WMS.Utils.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 HH.WMS.Utils.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
|
}
|