Contents
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.
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.
The code is organized in three parts:
- The
ArchiveCompiler.Demo
namespace contains the demo project.
- The
SelfExtractor
class in ArchiveCompiler.cs contains the logic to build a standalone executable with selected files embedded as resource.
- 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:
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
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.
public void AddFile(string filename)
{
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);
}
}
}
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.
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";
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");
cp.EmbeddedResources.AddRange(filenames.ToArray());
CompilerResults cr = csc.CompileAssemblyFromFile(cp, sourceName);
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.
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))
{
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();
}
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.
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.
- December 03, 2008 - Original article.
- July 30, 2009
- Self-extracting EXE prompts for output location.
- Custom icon support for self-extracting EXE.