Just one file please…
Recently I had to create an installer from a Visual Studio 2008 project. Having bought the professional edition this should be a no brainer.
- Create a setup project
- Configure the dependencies and prompts
- Build and deploy
While all of the above actually holds true, there are a few skeletons in the closet. The setup project creates not one file, but two files. Both are required for the installation to work correctly on all windows versions.
My customer would like to have just one file for deployment, since they need to distribute over the internet and it would be inconvenient to distribute two files. I really can’t blame them, who doesn’t distribute over the internet.
After having spent a few minutes with google, it soon became apparant that VS2008 always produces 2 files (setup.exe + something.msi) for setup (why oh why MS)
Microsoft does not provide a tool for joining them.
My quest was simple. Find a tool that merges setup.exe and a something.msi into a single executable file. Seconds into my search I found that many others had experienced the exact same problem. Several companies even make tools for solving this simple puzzle.
I found that WinZip had the exact tool for my need; WinZip Self-Extractor
This tool does everything you need to merge the two files into a single executable, with Icons and text dialogs and the works.. perfect, and cheap too.
Equipped with a purchased copy of WinZip Self-Extractor I quickly compiled my setup.exe and something.msi into a single file and ran it. It worked like a charm.. perfect.
I shipped a test version to my customer, who quickly called me back and said that it didn’t work at all!
Why what happened. Google my friend in need, gave me the answer. MS did a small change in the way their setup.exe bahaves between the VS2005 and VS2008 version. Read the details here: forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3528121&SiteID=1
Short story is that WinZip Self-Extractor and VS2008 version of setup.exe does not play nice together at all, and the error is timing dependent. (It worked on my computer, but not with my customer)
The process of installing using the WinZip Self-Extractor is as follows:
- WinZip unzips itself into a temp directory
- Setup.exe is launced from the temp directory
- Setup.exe checks for missing dependecies (MS installer, .NET etc.. )
- Setup.exe installs missing dependencies
- Setup.exe launches something.msi
- Something.msi installs the application
- Something.msi ends
- Setup.exe ends
- WinZip cleans up the temp directory
- WinZip ends
Well, thats actually the VS2005 + WinZip version (the one that works). In the VS2008 + WinZip version item #8 is placed between #5 and #6, meaning that the setup.exe ends before the msi does. In effect WinZip thinks that the setup.exe process (and thus the entire install) is done and proceed to clean the temp directory while the msi is running and needs to read from that temp directory. Boom! installation fails.
Being this close to having a single file installer, I just could not give up. (Yes, I could have bought an different installation package, but I had VS2008 and WinZip self-extractor, I really didn’t want to buy another software package, just because the two I had, had a few bugs.)
The solution I came up with, was this
- Insert a exe file in the zip file that would run after the files were unpacked, but before setup.exe
- Make this file copy all the temp files to some other location
- Mark the files for deletion on next reboot
- Launch the setup.exe from the new location
This cheats WinZip and works.
Here is the full source code for the SetupProxy.exe program that I run prior to the setup.exe (That is, the program that WinZip runs instead of setup.exe)
// SetupProxy.cpp : Defines the entry point for the application.
//
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// Copy the setup.exe file
CopyFileA( "setup.exe", "..\\setup.exe", FALSE );
MoveFileExA( "..\\setup.exe", NULL, MOVEFILE_DELAY_UNTIL_REBOOT );
// Copy all msi files (most likely just one)
WIN32_FIND_DATAA findData;
HANDLE fileRef = FindFirstFileA( "*.msi", &findData );
if( fileRef != INVALID_HANDLE_VALUE )
{
do{
char dstName[MAX_PATH] = "..\\";
strcat_s( dstName, sizeof(dstName), findData.cFileName );
CopyFileA( findData.cFileName, dstName, FALSE );
// Mark the copied file for deletion on next reboot
MoveFileExA( dstName, NULL, MOVEFILE_DELAY_UNTIL_REBOOT );
}while( FindNextFileA( fileRef, &findData ));
FindClose( fileRef );
}
// kick off the setup.exe file
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
SetCurrentDirectoryA( ".." );
CreateProcessA( "setup.exe", NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, "..", &si, &pi );
WaitForSingleObject( pi.hProcess, 0 );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
// and.. we're done
return 0;
}
It’s free!
Cheers,
Ruben