ZipFile.SaveSelfExtractor.cs 48 KB

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