Self Extracting and replacing Stub

Apr 29, 2010 at 2:49 PM

Thank you for providing the DotNetZip library. It's been wonderful so far.

I'm having some trouble with replacing the SFX stub though. I'd like to provide the same "use" as the current SFX, just with a couple more options in the UI (I do some preprocessing on the files before they are extracted). 

I read the thread at: http://dotnetzip.codeplex.com/Thread/View.aspx?ThreadId=70602

Specifically the post from Oct 15 2009 at 12:32 AM


I implemented your logic, and can create the exe fine:

            string outputFile = "sfx.exe";
            string sfxStub = "Stub.exe";

            byte[] buffer = new byte[4000];

            using (System.IO.FileStream output = new FileStream(outputFile, FileMode.Create))
            using (ZipFile zip = new ZipFile())
            using (System.IO.FileStream input = new FileStream(sfxStub, FileMode.Open))
            {
                zip.AddFile("test.txt");

                int n = 1;

                while ((n = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    output.Write(buffer, 0, n);
                }

                zip.Save(output);
            }

 

But when I attempt to read it back in, inside the stub, I get a System.IO.FileNotFound exception on the using line:

            Assembly assembly = Assembly.GetExecutingAssembly();

            using (Ionic.Zip.ZipFile file = ZipFile.Read(assembly.Location))

 

The other thing I realized, if I use this method, dependencies won't be included in the EXE will it? Should I run ILMerge after builds to merge the required assemblies into the stub exe, so that it's self contained?

Thanks for any help.

Coordinator
Apr 29, 2010 at 7:13 PM
Edited Apr 29, 2010 at 7:15 PM

I don't know what I wrote in October, but just for clarity, I think you might want to structure your creation logic like this:

public void CreateSfx()
{
    string outputFile = "sfx.exe";
    string sfxStub = "Stub.exe";

    byte[] buffer = new byte[4000];

    using (System.IO.FileStream output = new FileStream(outputFile, FileMode.Create))
    {
        using (System.IO.FileStream input = new FileStream(sfxStub, FileMode.Open))
        {
            int n = 1;
            while ((n = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, n);
            }

        }       
        using (ZipFile zip = new ZipFile())
        {
            zip.AddFile("test.txt");
            zip.Save(output);
        }
    }
}

To me that is much more readable - it clearly shows writing the stub into the output stream, then saving the zip file into the same stream.

Now, as for why you are getting a FileNotFoundException, you didn't provide the complete exception so I can't know what file it is.   I suggest that you examine the assembly.Location and see if it makes sense to you. It should be the path to a regular filesystem file, specifically the SFX exe that you created.

Regarding dependencies, yes, the SFX that you create this way will depend on the Ionic.Zip.dll . If you want a single-exe deployment, then normally you would need to use ILMERGE.  But I think that ILMerge cannot preserve the signature of a strongly-named assembly when merging them, unless you have the key that was used for signing.  Ionic.Zip.dll is strongly-named, and you don't have the key, therefore I don't think you can use ILMerge to embed Ionic.Zip.dll into your assembly.  The workaround is to embed the Ionic.Zip.dll into the EXE as a resource.

Apr 29, 2010 at 7:35 PM

 

Thanks for the quick reply. I meant to paste the entire exception, but somehow didn't. In collecting it just now though, I realized what the problem is. I was carelessly missing the Ionic.Zip assembly, so the FileIOException was it couldn't be found. Moving to a directory with the assemblies, it all works as expected.

 

Now I'm on to read about embedding as a resource. Thanks for pointing me to that.

 

Apr 29, 2010 at 8:24 PM

Thanks for the help thus far. I seem to have it working, but with a small hack.

I have embedded Ionic.Zip.dll as a resource, and set the build action to "Embedded Resource".

In ResolveEventArgs.Name, it is passing "Ionic.Zip, Version=1.9.1.5, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c".

If I call Assembly.GetExecutingAssembly().GetManifestResourceNames(), I see MyStub.Resources.Ionic.Zip.dll

I'm sure it's something simple like I embedded the resource wrong, but is there a way to translate the names? If I replace the call

 

Stream s = a1.GetManifestResourceStream(args.Name);

with

Stream s = a1.GetManifestResourceStream("MyStub.Resources.Ionic.Zip.dll");

 

everything works great. Sorry for the somewhat offtopic question.

 

Coordinator
Apr 29, 2010 at 11:14 PM

Yep  - your logic that receives the assembly name ("Ionic.Zip, Version=1.9....etc") needs to return an assembly. 
Sounds like you already know how to do that.  If the name begins with Ionic.Zip, then you know it wants the resource stream with the name MyStub.Resources.Ionic.Zip.dll.  

Maybe you are concerned about the name mismatch.  But the names of embedded streams are part of a different naming scope than the assemblies. So, the fact that the resource stream is not named "Ionic.Zip.dll" is not a concern, as long as you know how to map between names of resource streams and names of assemblies that you need to resolve.