Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/ZipFile.SaveSelfExtractor.cs @ 12074

Last change on this file since 12074 was 12074, checked in by sraggl, 9 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 48.7 KB
Line 
1// ZipFile.saveSelfExtractor.cs
2// ------------------------------------------------------------------
3//
4// Copyright (c) 2008-2011 Dino Chiesa.
5// All rights reserved.
6//
7// This code module is part of DotNetZip, a zipfile class library.
8//
9// ------------------------------------------------------------------
10//
11// This code is licensed under the Microsoft Public License.
12// See the file License.txt for the license details.
13// More info on: http://dotnetzip.codeplex.com
14//
15// ------------------------------------------------------------------
16//
17// last saved (in emacs):
18// Time-stamp: <2011-August-10 19:22:46>
19//
20// ------------------------------------------------------------------
21//
22// This is a the source module that implements the stuff for saving to a
23// self-extracting Zip archive.
24//
25// ZipFile is set up as a "partial class" - defined in multiple .cs source modules.
26// This is one of the source modules for the ZipFile class.
27//
28// Here's the design: The self-extracting zip file is just a regular managed EXE
29// file, with embedded resources.  The managed code logic instantiates a ZipFile, and
30// then extracts each entry.  The embedded resources include the zip archive content,
31// as well as the Zip library itself.  The latter is required so that self-extracting
32// can work on any machine, whether or not it has the DotNetZip library installed on
33// it.
34//
35// What we need to do is create the animal I just described, within a method on the
36// ZipFile class.  This source module provides that capability. The method is
37// SaveSelfExtractor().
38//
39// The way the method works: it uses the programmatic interface to the csc.exe
40// compiler, Microsoft.CSharp.CSharpCodeProvider, to compile "boilerplate"
41// extraction logic into a new assembly.  As part of that compile, we embed within
42// that assembly the zip archive itself, as well as the Zip library.
43//
44// Therefore we need to first save to a temporary zip file, then produce the exe.
45//
46// There are a few twists.
47//
48// The Visual Studio Project structure is a little weird.  There are code files
49// that ARE NOT compiled during a normal build of the VS Solution.  They are
50// marked as embedded resources.  These are the various "boilerplate" modules that
51// are used in the self-extractor. These modules are: WinFormsSelfExtractorStub.cs
52// WinFormsSelfExtractorStub.Designer.cs CommandLineSelfExtractorStub.cs
53// PasswordDialog.cs PasswordDialog.Designer.cs
54//
55// At design time, if you want to modify the way the GUI looks, you have to
56// mark those modules to have a "compile" build action.  Then tweak em, test,
57// etc.  Then again mark them as "Embedded resource".
58//
59// ------------------------------------------------------------------
60
61using System;
62using System.Reflection;
63using System.IO;
64using System.Collections.Generic;
65
66
67namespace OfficeOpenXml.Packaging.Ionic.Zip
68{
69#if !NO_SFX
70    /// <summary>
71    /// An enum that provides the different self-extractor flavors
72    /// </summary>
73    internal enum SelfExtractorFlavor
74    {
75        /// <summary>
76        /// A self-extracting zip archive that runs from the console or
77        /// command line.
78        /// </summary>
79        ConsoleApplication = 0,
80
81        /// <summary>
82        /// A self-extracting zip archive that presents a graphical user
83        /// interface when it is executed.
84        /// </summary>
85        WinFormsApplication,
86    }
87
88    /// <summary>
89    /// The options for generating a self-extracting archive.
90    /// </summary>
91    internal class SelfExtractorSaveOptions
92    {
93        /// <summary>
94        ///   The type of SFX to create.
95        /// </summary>
96        public SelfExtractorFlavor Flavor
97        {
98            get;
99            set;
100        }
101
102        /// <summary>
103        ///   The command to run after extraction.
104        /// </summary>
105        ///
106        /// <remarks>
107        /// <para>
108        ///   This is optional. Leave it empty (<c>null</c> in C# or <c>Nothing</c> in
109        ///   VB) to run no command after extraction.
110        /// </para>
111        ///
112        /// <para>
113        ///   If it is non-empty, the SFX will execute the command specified in this
114        ///   string on the user's machine, and using the extract directory as the
115        ///   working directory for the process, after unpacking the archive. The
116        ///   program to execute can include a path, if you like. If you want to execute
117        ///   a program that accepts arguments, specify the program name, followed by a
118        ///   space, and then the arguments for the program, each separated by a space,
119        ///   just as you would on a normal command line. Example: <c>program.exe arg1
120        ///   arg2</c>.  The string prior to the first space will be taken as the
121        ///   program name, and the string following the first space specifies the
122        ///   arguments to the program.
123        /// </para>
124        ///
125        /// <para>
126        ///   If you want to execute a program that has a space in the name or path of
127        ///   the file, surround the program name in double-quotes. The first character
128        ///   of the command line should be a double-quote character, and there must be
129        ///   a matching double-quote following the end of the program file name. Any
130        ///   optional arguments to the program follow that, separated by
131        ///   spaces. Example: <c>"c:\project files\program name.exe" arg1 arg2</c>.
132        /// </para>
133        ///
134        /// <para>
135        ///   If the flavor of the SFX is <c>SelfExtractorFlavor.ConsoleApplication</c>,
136        ///   then the SFX starts a new process, using this string as the post-extract
137        ///   command line.  The SFX waits for the process to exit.  The exit code of
138        ///   the post-extract command line is returned as the exit code of the
139        ///   command-line self-extractor exe. A non-zero exit code is typically used to
140        ///   indicated a failure by the program. In the case of an SFX, a non-zero exit
141        ///   code may indicate a failure during extraction, OR, it may indicate a
142        ///   failure of the run-after-extract program if specified, OR, it may indicate
143        ///   the run-after-extract program could not be fuond. There is no way to
144        ///   distinguish these conditions from the calling shell, aside from parsing
145        ///   the output of the SFX. If you have Quiet set to <c>true</c>, you may not
146        ///   see error messages, if a problem occurs.
147        /// </para>
148        ///
149        /// <para>
150        ///   If the flavor of the SFX is
151        ///   <c>SelfExtractorFlavor.WinFormsApplication</c>, then the SFX starts a new
152        ///   process, using this string as the post-extract command line, and using the
153        ///   extract directory as the working directory for the process. The SFX does
154        ///   not wait for the command to complete, and does not check the exit code of
155        ///   the program. If the run-after-extract program cannot be fuond, a message
156        ///   box is displayed indicating that fact.
157        /// </para>
158        ///
159        /// <para>
160        ///   You can specify environment variables within this string, with a format like
161        ///   <c>%NAME%</c>. The value of these variables will be expanded at the time
162        ///   the SFX is run. Example: <c>%WINDIR%\system32\xcopy.exe</c> may expand at
163        ///   runtime to <c>c:\Windows\System32\xcopy.exe</c>.
164        /// </para>
165        ///
166        /// <para>
167        ///   By combining this with the <c>RemoveUnpackedFilesAfterExecute</c>
168        ///   flag, you can create an SFX that extracts itself, runs a file that
169        ///   was extracted, then deletes all the files that were extracted. If
170        ///   you want it to run "invisibly" then set <c>Flavor</c> to
171        ///   <c>SelfExtractorFlavor.ConsoleApplication</c>, and set <c>Quiet</c>
172        ///   to true.  The user running such an EXE will see a console window
173        ///   appear, then disappear quickly.  You may also want to specify the
174        ///   default extract location, with <c>DefaultExtractDirectory</c>.
175        /// </para>
176        ///
177        /// <para>
178        ///   If you set <c>Flavor</c> to
179        ///   <c>SelfExtractorFlavor.WinFormsApplication</c>, and set <c>Quiet</c> to
180        ///   true, then a GUI with progressbars is displayed, but it is
181        ///   "non-interactive" - it accepts no input from the user.  Instead the SFX
182        ///   just automatically unpacks and exits.
183        /// </para>
184        ///
185        /// </remarks>
186        public String PostExtractCommandLine
187        {
188            get;
189            set;
190        }
191
192        /// <summary>
193        ///   The default extract directory the user will see when
194        ///   running the self-extracting archive.
195        /// </summary>
196        ///
197        /// <remarks>
198        /// <para>
199        ///   Passing null (or Nothing in VB) here will cause the Self Extractor to use
200        ///   the the user's personal directory (<see
201        ///   cref="Environment.SpecialFolder.Personal"/>) for the default extract
202        ///   location.
203        /// </para>
204        ///
205        /// <para>
206        ///   This is only a default location.  The actual extract location will be
207        ///   settable on the command line when the SFX is executed.
208        /// </para>
209        ///
210        /// <para>
211        ///   You can specify environment variables within this string,
212        ///   with <c>%NAME%</c>. The value of these variables will be
213        ///   expanded at the time the SFX is run. Example:
214        ///   <c>%USERPROFILE%\Documents\unpack</c> may expand at runtime to
215        ///   <c>c:\users\melvin\Documents\unpack</c>.
216        /// </para>
217        /// </remarks>
218        public String DefaultExtractDirectory
219        {
220            get;
221            set;
222        }
223
224        /// <summary>
225        ///   The name of an .ico file in the filesystem to use for the application icon
226        ///   for the generated SFX.
227        /// </summary>
228        ///
229        /// <remarks>
230        /// <para>
231        ///   Normally, DotNetZip will embed an "zipped folder" icon into the generated
232        ///   SFX.  If you prefer to use a different icon, you can specify it here. It
233        ///   should be a .ico file.  This file is passed as the <c>/win32icon</c>
234        ///   option to the csc.exe compiler when constructing the SFX file.
235        /// </para>
236        /// </remarks>
237        ///
238        public string IconFile
239        {
240            get;
241            set;
242        }
243
244        /// <summary>
245        ///   Whether the ConsoleApplication SFX will be quiet during extraction.
246        /// </summary>
247        ///
248        /// <remarks>
249        /// <para>
250        ///   This option affects the way the generated SFX runs. By default it is
251        ///   false.  When you set it to true,...
252        /// </para>
253        ///
254        /// <list type="table">
255        ///   <listheader>
256        ///     <term>Flavor</term>
257        ///     <description>Behavior</description>
258        ///   </listheader>
259        ///
260        /// <item>
261        ///   <term><c>ConsoleApplication</c></term>
262        ///   <description><para>no messages will be emitted during successful
263        ///     operation.</para> <para> Double-clicking the SFX in Windows
264        ///     Explorer or as an attachment in an email will cause a console
265        ///     window to appear briefly, before it disappears. If you run the
266        ///     ConsoleApplication SFX from the cmd.exe prompt, it runs as a
267        ///     normal console app; by default, because it is quiet, it displays
268        ///     no messages to the console.  If you pass the -v+ command line
269        ///     argument to the Console SFX when you run it, you will get verbose
270        ///     messages to the console. </para>
271        ///   </description>
272        /// </item>
273        ///
274        /// <item>
275        ///   <term><c>WinFormsApplication</c></term>
276        ///   <description>the SFX extracts automatically when the application
277        ///        is launched, with no additional user input.
278        ///   </description>
279        /// </item>
280        ///
281        /// </list>
282        ///
283        /// <para>
284        ///   When you set it to false,...
285        /// </para>
286        ///
287        /// <list type="table">
288        ///   <listheader>
289        ///     <term>Flavor</term>
290        ///     <description>Behavior</description>
291        ///   </listheader>
292        ///
293        /// <item>
294        ///   <term><c>ConsoleApplication</c></term>
295        ///   <description><para>the extractor will emit a
296        ///     message to the console for each entry extracted.</para>
297        ///     <para>
298        ///       When double-clicking to launch the SFX, the console window will
299        ///       remain, and the SFX will emit a message for each file as it
300        ///       extracts. The messages fly by quickly, they won't be easily
301        ///       readable, unless the extracted files are fairly large.
302        ///     </para>
303        ///   </description>
304        /// </item>
305        ///
306        /// <item>
307        ///   <term><c>WinFormsApplication</c></term>
308        ///   <description>the SFX presents a forms UI and allows the user to select
309        ///     options before extracting.
310        ///   </description>
311        /// </item>
312        ///
313        /// </list>
314        ///
315        /// </remarks>
316        public bool Quiet
317        {
318            get;
319            set;
320        }
321
322
323        /// <summary>
324        ///   Specify what the self-extractor will do when extracting an entry
325        ///   would overwrite an existing file.
326        /// </summary>
327        /// <remarks>
328        /// <para>
329        ///   The default behavvior is to Throw.
330        /// </para>
331        /// </remarks>
332        public Ionic.Zip.ExtractExistingFileAction ExtractExistingFile
333        {
334            get;
335            set;
336        }
337
338
339        /// <summary>
340        ///   Whether to remove the files that have been unpacked, after executing the
341        ///   PostExtractCommandLine.
342        /// </summary>
343        ///
344        /// <remarks>
345        /// <para>
346        ///   If true, and if there is a <see
347        ///   cref="SelfExtractorSaveOptions.PostExtractCommandLine">
348        ///   PostExtractCommandLine</see>, and if the command runs successfully,
349        ///   then the files that the SFX unpacked will be removed, afterwards.  If
350        ///   the command does not complete successfully (non-zero return code),
351        ///   that is interpreted as a failure, and the extracted files will not be
352        ///   removed.
353        /// </para>
354        ///
355        /// <para>
356        ///   Setting this flag, and setting <c>Flavor</c> to
357        ///   <c>SelfExtractorFlavor.ConsoleApplication</c>, and setting <c>Quiet</c> to
358        ///   true, results in an SFX that extracts itself, runs a file that was
359        ///   extracted, then deletes all the files that were extracted, with no
360        ///   intervention by the user.  You may also want to specify the default
361        ///   extract location, with <c>DefaultExtractDirectory</c>.
362        /// </para>
363        ///
364        /// </remarks>
365        public bool RemoveUnpackedFilesAfterExecute
366        {
367            get;
368            set;
369        }
370
371
372        /// <summary>
373        ///   The file version number to embed into the generated EXE. It will show up, for
374        ///   example, during a mouseover in Windows Explorer.
375        /// </summary>
376        ///
377        public Version FileVersion
378        {
379            get;
380            set;
381        }
382
383        /// <summary>
384        ///   The product version to embed into the generated EXE. It will show up, for
385        ///   example, during a mouseover in Windows Explorer.
386        /// </summary>
387        ///
388        /// <remarks>
389        ///   You can use any arbitrary string, but a human-readable version number is
390        ///   recommended. For example "v1.2 alpha" or "v4.2 RC2".  If you specify nothing,
391        ///   then there is no product version embedded into the EXE.
392        /// </remarks>
393        ///
394        public String ProductVersion
395        {
396            get;
397            set;
398        }
399
400        /// <summary>
401        ///   The copyright notice, if any, to embed into the generated EXE.
402        /// </summary>
403        ///
404        /// <remarks>
405        ///   It will show up, for example, while viewing properties of the file in
406        ///   Windows Explorer.  You can use any arbitrary string, but typically you
407        ///   want something like "Copyright ï¿œ Dino Chiesa 2011".
408        /// </remarks>
409        ///
410        public String Copyright
411        {
412            get;
413            set;
414        }
415
416
417        /// <summary>
418        ///   The description to embed into the generated EXE.
419        /// </summary>
420        ///
421        /// <remarks>
422        ///   Use any arbitrary string.  This text will be displayed during a
423        ///   mouseover in Windows Explorer.  If you specify nothing, then the string
424        ///   "DotNetZip SFX Archive" is embedded into the EXE as the description.
425        /// </remarks>
426        ///
427        public String Description
428        {
429            get;
430            set;
431        }
432
433        /// <summary>
434        ///   The product name to embed into the generated EXE.
435        /// </summary>
436        ///
437        /// <remarks>
438        ///   Use any arbitrary string. This text will be displayed
439        ///   while viewing properties of the EXE file in
440        ///   Windows Explorer.
441        /// </remarks>
442        ///
443        public String ProductName
444        {
445            get;
446            set;
447        }
448
449        /// <summary>
450        ///   The title to display in the Window of a GUI SFX, while it extracts.
451        /// </summary>
452        ///
453        /// <remarks>
454        ///   <para>
455        ///     By default the title show in the GUI window of a self-extractor
456        ///     is "DotNetZip Self-extractor (http://DotNetZip.codeplex.com/)".
457        ///     You can change that by setting this property before saving the SFX.
458        ///   </para>
459        ///
460        ///   <para>
461        ///     This property has an effect only when producing a Self-extractor
462        ///     of flavor <c>SelfExtractorFlavor.WinFormsApplication</c>.
463        ///   </para>
464        /// </remarks>
465        ///
466        public String SfxExeWindowTitle
467        {
468            // workitem 12608
469            get;
470            set;
471        }
472
473        /// <summary>
474        ///   Additional options for the csc.exe compiler, when producing the SFX
475        ///   EXE.
476        /// </summary>
477        /// <exclude/>
478        public string AdditionalCompilerSwitches
479        {
480            get; set;
481        }
482    }
483
484
485
486
487    partial class ZipFile
488    {
489        class ExtractorSettings
490        {
491            public SelfExtractorFlavor Flavor;
492            public List<string> ReferencedAssemblies;
493            public List<string> CopyThroughResources;
494            public List<string> ResourcesToCompile;
495        }
496
497
498        private static ExtractorSettings[] SettingsList = {
499            new ExtractorSettings() {
500                Flavor = SelfExtractorFlavor.WinFormsApplication,
501                ReferencedAssemblies= new List<string>{
502                    "System.dll", "System.Windows.Forms.dll", "System.Drawing.dll"},
503                CopyThroughResources = new List<string>{
504                    "Ionic.Zip.WinFormsSelfExtractorStub.resources",
505                    "Ionic.Zip.Forms.PasswordDialog.resources",
506                    "Ionic.Zip.Forms.ZipContentsDialog.resources"},
507                ResourcesToCompile = new List<string>{
508                    "WinFormsSelfExtractorStub.cs",
509                    "WinFormsSelfExtractorStub.Designer.cs", // .Designer.cs?
510                    "PasswordDialog.cs",
511                    "PasswordDialog.Designer.cs",             //.Designer.cs"
512                    "ZipContentsDialog.cs",
513                    "ZipContentsDialog.Designer.cs",             //.Designer.cs"
514                    "FolderBrowserDialogEx.cs",
515                }
516            },
517            new ExtractorSettings() {
518                Flavor = SelfExtractorFlavor.ConsoleApplication,
519                ReferencedAssemblies= new List<string> { "System.dll", },
520                CopyThroughResources = null,
521                ResourcesToCompile = new List<string>{"CommandLineSelfExtractorStub.cs"}
522            }
523        };
524
525
526
527        //string _defaultExtractLocation;
528        //string _postExtractCmdLine;
529        //         string _SetDefaultLocationCode =
530        //         "namespace OfficeOpenXml.Packaging.Ionic.Zip { internal partial class WinFormsSelfExtractorStub { partial void _SetDefaultExtractLocation() {" +
531        //         " txtExtractDirectory.Text = \"@@VALUE\"; } }}";
532
533
534
535        /// <summary>
536        /// Saves the ZipFile instance to a self-extracting zip archive.
537        /// </summary>
538        ///
539        /// <remarks>
540        ///
541        /// <para>
542        /// The generated exe image will execute on any machine that has the .NET
543        /// Framework 2.0 installed on it.  The generated exe image is also a
544        /// valid ZIP file, readable with DotNetZip or another Zip library or tool
545        /// such as WinZip.
546        /// </para>
547        ///
548        /// <para>
549        /// There are two "flavors" of self-extracting archive.  The
550        /// <c>WinFormsApplication</c> version will pop up a GUI and allow the
551        /// user to select a target directory into which to extract. There's also
552        /// a checkbox allowing the user to specify to overwrite existing files,
553        /// and another checkbox to allow the user to request that Explorer be
554        /// opened to see the extracted files after extraction.  The other flavor
555        /// is <c>ConsoleApplication</c>.  A self-extractor generated with that
556        /// flavor setting will run from the command line. It accepts command-line
557        /// options to set the overwrite behavior, and to specify the target
558        /// extraction directory.
559        /// </para>
560        ///
561        /// <para>
562        /// There are a few temporary files created during the saving to a
563        /// self-extracting zip.  These files are created in the directory pointed
564        /// to by <see cref="ZipFile.TempFileFolder"/>, which defaults to <see
565        /// cref="System.IO.Path.GetTempPath"/>.  These temporary files are
566        /// removed upon successful completion of this method.
567        /// </para>
568        ///
569        /// <para>
570        /// When a user runs the WinForms SFX, the user's personal directory (<see
571        /// cref="Environment.SpecialFolder.Personal">Environment.SpecialFolder.Personal</see>)
572        /// will be used as the default extract location.  If you want to set the
573        /// default extract location, you should use the other overload of
574        /// <c>SaveSelfExtractor()</c>/ The user who runs the SFX will have the
575        /// opportunity to change the extract directory before extracting. When
576        /// the user runs the Command-Line SFX, the user must explicitly specify
577        /// the directory to which to extract.  The .NET Framework 2.0 is required
578        /// on the computer when the self-extracting archive is run.
579        /// </para>
580        ///
581        /// <para>
582        /// NB: This method is not available in the version of DotNetZip build for
583        /// the .NET Compact Framework, nor in the "Reduced" DotNetZip library.
584        /// </para>
585        ///
586        /// </remarks>
587        ///
588        /// <example>
589        /// <code>
590        /// string DirectoryPath = "c:\\Documents\\Project7";
591        /// using (ZipFile zip = new ZipFile())
592        /// {
593        ///     zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath));
594        ///     zip.Comment = "This will be embedded into a self-extracting console-based exe";
595        ///     zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication);
596        /// }
597        /// </code>
598        /// <code lang="VB">
599        /// Dim DirectoryPath As String = "c:\Documents\Project7"
600        /// Using zip As New ZipFile()
601        ///     zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath))
602        ///     zip.Comment = "This will be embedded into a self-extracting console-based exe"
603        ///     zip.SaveSelfExtractor("archive.exe", SelfExtractorFlavor.ConsoleApplication)
604        /// End Using
605        /// </code>
606        /// </example>
607        ///
608        /// <param name="exeToGenerate">
609        ///   a pathname, possibly fully qualified, to be created. Typically it
610        ///   will end in an .exe extension.</param>
611        /// <param name="flavor">
612        ///   Indicates whether a Winforms or Console self-extractor is
613        ///   desired. </param>
614        internal void SaveSelfExtractor(string exeToGenerate, SelfExtractorFlavor flavor)
615        {
616            SelfExtractorSaveOptions options = new SelfExtractorSaveOptions();
617            options.Flavor = flavor;
618            SaveSelfExtractor(exeToGenerate, options);
619        }
620
621
622
623        /// <summary>
624        ///   Saves the ZipFile instance to a self-extracting zip archive, using
625        ///   the specified save options.
626        /// </summary>
627        ///
628        /// <remarks>
629        /// <para>
630        ///   This method saves a self extracting archive, using the specified save
631        ///   options. These options include the flavor of the SFX, the default extract
632        ///   directory, the icon file, and so on.  See the documentation
633        ///   for <see cref="SaveSelfExtractor(string , SelfExtractorFlavor)"/> for more
634        ///   details.
635        /// </para>
636        ///
637        /// <para>
638        ///   The user who runs the SFX will have the opportunity to change the extract
639        ///   directory before extracting. If at the time of extraction, the specified
640        ///   directory does not exist, the SFX will create the directory before
641        ///   extracting the files.
642        /// </para>
643        ///
644        /// </remarks>
645        ///
646        /// <example>
647        ///   This example saves a WinForms-based self-extracting archive EXE that
648        ///   will use c:\ExtractHere as the default extract location. The C# code
649        ///   shows syntax for .NET 3.0, which uses an object initializer for
650        ///   the SelfExtractorOptions object.
651        /// <code>
652        /// string DirectoryPath = "c:\\Documents\\Project7";
653        /// using (ZipFile zip = new ZipFile())
654        /// {
655        ///     zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath));
656        ///     zip.Comment = "This will be embedded into a self-extracting WinForms-based exe";
657        ///     var options = new SelfExtractorOptions
658        ///     {
659        ///       Flavor = SelfExtractorFlavor.WinFormsApplication,
660        ///       DefaultExtractDirectory = "%USERPROFILE%\\ExtractHere",
661        ///       PostExtractCommandLine = ExeToRunAfterExtract,
662        ///       SfxExeWindowTitle = "My Custom Window Title",
663        ///       RemoveUnpackedFilesAfterExecute = true
664        ///     };
665        ///     zip.SaveSelfExtractor("archive.exe", options);
666        /// }
667        /// </code>
668        /// <code lang="VB">
669        /// Dim DirectoryPath As String = "c:\Documents\Project7"
670        /// Using zip As New ZipFile()
671        ///     zip.AddDirectory(DirectoryPath, System.IO.Path.GetFileName(DirectoryPath))
672        ///     zip.Comment = "This will be embedded into a self-extracting console-based exe"
673        ///     Dim options As New SelfExtractorOptions()
674        ///     options.Flavor = SelfExtractorFlavor.WinFormsApplication
675        ///     options.DefaultExtractDirectory = "%USERPROFILE%\\ExtractHere"
676        ///     options.PostExtractCommandLine = ExeToRunAfterExtract
677        ///     options.SfxExeWindowTitle = "My Custom Window Title"
678        ///     options.RemoveUnpackedFilesAfterExecute = True
679        ///     zip.SaveSelfExtractor("archive.exe", options)
680        /// End Using
681        /// </code>
682        /// </example>
683        ///
684        /// <param name="exeToGenerate">The name of the EXE to generate.</param>
685        /// <param name="options">provides the options for creating the
686        /// Self-extracting archive.</param>
687        internal void SaveSelfExtractor(string exeToGenerate, SelfExtractorSaveOptions options)
688        {
689            // Save an SFX that is both an EXE and a ZIP.
690
691            // Check for the case where we are re-saving a zip archive
692            // that was originally instantiated with a stream.  In that case,
693            // the _name will be null. If so, we set _writestream to null,
694            // which insures that we'll cons up a new WriteStream (with a filesystem
695            // file backing it) in the Save() method.
696            if (_name == null)
697                _writestream = null;
698
699            _SavingSfx = true;
700            _name = exeToGenerate;
701            if (Directory.Exists(_name))
702                throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "exeToGenerate"));
703            _contentsChanged = true;
704            _fileAlreadyExists = File.Exists(_name);
705
706            _SaveSfxStub(exeToGenerate, options);
707
708            Save();
709            _SavingSfx = false;
710        }
711
712
713
714
715        private static void ExtractResourceToFile(Assembly a, string resourceName, string filename)
716        {
717            int n = 0;
718            byte[] bytes = new byte[1024];
719            using (Stream instream = a.GetManifestResourceStream(resourceName))
720            {
721                if (instream == null)
722                    throw new ZipException(String.Format("missing resource '{0}'", resourceName));
723
724                using (FileStream outstream = File.OpenWrite(filename))
725                {
726                    do
727                    {
728                        n = instream.Read(bytes, 0, bytes.Length);
729                        outstream.Write(bytes, 0, n);
730                    } while (n > 0);
731                }
732            }
733        }
734
735
736        private void _SaveSfxStub(string exeToGenerate, SelfExtractorSaveOptions options)
737        {
738            string nameOfIconFile = null;
739            string stubExe = null;
740            string unpackedResourceDir = null;
741            string tmpDir = null;
742            try
743            {
744                if (File.Exists(exeToGenerate))
745                {
746                    if (Verbose) StatusMessageTextWriter.WriteLine("The existing file ({0}) will be overwritten.", exeToGenerate);
747                }
748                if (!exeToGenerate.EndsWith(".exe"))
749                {
750                    if (Verbose) StatusMessageTextWriter.WriteLine("Warning: The generated self-extracting file will not have an .exe extension.");
751                }
752
753                // workitem 10553
754                tmpDir = TempFileFolder ?? Path.GetDirectoryName(exeToGenerate);
755                stubExe = GenerateTempPathname(tmpDir, "exe");
756
757                // get the Ionic.Zip assembly
758                Assembly a1 = typeof(ZipFile).Assembly;
759
760                using (var csharp = new Microsoft.CSharp.CSharpCodeProvider
761                       (new Dictionary<string,string>() { { "CompilerVersion", "v2.0" } })) {
762
763                    // The following is a perfect opportunity for a linq query, but
764                    // I cannot use it.  DotNetZip needs to run on .NET 2.0,
765                    // and using LINQ would break that. Here's what it would look
766                    // like:
767                    //
768                    //   var settings = (from x in SettingsList
769                    //                   where x.Flavor == flavor
770                    //                   select x).First();
771
772                    ExtractorSettings settings = null;
773                    foreach (var x in SettingsList)
774                    {
775                        if (x.Flavor == options.Flavor)
776                        {
777                            settings = x;
778                            break;
779                        }
780                    }
781
782                    // sanity check; should never happen
783                    if (settings == null)
784                        throw new BadStateException(String.Format("While saving a Self-Extracting Zip, Cannot find that flavor ({0})?", options.Flavor));
785
786                    // This is the list of referenced assemblies.  Ionic.Zip is
787                    // needed here.  Also if it is the winforms (gui) extractor, we
788                    // need other referenced assemblies, like
789                    // System.Windows.Forms.dll, etc.
790                    var cp = new System.CodeDom.Compiler.CompilerParameters();
791                    cp.ReferencedAssemblies.Add(a1.Location);
792                    if (settings.ReferencedAssemblies != null)
793                        foreach (string ra in settings.ReferencedAssemblies)
794                            cp.ReferencedAssemblies.Add(ra);
795
796                    cp.GenerateInMemory = false;
797                    cp.GenerateExecutable = true;
798                    cp.IncludeDebugInformation = false;
799                    cp.CompilerOptions = "";
800
801                    Assembly a2 = Assembly.GetExecutingAssembly();
802
803                    // Use this to concatenate all the source code resources into a
804                    // single module.
805                    var sb = new System.Text.StringBuilder();
806
807                    // In case there are compiler errors later, we allocate a source
808                    // file name now. If errors are detected, we'll spool the source
809                    // code as well as the errors (in comments) into that filename,
810                    // and throw an exception with the filename.  Makes it easier to
811                    // diagnose.  This should be rare; most errors happen only
812                    // during devlpmt of DotNetZip itself, but there are rare
813                    // occasions when they occur in other cases.
814                    string sourceFile = GenerateTempPathname(tmpDir, "cs");
815
816
817                    // // debugging: enumerate the resources in this assembly
818                    // Console.WriteLine("Resources in this assembly:");
819                    // foreach (string rsrc in a2.GetManifestResourceNames())
820                    //   {
821                    //     Console.WriteLine(rsrc);
822                    //   }
823                    // Console.WriteLine();
824
825
826                    // all the source code is embedded in the DLL as a zip file.
827                    using (ZipFile zip = ZipFile.Read(a2.GetManifestResourceStream("Ionic.Zip.Resources.ZippedResources.zip")))
828                    {
829                        // // debugging: enumerate the files in the embedded zip
830                        // Console.WriteLine("Entries in the embbedded zip:");
831                        // foreach (ZipEntry entry in zip)
832                        //   {
833                        //     Console.WriteLine(entry.FileName);
834                        //   }
835                        // Console.WriteLine();
836
837                        unpackedResourceDir = GenerateTempPathname(tmpDir, "tmp");
838
839                        if (String.IsNullOrEmpty(options.IconFile))
840                        {
841                            // Use the ico file that is embedded into the Ionic.Zip
842                            // DLL itself.  To do this we must unpack the icon to
843                            // the filesystem, in order to specify it on the cmdline
844                            // of csc.exe.  This method will remove the unpacked
845                            // file later.
846                            System.IO.Directory.CreateDirectory(unpackedResourceDir);
847                            ZipEntry e = zip["zippedFile.ico"];
848                            // Must not extract a readonly file - it will be impossible to
849                            // delete later.
850                            if ((e.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
851                                e.Attributes ^= FileAttributes.ReadOnly;
852                            e.Extract(unpackedResourceDir);
853                            nameOfIconFile = Path.Combine(unpackedResourceDir, "zippedFile.ico");
854                            cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", nameOfIconFile);
855                        }
856                        else
857                            cp.CompilerOptions += String.Format("/win32icon:\"{0}\"", options.IconFile);
858
859                        cp.OutputAssembly = stubExe;
860
861                        if (options.Flavor == SelfExtractorFlavor.WinFormsApplication)
862                            cp.CompilerOptions += " /target:winexe";
863
864                        if (!String.IsNullOrEmpty(options.AdditionalCompilerSwitches))
865                            cp.CompilerOptions += " " + options.AdditionalCompilerSwitches;
866
867                        if (String.IsNullOrEmpty(cp.CompilerOptions))
868                            cp.CompilerOptions = null;
869
870                        if ((settings.CopyThroughResources != null) && (settings.CopyThroughResources.Count != 0))
871                        {
872                            if (!Directory.Exists(unpackedResourceDir)) System.IO.Directory.CreateDirectory(unpackedResourceDir);
873                            foreach (string re in settings.CopyThroughResources)
874                            {
875                                string filename = Path.Combine(unpackedResourceDir, re);
876
877                                ExtractResourceToFile(a2, re, filename);
878                                // add the file into the target assembly as an embedded resource
879                                cp.EmbeddedResources.Add(filename);
880                            }
881                        }
882
883                        // add the Ionic.Utils.Zip DLL as an embedded resource
884                        cp.EmbeddedResources.Add(a1.Location);
885
886                        // file header
887                        sb.Append("// " + Path.GetFileName(sourceFile) + "\n")
888                            .Append("// --------------------------------------------\n//\n")
889                            .Append("// This SFX source file was generated by DotNetZip ")
890                            .Append(ZipFile.LibraryVersion.ToString())
891                            .Append("\n//         at ")
892                            .Append(System.DateTime.Now.ToString("yyyy MMMM dd  HH:mm:ss"))
893                            .Append("\n//\n// --------------------------------------------\n\n\n");
894
895                        // assembly attributes
896                        if (!String.IsNullOrEmpty(options.Description))
897                            sb.Append("[assembly: System.Reflection.AssemblyTitle(\""
898                                      + options.Description.Replace("\"", "")
899                                      + "\")]\n");
900                        else
901                            sb.Append("[assembly: System.Reflection.AssemblyTitle(\"DotNetZip SFX Archive\")]\n");
902
903                        if (!String.IsNullOrEmpty(options.ProductVersion))
904                            sb.Append("[assembly: System.Reflection.AssemblyInformationalVersion(\""
905                                      + options.ProductVersion.Replace("\"", "")
906                                      + "\")]\n");
907
908                        // workitem
909                        string copyright =
910                            (String.IsNullOrEmpty(options.Copyright))
911                            ? "Extractor: Copyright ï¿œ Dino Chiesa 2008-2011"
912                            : options.Copyright.Replace("\"", "");
913
914                        if (!String.IsNullOrEmpty(options.ProductName))
915                            sb.Append("[assembly: System.Reflection.AssemblyProduct(\"")
916                                .Append(options.ProductName.Replace("\"", ""))
917                                .Append("\")]\n");
918                        else
919                            sb.Append("[assembly: System.Reflection.AssemblyProduct(\"DotNetZip\")]\n");
920
921
922                        sb.Append("[assembly: System.Reflection.AssemblyCopyright(\"" + copyright + "\")]\n")
923                            .Append(String.Format("[assembly: System.Reflection.AssemblyVersion(\"{0}\")]\n", ZipFile.LibraryVersion.ToString()));
924                        if (options.FileVersion != null)
925                            sb.Append(String.Format("[assembly: System.Reflection.AssemblyFileVersion(\"{0}\")]\n",
926                                                    options.FileVersion.ToString()));
927
928                        sb.Append("\n\n\n");
929
930                        // Set the default extract location if it is available
931                        string extractLoc = options.DefaultExtractDirectory;
932                        if (extractLoc != null)
933                        {
934                            // remove double-quotes and replace slash with double-slash.
935                            // This, because the value is going to be embedded into a
936                            // cs file as a quoted string, and it needs to be escaped.
937                            extractLoc = extractLoc.Replace("\"", "").Replace("\\", "\\\\");
938                        }
939
940                        string postExCmdLine = options.PostExtractCommandLine;
941                        if (postExCmdLine != null)
942                        {
943                            postExCmdLine = postExCmdLine.Replace("\\", "\\\\");
944                            postExCmdLine = postExCmdLine.Replace("\"", "\\\"");
945                        }
946
947
948                        foreach (string rc in settings.ResourcesToCompile)
949                        {
950                            using (Stream s = zip[rc].OpenReader())
951                            {
952                                if (s == null)
953                                    throw new ZipException(String.Format("missing resource '{0}'", rc));
954                                using (StreamReader sr = new StreamReader(s))
955                                {
956                                    while (sr.Peek() >= 0)
957                                    {
958                                        string line = sr.ReadLine();
959                                        if (extractLoc != null)
960                                            line = line.Replace("@@EXTRACTLOCATION", extractLoc);
961
962                                        line = line.Replace("@@REMOVE_AFTER_EXECUTE", options.RemoveUnpackedFilesAfterExecute.ToString());
963                                        line = line.Replace("@@QUIET", options.Quiet.ToString());
964                                        if (!String.IsNullOrEmpty(options.SfxExeWindowTitle))
965
966                                            line = line.Replace("@@SFX_EXE_WINDOW_TITLE", options.SfxExeWindowTitle);
967
968                                        line = line.Replace("@@EXTRACT_EXISTING_FILE", ((int)options.ExtractExistingFile).ToString());
969
970                                        if (postExCmdLine != null)
971                                            line = line.Replace("@@POST_UNPACK_CMD_LINE", postExCmdLine);
972
973                                        sb.Append(line).Append("\n");
974                                    }
975                                }
976                                sb.Append("\n\n");
977                            }
978                        }
979                    }
980
981                    string LiteralSource = sb.ToString();
982
983#if DEBUGSFX
984                    // for debugging only
985                    string sourceModule = GenerateTempPathname(tmpDir, "cs");
986                    using (StreamWriter sw = File.CreateText(sourceModule))
987                    {
988                        sw.Write(LiteralSource);
989                    }
990                    Console.WriteLine("source: {0}", sourceModule);
991#endif
992
993                    var cr = csharp.CompileAssemblyFromSource(cp, LiteralSource);
994
995
996                    if (cr == null)
997                        throw new SfxGenerationException("Cannot compile the extraction logic!");
998
999                    if (Verbose)
1000                        foreach (string output in cr.Output)
1001                            StatusMessageTextWriter.WriteLine(output);
1002
1003                    if (cr.Errors.Count != 0)
1004                    {
1005                        using (TextWriter tw = new StreamWriter(sourceFile))
1006                        {
1007                            // first, the source we compiled
1008                            tw.Write(LiteralSource);
1009
1010                            // now, append the compile errors
1011                            tw.Write("\n\n\n// ------------------------------------------------------------------\n");
1012                            tw.Write("// Errors during compilation: \n//\n");
1013                            string p = Path.GetFileName(sourceFile);
1014
1015                            foreach (System.CodeDom.Compiler.CompilerError error in cr.Errors)
1016                            {
1017                                tw.Write(String.Format("//   {0}({1},{2}): {3} {4}: {5}\n//\n",
1018                                                       p,                                   // 0
1019                                                       error.Line,                          // 1
1020                                                       error.Column,                        // 2
1021                                                       error.IsWarning ? "Warning" : "error",   // 3
1022                                                       error.ErrorNumber,                   // 4
1023                                                       error.ErrorText));                  // 5
1024                            }
1025                        }
1026                        throw new SfxGenerationException(String.Format("Errors compiling the extraction logic!  {0}", sourceFile));
1027                    }
1028
1029                    OnSaveEvent(ZipProgressEventType.Saving_AfterCompileSelfExtractor);
1030
1031                    // Now, copy the resulting EXE image to the _writestream.
1032                    // Because this stub exe is being saved first, the effect will be to
1033                    // concatenate the exe and the zip data together.
1034                    using (System.IO.Stream input = System.IO.File.OpenRead(stubExe))
1035                    {
1036                        byte[] buffer = new byte[4000];
1037                        int n = 1;
1038                        while (n != 0)
1039                        {
1040                            n = input.Read(buffer, 0, buffer.Length);
1041                            if (n != 0)
1042                                WriteStream.Write(buffer, 0, n);
1043                        }
1044                    }
1045                }
1046
1047                OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
1048            }
1049            finally
1050            {
1051                try
1052                {
1053                    if (Directory.Exists(unpackedResourceDir))
1054                    {
1055                        try { Directory.Delete(unpackedResourceDir, true); }
1056                        catch (System.IO.IOException exc1)
1057                        {
1058                            StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1);
1059                        }
1060                    }
1061                    if (File.Exists(stubExe))
1062                    {
1063                        try { File.Delete(stubExe); }
1064                        catch (System.IO.IOException exc1)
1065                        {
1066                            StatusMessageTextWriter.WriteLine("Warning: Exception: {0}", exc1);
1067                        }
1068                    }
1069                }
1070                catch (System.IO.IOException) { }
1071            }
1072
1073            return;
1074
1075        }
1076
1077
1078
1079        internal static string GenerateTempPathname(string dir, string extension)
1080        {
1081            string candidate = null;
1082            String AppName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
1083            do
1084            {
1085                // workitem 13475
1086                string uuid = System.Guid.NewGuid().ToString();
1087
1088                string Name = String.Format("{0}-{1}-{2}.{3}",
1089                        AppName, System.DateTime.Now.ToString("yyyyMMMdd-HHmmss"),
1090                                            uuid, extension);
1091                candidate = System.IO.Path.Combine(dir, Name);
1092            } while (System.IO.File.Exists(candidate) || System.IO.Directory.Exists(candidate));
1093
1094            // The candidate path does not exist as a file or directory.
1095            // It can now be created, as a file or directory.
1096            return candidate;
1097        }
1098
1099    }
1100#endif
1101}
Note: See TracBrowser for help on using the repository browser.