Friday, 29 July 2011

Handling big files in Visual Studio Setup Projects

If you use Visual Studio to make your Setup projects, you probably noticed a very annoying and long lasting limitation of Visual Studio / Windows Installer, which is supposed to be fixed always in future versions. It´s nothing else than file size.
Setup projects do not handle well big files. In this post, I’ll show you how to workaround those problems:

Scenario 1: your files are big, but not that big

The first problem you will face when including big files in a setup project is a build error saying something like “Not enough storage is available to complete the operation”.
As mentioned here, If you handle files of a few hundreds of Megabytes, you can workaround this by:
  1. In the project, add a fake small file that has the same name as the large file.
  2. In the project properties page, set to installer to Package as Loose Uncompressed files.
  3. Build.
  4. Copy the full-sized large files to the build location.
In other words, you will fool the installer by inserting fake small files with the same names, and leaving them outside the MSI, so you can overwrite them before distributing your installer with the real, bigger files. As Windows Installer just looks for the file names, it will think the files are Ok, and will install the big ones.
Quick-Tip: If it’s annoying for you to have all the installation files as “loose uncompressed files”, you can only leave as “loose uncompressed” those files to be overwritten, leaving the rest inside the installer or in CAB files, whatever you prefer. To do so, do not change the project´s property, but instead change the property PackageAs of those files to be overwritten. The default value is vsdpaDefault, which means “do as specified in the project properties”. If you change it to vsdpaLoose, those files will remain loose in the disk, no matter what the general behavior for the rest of the files is.
This workaround is fine, but it doesn’t work always, as it will also fail if you have very big files, of several GBs…

Scenario 2: your files are huge

If you are dealing with huge files, of several GBs, the previous workaround won´t work either. The setup project will compile fine, but Windows Installer will probably fail later. This time, the error is shown when installing the application, and it appears in the form of a “couldn’t access destination directory, check you have enough privileges to do so”… or something like that.
What can you do in those cases?

Option 1: Split your files into smaller ones

You can use a file splitting utility, like FFSJ, to split your files into parts. Then add those smaller files to your Setup Project and install normally. When installation is complete, you will need to deal with re-joining the parts back into the original file. FFSJ supports receiving commands from the command line, so you can easily do this with a Custom Action.
  • Insert FFSJ into your setup project (check copyright)
  • Add a “Commit Custom Action” that points to FFSJ
  • Mark the Custom Action as InstallerClass = False
  • Pass the following “Arguments” to the custom action:
"-Task=Join" "-Input=[TARGETDIR]\FILE_TO_JOIN.001" "-Output=[TARGETDIR]\OUTPUT_FILE.dat" –DeleteInput
This action will be executed once the installation is complete, and once all the files have been copied to the Target Dir. If you include the “-DeleteInput” parameter, original file parts will be deleted.
Pros of this workaround:
  1. All files are handled “inside” Visual Studio, but that won´t help much anyway, as the file copy progress is not as “fluid” as it should be
Cons of this workaround:
        • You need to split the file into parts and insert them in Visual Studio manually
        • Copyright issues with the file splitter may apply
        • Do not handle uninstallation, as Windows Installer will try to remove the original files (parts), which no longer exist

Option 2: Code an specific Custom Action

I´m not sure of this, but I know of no way to call system commands like “copy” from a Visual Studio custom action, so we will code a very simple C# program, that will handle some basic file operations for us, like copying and deleting files. 
  1. Create a new Visual Studio project of the type Windows –> Console Application
  2. In the class Program.cs, we will deal with some file operations. In our example, we will assume that the huge file we want to copy has the name “Contents.dat” (you can experiment here to make it fit your needs). So, for our example, we would paste some code like the following:
        static int Main(string[] cmdLine)
            string operation = cmdLine[0];
            string sourcePath, installPath, sourceFileName, destFileName;
            switch (operation)
                case "Copy":                   
                    sourcePath = cmdLine[1];
                    installPath = cmdLine[2];
                    sourceFileName = System.IO.Path.Combine(sourcePath, "Contents.dat");
                    destFileName = System.IO.Path.Combine(installPath, "Contents.dat");
                    if (!System.IO.Directory.Exists(installPath))
                        return -1;
                    System.IO.File.Copy(sourceFileName, destFileName, true);
                case "Delete":
                    installPath = cmdLine[1];
                    destFileName = System.IO.Path.Combine(installPath, "Contents.dat");
            return 0;
As you can see, this is a very basic code that expects some Command Line parameters, with the following syntax:
Parameter 0: Operation type [“Copy” or “Delete”, without the quotes].
If copying: Parameter 1: Source path where “Contents.dat” is located. Parameter 2: Destination path where we want to copy it
If Deleting: Parameter 1: Path to delete “Contents.dat” from.
            • Next, we compile the code and generate an EXE file
            • Insert that EXE file into your setup project
            • Now we just need to create a couple of Custom Actions in our setup project that point to that EXE file, like the following (note that they will be executed at installation Commit and at Uninstallation):
              • Mark your custom actions as InstallerClass = False
              • Change the Custom Action arguments like the following:
                            • Arguments for the Copy operation: Copy "[SOURCEDIR]\" "[TARGETDIR]\"
                            • Arguments for the delete operation: Delete "[TARGETDIR]\"
Quick-Tip: Variables that contain paths, like [SOURCEDIR] or [TARGETDIR] might contain spaces. By default, that would mean that each part of the path would be interpreted as separated arguments. In order to avoid that, we surround those variables with quotes (“”).
Quick-Tip 2: As explained here, Windows Installer automatically adds a trailing slash to variables like [TARGETDIR], so in order to avoid messing the arguments, you will need to put a \” at the end, instead of a single quote (“).
Quick-Tip 3: It is important to note the return value of the Custom Action, as it’s checked by Windows Installer to decide if the action was completed successfully. So, if it’s 0, everything went fine. If it’s –1, an error occurred and the installation will be cancelled. So it’s up to you if the file copy is vital for your installation or not. But if it is, you´d better be returning a “–1” in those cases, to interrupt the installation.
So what´s going on here?
Easy to guess: Windows Installer will call our custom action after when the installation finish is about to end (Commit), with the arguments we specified, that will make our EXE copy the huge files to the installation directory.
In order to properly handle application uninstallation, we added another call to the same custom action, but this time with different arguments that will make it remove the huge files copied from the installation directory.
Pros of this workaround:
  1. No need to split files
  2. No copyright issues (everything is home made)
  3. Handles uninstallation
  4. No need to create fake, smaller files to fool the installer
  5. The custom action created can be made generic, so you can re-use it as many times you want.
Cons of this workaround:
        • It would be much better if Visual Studio didn’t have this limitation, and we never needed to worry about this Guiño
So, I think option 2 is much better. In fact, is what I use.
Hope it helped.