Click here to Skip to main content
15,867,141 members
Articles / Desktop Programming / Windows Forms

Self-Extractor

Rate me:
Please Sign up or sign in to vote.
4.89/5 (37 votes)
31 Jul 2009CPOL4 min read 90K   2K   106   35
How to embed resources at runtime by creating dynamic assemblies.

Self-Extractor compiler

Contents

Introduction

Visual Studio comes up with a user interface tool to manage resources. The Resource Designer allows you to add/edit resources in an assembly at design-time. But, the resources might not be available at design-time: consider a scenario where the user provides some pictures to be embedded into a slideshow application. In that case, the Resource Designer is helpless. When you need to add resources at runtime, you are just alone.

This article shows how to embed resources at runtime by creating dynamic assemblies. It also shows how to compress files using the .NET Framework.

Background

Why embed resources into an existing executable file at runtime? I was working on a script-driven testing tool. The tool is used both by advanced users and by validation users. Advanced users edit script files and validation users run scripts. To make the job of validation users easier, advanced users should be able to deploy their scripts as standalone executables.

I decided to package the script file and all its dependencies into the executable script engine as embedded resources. I finally came up with the solution described in this article. I found the result interesting enough to share it with you. Since the script-driven testing tool is not a suitable demonstration application, I looked for something else and the self-extractor compiler is the first idea that came across my mind.

Using the Code

The code is organized in three parts:

  1. The ArchiveCompiler.Demo namespace contains the demo project.
  2. The SelfExtractor class in ArchiveCompiler.cs contains the logic to build a standalone executable with selected files embedded as resource.
  3. The SelfExtractor.cs file contains the source code built by the SelfExtractor class to generate the standalone executable.

1. Demo App

The self-extracted archive compiler (see screenshot above) is provided as an example. It demonstrates the dynamic creation of assemblies with embedded resources. Drop some files on the form and click the bottom-right button to create a self-extracted archive:

C#
using (SelfExtractor archive = new SelfExtractor())
{
    foreach (IconFileInfo file in files)
    {
        archive.AddFile(file.FullName);
    }
    archive.CompileArchive(saveFileDialog1.FileName, checkBox1.Checked);
}

The 'Run first item after extraction' option defines the RUN_1ST_ITEM symbol that enables a piece of code in the SelfExtractor.cs file. After extraction, the self-extracted archive starts a new process with the first embedded file (e.g., open a readme file, run post-extraction action...).

2. SelfExtractor Class

ArchiveCompiler.SelfExtractor

The SelfExtractor class exposes two methods, AddFile and CompileArchive. Call the AddFile method for each file to be included into the self-extracted archive. The file is compressed using the System.IO.Compression.GZipStream class. A temporary file is created with a '.gz' extension in the same directory as the selected file. The temporary compressed filename is added to the list of files ready to be embedded into the self-extracted archive.

The SelfExtractor class implements the IDisposable interface. The Dispose method is used to release unmanaged resources held by an instance of the class. Disposing a SelfExtractor object deletes all temporary compressed files created by the AddFile method.

C#
public void AddFile(string filename)
{
    // Compress input file using System.IO.Compression
    using (Stream file = File.OpenRead(filename))
    {
        byte[] buffer = new byte[file.Length];

        if (file.Length != file.Read(buffer, 0, buffer.Length))
            throw new IOException("Unable to read " + filename);

        using (Stream gzFile = File.Create(filename + ".gz"))
        {
            using (Stream gzip = new GZipStream
                (gzFile, CompressionMode.Compress))
            {
                gzip.Write(buffer, 0, buffer.Length);
            }
        }
    }
    // Store filename so we can embed it on CompileArchive() call
    filenames.Add(filename + ".gz");
}

The CompileArchive method contains the logic to build the self-extracted archive at runtime. It is based on the Microsoft.CSharp.CSharpCodeProvider class. It compiles an assembly from the source code contained in the SelfExtractor.cs file. The temporary compressed files are embedded as resource using the EmbeddedResources property of the CSharpCodeProvider class.

To keep things simple, the SelfExtractor.cs file is deployed side by side with the assembly containing the ArchiveCompiler.SelfExtractor class.

C#
public void CompileArchive(string archiveFilename, bool run1stItem, string iconFilename)
{
    CodeDomProvider csc = new CSharpCodeProvider();
    CompilerParameters cp = new CompilerParameters();

    cp.GenerateExecutable = true;
    cp.OutputAssembly = archiveFilename;
    cp.CompilerOptions = "/target:winexe";

    // Add a custom option to run a file after extraction
    if (run1stItem)
    {
        cp.CompilerOptions += " /define:RUN_1ST_ITEM";
    }
    if (!string.IsNullOrEmpty(iconFilename))
    {
        cp.CompilerOptions += " /win32icon:" + iconFilename;
    }
    cp.ReferencedAssemblies.Add("System.dll");
    cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");

    // Add compressed files as embedded resources
    cp.EmbeddedResources.AddRange(filenames.ToArray());

    // Compile standalone executable with input files embedded as resource
    CompilerResults cr = csc.CompileAssemblyFromFile(cp, sourceName);

    // yell if compilation error
    if (cr.Errors.Count > 0)
    {
        string msg = "Errors building " + cr.PathToAssembly;

        foreach (CompilerError ce in cr.Errors)
        {
            msg += Environment.NewLine + ce.ToString();
        }
        throw new ApplicationException(msg);
    }
}

3. SelfExtractor.cs

The SelfExtractor.cs file is the source code file compiled at runtime by the SelfExtractor class. It builds a simple Windows Forms application with a single hidden form. The job is done in the handler of the form Load event. First, it prompts the user for the output location. Then, embedded files are retrieved from resources and decompressed in the target directory. If the SelfExtractor.cs file has been compiled with the RUN_1ST_ITEM symbol, a new process is started with the first extracted file.

C#
private void Form1_Load(object sender, EventArgs e)
{
    Visible = false;
    ShowInTaskbar = false;

    FolderBrowserDialog fbd = new FolderBrowserDialog();
    fbd.Description = "Please, select a destination folder.";

    if (fbd.ShowDialog() == DialogResult.OK)
    {
        Assembly ass = Assembly.GetExecutingAssembly();
        string[] res = ass.GetManifestResourceNames();

        try
        {
            foreach (string name in res)
            {
                Stream rs = ass.GetManifestResourceStream(name);

                using (Stream gzip = new GZipStream
                    (rs, CompressionMode.Decompress, true))
                {
                    // remove ".gz"
                    string path = Path.Combine(fbd.SelectedPath,
                        Path.GetFileNameWithoutExtension(name));

                    using (Stream file = File.Create(path))
                    {
                        for (int b = gzip.ReadByte(); 
                            b != -1; b = gzip.ReadByte())
                        {
                            file.WriteByte((byte)b);
                        }
                    }
                }
            }
    #if RUN_1ST_ITEM
            if (res.Length > 0)
            {
                Process.Start(Path.GetFileNameWithoutExtension(res[0]));
            }
    #endif
        }
        catch (Exception ex)
        {
            MessageBox.Show(this, ex.Message, ass.GetName().Name, 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    Close();
}

Conclusion

In this article, we saw how to compress files with the System.IO.Compression namespace. We also saw how to dynamically create an assembly with embedded resources.

As mentioned in the Background section, creating a self-extracted archive compiler is not our primary objective. To keep the code as simple as possible, I deliberately ignore some issues. First, the original directory structure is not preserved. Secondly, write permission is required in every directory of the archived files in order to create the temporary compressed files.

Points of Interest

An alternative to the dynamic creation of assemblies could be the UpdateResource Win32 API with P/Invoke. It allows you to add/edit resources in an existing executable file. This API can be an option if the assembly cannot be built at runtime (too complex to build, source code not available...).

I chose the System.IO.Compression namespace for compression because it does not require additional reference. If you need to preserve the original directory structure, you should use a different solution such as a third-party ZIP library, the java.utils.zip utility from J#, or the System.IO.Packaging namespace if you are using .NET Framework 3.0.

History

  • December 03, 2008 - Original article.
  • July 30, 2009
    • Self-extracting EXE prompts for output location.
    • Custom icon support for self-extracting EXE.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSmall Clarification Pin
Joe Rozario2-Jun-09 19:11
Joe Rozario2-Jun-09 19:11 
GeneralRe: Small Clarification Pin
Thomas Polaert3-Jun-09 0:14
Thomas Polaert3-Jun-09 0:14 
GeneralRe: Small Clarification Pin
Joe Rozario4-Jun-09 23:43
Joe Rozario4-Jun-09 23:43 
GeneralRe: Small Clarification Pin
peejay0214-Apr-10 21:48
peejay0214-Apr-10 21:48 
GeneralRe: Small Clarification Pin
Thomas Polaert14-Apr-10 22:23
Thomas Polaert14-Apr-10 22:23 
GeneralRe: Small Clarification Pin
peejay0215-Apr-10 1:52
peejay0215-Apr-10 1:52 
GeneralVery nice work. Pin
MJ.Idrees6-Jan-09 7:33
MJ.Idrees6-Jan-09 7:33 
GeneralRe: Very nice work. [modified] Pin
Thomas Polaert9-Jan-09 2:03
Thomas Polaert9-Jan-09 2:03 
GeneralRe: Very nice work. Pin
MJ.Idrees8-Apr-10 20:24
MJ.Idrees8-Apr-10 20:24 
Generalnice Pin
radioman.lt4-Dec-08 18:54
radioman.lt4-Dec-08 18:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.