// CommandLineSelfExtractorStub.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2008, 2009 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: <2009-October-27 06:54:42>
//
// ------------------------------------------------------------------
//
// This is a the source module that implements the stub of a
// command-line self-extracting Zip archive - the code included in all
// command-line SFX files. This code is included as a resource into the
// DotNetZip DLL, and then is compiled at runtime when a SFX is saved.
//
// ------------------------------------------------------------------
namespace Ionic.Zip{
// include the using statements inside the namespace decl, because
// source code will be concatenated together before compilation.
using System;
using System.Reflection;
using System.Resources;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using Ionic.Zip;
public class CommandLineSelfExtractor
{
const string DllResourceName = "Ionic.Zip.dll";
string TargetDirectory = "@@EXTRACTLOCATION";
string PostUnpackCmdLine = "@@POST_UNPACK_CMD_LINE";
bool ReplacedEnvVarsForTargetDirectory;
bool ReplacedEnvVarsForCmdLine;
bool ListOnly;
bool Verbose;
bool ReallyVerbose;
bool RemoveFilesAfterExe;
bool SkipPostUnpackCommand;
string Password = null;
//Ionic.Zip.ExtractExistingFileAction Overwrite; // cannot do - because of tricks with assembly resolution
int Overwrite;
// Attention: it isn't possible, with the design of this class as it is now, to have a
// member variable of a type from the Ionic.Zip assembly. The class design registers
// an assembly resolver, but apparently NOT in time to allow the assembly to be used
// in private instance variables.
private bool PostUnpackCmdLineIsSet()
{
// What is going on here?
// The PostUnpackCmdLine is initialized to a particular value, then
// we test to see if it begins with the first two chars of that value,
// and ends with the last part of the value. Why?
// Here's the thing. In order to insure the code is right, this module has
// to compile as it is, as a standalone module. But then, inside
// DotNetZip, when generating an SFX, we do a text.Replace on the source
// code, potentially replacing @@POST_UNPACK_CMD_LINE with an actual value.
// The test here checks to see if it has been set.
bool result = !(PostUnpackCmdLine.StartsWith("@@") &&
PostUnpackCmdLine.EndsWith("POST_UNPACK_CMD_LINE"));
if (result && ReplacedEnvVarsForCmdLine == false)
{
PostUnpackCmdLine= ReplaceEnvVars(PostUnpackCmdLine);
ReplacedEnvVarsForCmdLine = true;
}
return result;
}
private bool TargetDirectoryIsSet()
{
bool result = !(TargetDirectory.StartsWith("@@") &&
TargetDirectory.EndsWith("EXTRACTLOCATION"));
if (result && ReplacedEnvVarsForTargetDirectory == false)
{
TargetDirectory= ReplaceEnvVars(TargetDirectory);
ReplacedEnvVarsForTargetDirectory = true;
}
return result;
}
private string ReplaceEnvVars(string s)
{
System.Collections.IDictionary envVars = Environment.GetEnvironmentVariables();
foreach (System.Collections.DictionaryEntry de in envVars)
{
string t = "%" + de.Key + "%";
s= s.Replace(t, de.Value as String);
}
return s;
}
private bool SetRemoveFilesFlag()
{
bool result = false;
Boolean.TryParse("@@REMOVE_AFTER_EXECUTE", out result);
RemoveFilesAfterExe = result;
return result;
}
private bool SetVerboseFlag()
{
bool result = false;
Boolean.TryParse("@@QUIET", out result);
Verbose = !result;
return Verbose;
}
private int SetOverwriteBehavior()
{
Int32 result = 0;
Int32.TryParse("@@EXTRACT_EXISTING_FILE", out result);
Overwrite = (int) result;
return result;
}
// ctor
private CommandLineSelfExtractor()
{
SetRemoveFilesFlag();
SetVerboseFlag();
SetOverwriteBehavior();
PostUnpackCmdLineIsSet();
TargetDirectoryIsSet();
}
// ctor
public CommandLineSelfExtractor(string[] args) : this()
{
string specifiedDirectory = null;
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "-d":
i++;
if (args.Length <= i)
{
Console.WriteLine("please supply a directory.\n");
GiveUsageAndExit();
}
if (specifiedDirectory != null)
{
Console.WriteLine("You already provided a directory.\n");
GiveUsageAndExit();
}
specifiedDirectory = args[i];
break;
case "-p":
i++;
if (args.Length <= i)
{
Console.WriteLine("please supply a password.\n");
GiveUsageAndExit();
}
if (Password != null)
{
Console.WriteLine("You already provided a password.\n");
GiveUsageAndExit();
}
Password = args[i];
break;
case "-o":
Overwrite = 1;
//WantOverwrite = ExtractExistingFileAction.OverwriteSilently;
break;
case "-n":
Overwrite= 2;
//WantOverwrite = ExtractExistingFileAction.DoNotOverwrite;
break;
case "-l":
ListOnly = true;
break;
case "-r+":
RemoveFilesAfterExe = true;
break;
case "-r-":
RemoveFilesAfterExe = false;
break;
case "-x":
SkipPostUnpackCommand = true;
break;
case "-?":
GiveUsageAndExit();
break;
case "-v-":
Verbose = false;
break;
case "-v+":
if (Verbose)
ReallyVerbose = true;
else
Verbose = true;
break;
default:
Console.WriteLine("unrecognized argument: '{0}'\n", args[i]);
GiveUsageAndExit();
break;
}
}
if (!ListOnly)
{
if (specifiedDirectory!=null)
TargetDirectory = specifiedDirectory;
else if (!TargetDirectoryIsSet())
TargetDirectory = "."; // cwd
}
if (ListOnly && ((Overwrite!= 0) || (specifiedDirectory != null)))
{
Console.WriteLine("Inconsistent options.\n");
GiveUsageAndExit();
}
}
// workitem 8988
private string[] SplitCommandLine(string cmdline)
{
// if the first char is NOT a double-quote, then just split the line
if (cmdline[0]!='"')
return cmdline.Split( new char[] {' '}, 2);
// the first char is double-quote. Need to verify that there's another one.
int ix = cmdline.IndexOf('"', 1);
if (ix == -1) return null; // no double-quote - FAIL
// if the double-quote is the last char, then just return an array of ONE string
if (ix+1 == cmdline.Length) return new string[] { cmdline.Substring(1,ix-1) };
if (cmdline[ix+1]!= ' ') return null; // no space following the double-quote - FAIL
// there's definitely another double quote, followed by a space
string[] args = new string[2];
args[0] = cmdline.Substring(1,ix-1);
while (cmdline[ix+1]==' ') ix++; // go to next non-space char
args[1] = cmdline.Substring(ix+1);
return args;
}
static CommandLineSelfExtractor()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Resolver);
}
static System.Reflection.Assembly Resolver(object sender, ResolveEventArgs args)
{
// super defensive
Assembly a1 = Assembly.GetExecutingAssembly();
if (a1==null)
throw new Exception("GetExecutingAssembly returns null.");
string[] tokens = args.Name.Split(',');
String[] names = a1.GetManifestResourceNames();
if (names==null)
throw new Exception("GetManifestResourceNames returns null.");
// workitem 7978
Stream s = null;
foreach (string n in names)
{
string root = n.Substring(0,n.Length-4);
string ext = n.Substring(n.Length-3);
if (root.Equals(tokens[0]) && ext.ToLower().Equals("dll"))
{
s= a1.GetManifestResourceStream(n);
if (s!=null) break;
}
}
if (s==null)
throw new Exception(String.Format("GetManifestResourceStream returns null. Available resources: [{0}]",
String.Join("|", names)));
byte[] block = new byte[s.Length];
if (block==null)
throw new Exception(String.Format("Cannot allocated buffer of length({0}).", s.Length));
s.Read(block, 0, block.Length);
Assembly a2 = Assembly.Load(block);
if (a2==null)
throw new Exception("Assembly.Load(block) returns null");
return a2;
}
public int Run()
{
//System.Diagnostics.Debugger.Break();
List<String> itemsExtracted= new List<String>();
global::Ionic.Zip.ExtractExistingFileAction WantOverwrite =
(Ionic.Zip.ExtractExistingFileAction) Overwrite;
// There way this works: the EXE is a ZIP file. So
// read from the location of the assembly, in other words the path to the exe.
Assembly a = Assembly.GetExecutingAssembly();
int rc = 0;
try
{
// workitem 7067
using (global::Ionic.Zip.ZipFile zip = global::Ionic.Zip.ZipFile.Read(a.Location))
{
if (!ListOnly)
{
if (Verbose)
{
System.Console.Write("Extracting to {0}", TargetDirectory);
System.Console.WriteLine(" (Existing file action: {0})", WantOverwrite.ToString());
}
}
bool header = true;
foreach (global::Ionic.Zip.ZipEntry entry in zip)
{
if (ListOnly || ReallyVerbose)
{
if (header)
{
System.Console.WriteLine("Extracting Zip file: {0}", zip.Name);
if ((zip.Comment != null) && (zip.Comment != ""))
System.Console.WriteLine("Comment: {0}", zip.Comment);
System.Console.WriteLine("\n{1,-22} {2,9} {3,5} {4,9} {5,3} {6,8} {0}",
"Filename", "Modified", "Size", "Ratio", "Packed", "pw?", "CRC");
System.Console.WriteLine(new System.String('-', 80));
header = false;
}
System.Console.WriteLine("{1,-22} {2,9} {3,5:F0}% {4,9} {5,3} {6:X8} {0}",
entry.FileName,
entry.LastModified.ToString("yyyy-MM-dd HH:mm:ss"),
entry.UncompressedSize,
entry.CompressionRatio,
entry.CompressedSize,
(entry.UsesEncryption) ? "Y" : "N",
entry.Crc);
}
if (!ListOnly)
{
if (Verbose && !ReallyVerbose)
System.Console.WriteLine(" {0}", entry.FileName);
if (entry.Encryption == global::Ionic.Zip.EncryptionAlgorithm.None)
{
try
{
entry.Extract(TargetDirectory, WantOverwrite);
itemsExtracted.Add(entry.FileName);
}
catch (Exception ex1)
{
Console.WriteLine(" Error -- {0}", ex1.Message);
rc++;
}
}
else
{
if (Password == null)
{
Console.WriteLine("Cannot extract entry {0} without a password.", entry.FileName);
rc++;
}
else
{
try
{
entry.ExtractWithPassword(TargetDirectory, WantOverwrite, Password);
itemsExtracted.Add(entry.FileName);
}
catch (Exception ex2)
{
Console.WriteLine(" Error -- {0}", ex2.Message);
rc++;
}
}
}
}
}
}
}
catch (Exception)
{
Console.WriteLine("The self-extracting zip file is corrupted.");
return 4;
}
if (rc != 0) return rc;
// potentially execute the embedded command
if (PostUnpackCmdLineIsSet() && !SkipPostUnpackCommand)
{
if (ListOnly)
{
Console.WriteLine("\nExecute on unpack: {0}", PostUnpackCmdLine);
}
else
{
try
{
string[] args = SplitCommandLine(PostUnpackCmdLine);
if (args!= null && args.Length > 0)
{
if (Verbose)
System.Console.WriteLine("Running command: {0}", PostUnpackCmdLine);
ProcessStartInfo startInfo = new ProcessStartInfo(args[0]);
startInfo.WorkingDirectory = TargetDirectory;
startInfo.CreateNoWindow = true;
if (args.Length > 1) startInfo.Arguments = args[1];
using (Process p = Process.Start(startInfo))
{
if (p!=null)
{
p.WaitForExit();
rc = p.ExitCode;
// workitem 8925
if (p.ExitCode == 0)
{
if (RemoveFilesAfterExe)
{
foreach (string s in itemsExtracted)
{
string fullPath = Path.Combine(TargetDirectory,s);
try
{
if (File.Exists(fullPath))
File.Delete(fullPath);
else if (Directory.Exists(fullPath))
Directory.Delete(fullPath, true);
}
catch
{
}
}
}
}
}
}
}
}
catch (Exception exc1)
{
System.Console.WriteLine("{0}", exc1);
rc = 5;
}
}
}
return rc;
}
private void GiveUsageAndExit()
{
Assembly a = Assembly.GetExecutingAssembly();
string s = Path.GetFileName(a.Location);
Console.WriteLine("DotNetZip Command-Line Self Extractor, see http://DotNetZip.codeplex.com/");
Console.WriteLine("usage:\n {0} [-p <password>] [-d <directory>]", s);
string more = " Extracts entries from the archive. If any files to be extracted already\n" +
" exist, the program will stop.\n\n Additional Options:\n" +
"{0}" +
"{1}" +
"{2}" +
"{3}";
string overwriteString =
String.Format(" -o - overwrite any existing files upon extraction{0}.\n" +
" -n - do not overwrite any existing files upon extraction{1}.\n",
(Overwrite == 1) ? " (default)" : "",
(Overwrite == 2) ? " (default)" : "");
string removeString = PostUnpackCmdLineIsSet()
? String.Format(" -r+ - remove files after the optional post-unpack exe completes{0}.\n" +
" -r- - don't remove files after the optional post-unpack exe completes{1}.\n",
RemoveFilesAfterExe ? " (default)" : "",
RemoveFilesAfterExe ? "" : " (default)")
: "";
string verbString = String.Format(" -v- - turn OFF verbose messages{0}.\n"+
" -v+ - turn ON verbose messages{1}.\n",
Verbose ? "" : " (default)",
Verbose ? " (default)" : "");
string cmdString = PostUnpackCmdLineIsSet()
? String.Format(" -x - don't run the post-unpack exe.\n [cmd is: {0}]\n",
PostUnpackCmdLine)
: "" ;
Console.WriteLine(more, overwriteString, removeString, cmdString, verbString);
if (TargetDirectoryIsSet())
Console.WriteLine(" default extract dir: [{0}]\n", TargetDirectory);
Console.WriteLine(" {0} -l", s);
Console.WriteLine(" Lists entries in the archive.");
FreeConsole();
Environment.Exit(1);
}
[STAThread]
public static int Main(string[] args)
{
int left = Console.CursorLeft;
int top = Console.CursorTop;
bool wantPause = (left==0 && top==0);
int rc = 0;
try
{
CommandLineSelfExtractor me = new CommandLineSelfExtractor(args);
// Hide my own console window if there is no parent console
// (which means, it was launched rom explorer).
if (!me.Verbose)
{
IntPtr myHandle = Process.GetCurrentProcess().MainWindowHandle;
ShowWindow(myHandle, SW_HIDE);
}
rc = me.Run();
// If there was an error, and this is a new console, and
// we're still displaying the console, then do a
// ReadLine. This gives the user a chance to read the
// window error messages before dismissing.
if (rc != 0 && wantPause && me.Verbose)
{
//Console.WriteLine("rc({0}) wantPause({1}) verbose({2})", rc, wantPause, me.Verbose);
Console.Write("<ENTER> to continue...");
Console.ReadLine();
}
}
catch (System.Exception exc1)
{
Console.WriteLine("Exception while extracting: {0}", exc1.ToString());
rc = 255;
}
FreeConsole();
return rc;
}
private static readonly int SW_HIDE= 0;
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool FreeConsole();
}
}
|