Build a WPF app or lib in one file

If you wrote an WPF Applicaiton and want to share is with someone you don’t want to hassel with all the files in the Debug folder.

Here I will show you, how to embedd all your files in one big executable.

For demonstration purpose I use an old WPF application I wrote on GitHub https://github.com/dhcgn/Encryptor

Problem

If you create a WPF application the typical output looks like this:

└───Debug
    │   Encryptor.Engine.dll
    │   Encryptor.Engine.pdb
    │   Encryptor.exe
    │   Encryptor.exe.config
    │   Encryptor.exe.manifest
    │   Encryptor.FileOperator.dll
    │   Encryptor.FileOperator.pdb
    │   Encryptor.pdb
    │   GalaSoft.MvvmLight.dll
    │   GalaSoft.MvvmLight.Extras.dll
    │   GalaSoft.MvvmLight.Extras.pdb
    │   GalaSoft.MvvmLight.Extras.xml
    │   GalaSoft.MvvmLight.pdb
    │   GalaSoft.MvvmLight.Platform.dll
    │   GalaSoft.MvvmLight.Platform.pdb
    │   GalaSoft.MvvmLight.Platform.xml
    │   GalaSoft.MvvmLight.xml
    │   Ionic.Zip.dll
    │   Ionic.Zip.xml
    │   Microsoft.Practices.ServiceLocation.dll
    │   Microsoft.Practices.ServiceLocation.pdb
    │   Microsoft.Practices.ServiceLocation.xml
    │   System.Windows.Interactivity.dll

Hosting Process (vshost.exe) files are excluded in this view

So if you want to share your application, you need to share the executable file with all its DLLs. Typically, this means that you put all in zip file.

Old fashion easy way

In the past I embedded all DLLs in the executable file and load then with the AppDomain.AssemblyResolve Event.

e.g.

2

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
    var name = args.Name + ".dll";    
    var assembly = Assembly.GetExecutingAssembly();    
    using (var input = assembly.GetManifestResourceStream(name))
    {
        return input != null ? Assembly.Load(StreamToBytes(input)): null;
    }
};

Better way

I found a few interesting material how to archive this with Project Target, see Telerik and Digitallycreated.

The Visual Studio build process is defined by a series of MSBuild .targets files that are imported into your project file. One of these imported files, Microsoft.Common.targets, can be extended to allow you to run custom tasks at several points in the build process. (Source: link)

List of Microsoft.Common.targets you can override

  • {Before|After}Compile
  • {Before|After}Build
  • {Before|After}Rebuild
  • {Before|After}Clean
  • {Before|After}Publish
  • {Before|After}ResolveReference
  • {Before|After}ResGen

Sad to say these Microsoft.Common.targets are really bad documented.

First, we want to embed all DLLs files.

  <Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>

than we delete all unnecessary files (xml, pdb, dll).

  <Target Name="AfterBuild">
    <ItemGroup>
      <FilesToDelete Include="$(TargetDir)\*.pdb" Exclude="$(TargetDir)\$(TargetName).pdb" />
      <FilesToDelete Include="$(TargetDir)\*.dll" Exclude="$(TargetDir)\$(TargetName).dll" />
      <FilesToDelete Include="$(TargetDir)\*.xml" />
    </ItemGroup>
    <Delete Files="@(FilesToDelete)" />
  </Target>

Now we load all these assemblies at the entry point for the application.

    public partial class App : Application
    {
        public App()
        {
            EmbeddedLibsResolver.Init();
        }
    }

    public class EmbeddedLibsResolver
    {
        public static void Init()
        {
            var assemblies = new Dictionary<string, Assembly>();
            var executingAssembly = Assembly.GetExecutingAssembly();
            var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase));

            foreach (var resource in resources)
            {
                using (var stream = executingAssembly.GetManifestResourceStream(resource))
                {
                    using (var memstream = new MemoryStream())
                    {
                        stream.CopyTo(memstream);

                        assemblies.Add(resource, Assembly.Load(memstream.ToArray()));
                    }
                }
            }

            AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
            {
                var assemblyName = new AssemblyName(e.Name);
                var path = $"{assemblyName.Name}.dll";

                return assemblies.ContainsKey(path) ? assemblies[path] : null;
            };
        }
    }

Or in a simple console application:

    public class Program
    {
        public Main(string[] args)
        {
            EmbeddedLibsResolver.Init();
            MainInternal(args); // Important!
        }
    }

Result

If we now build the application we see that the the file Encryptor.exe is noticeable larger, and all the dlls files are missing :thumbsup:.

1

Now you can just give someone this one file and the person can run your application.

The changes on this sample project can be seen here GitHub Commit

I will improve this implementation with upcoming experience.