AirScout/DotNetZip/Zip/ZipFile.SaveSelfExtractor.cs

1102 wiersze
48 KiB
C#

// 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 <20> 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 <20> 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
}