This post is about how you can give your customers the convenience of installing applications on portable drives, such as USB memory keys, allowing them to take their working environments – applications, settings, and data – from machine to machine.
A few weeks ago, a customer asked me whether one of my applications (Writer’s Café) could be made compatible with the PortableApps.com suite she was running from her USB memory key. Writer’s Café could already be installed on an external drive, but it used a different directory layout and wasn’t appearing in the PortableApps.com menu.
I wasn’t familiar with PortableApps.com but I checked out the web site (http://www.portableapps.com), found a lot of good reviews and decided it was worth spending some time making Writer’s Café compatible with it. After all, writing software is the kind of thing that people want to take around with them. Currently PortableApps.com is for Windows only, but this could change in future. It comes with a whole suite of free programs such as FireFox, Thunderbird, OpenOffice.org and Pidgin, and the PortableApps.com site provides tools for developers to ‘wrap’ applications to force them to leave no data on the computer’s local drives on exit. If you’re adapting your own application, then you won’t need to jump through as many hoops, though you will still need to use the PortableApps.com installer and tell your application where to put its settings and data.
First, we’ll look at what you need to do to make your application portable regardless of any particular directory layout standard. (In this context, ‘portable’ means ‘portable between machines’, and not ‘available on different operating systems’ – though your app may very well be portable in both those senses.) I’ll write from the perspective of working with C++ and wxWidgets but it shouldn’t be hard to adapt this to your favourite language and toolkit.
For your application to be portable, it will need to be flexible about where it keeps the following information:
Documents and Settings\<user>\Application Data\<your app>. These are typically writeable files, such as a contact database.
Normally, these locations are determined by API calls, but we need a way to override these defaults when in portable mode. My method is to have the installer copy a file called
startup.cnf to the directory containing the executable; the application reads a few settings from it at initialisation time. You can determine the executable location with
wxStandardPaths::GetExecutablePath, or your own function if you want to also take into account, say, an environment variable that can be set from a launch script on Linux.
startup.cnf file for PortableApps.com installation looks like this:
ConfigLocation tells the application where to store settings – it will then use wxFileConfig instead of wxConfig so settings are not written to the registry. (wxWidgets’ hierarchy of wxConfigBase-derived classes makes this easy – your application class can have a pointer to a wxConfigBase, and you can simply assign it a wxFileConfig or wxConfig depending on startup.cnf settings. Subsequent access will remain identical in either case.)
%APPDIR% is replaced by the actual application directory and the path normalized with
wxFileName::Normalize in case
ConfigLocation contains relative path components. While
ConfigLocation specifies the settings file location, it also implies the Application Data location, because it’s natural to keep writeable app data next to the app settings; so there is no separate Application Data key. However, you could add this as a separate setting for maximum flexibility.
AppSupportFilesDir tells the application where to find the read-only support files. My application already stores
m_appDir so the location only has to be found once when finding resources; so
m_appDir is simply set to the
AppSupportFilesDir value if present. If the resources are always stored next to the executable, then it’s not necessary to specify this key.
RunningFromExternalDrive indicates whether the application is running from an external drive or not. Normally this will be 1, or
startup.cnf probably wouldn’t be present anyway. When the application detects this mode, it will attempt to deduce the location of the drive from the full executable path, and if it fails (this is more likely on Linux or Mac than on Windows), it will prompt the user for the drive location.
Perhaps conspicuous for its absence is
DocumentDir – the default document directory. You could add this key, or you could do what Writer’s Café does and simply deduce that if running from an external drive, the initial document directory should be
Documents at the root of the drive, with the ability to customize this in the application preferences.
Dealing with changing drive letters
Because the drive name can change between sessions (for example if other devices have been plugged in, or the program is running on a different computer), any absolute paths stored between sessions will need a bit of massaging. The way I do this is to save the name of the current drive along with absolute paths such as the preferred default document directory and recently-used files. When reading old settings, I check the original drive name against the absolute paths and if there’s a match, replace the old drive with the new drive that the application is currently running on. If you’re using standard classes to read in file names, for example
wxFileHistory::Load, you’ll need to adapt this to read in the file names explicitly and do the drive name substitution. Don’t forget to reverse the order if using
wxFileHistory::AddFileToHistory since this function adds the file name to the front of the list and you may find that your history list is in reverse order.
Here's some code you can use for loading a file history from a wxConfig object.
GetFilenameSubstitutingVolume gets the new file name given the old file name, old volume and new volume, and
FileHistoryLoadWithVolumeSubstitution loads the file history with the new names.
/// Find the file substituting this volume. Tests existence first.
bool GetFilenameSubstitutingVolume(const wxString& filename, wxString& newFilename, const wxString& newVolume, const wxString& oldVolume)
if (filename.IsEmpty() || !IsAbsolutePath(filename))
if (!oldVolume.IsEmpty() && !newVolume.IsEmpty())
if (oldVolume.Length() != 1 /* don't accept "/" */ && filename.Left(oldVolume.Length()).Lower() == oldVolume.Lower())
wxString fname = filename.Mid(oldVolume.Length());
if (fname == wxT('\\') || fname == wxT('/'))
fname = fname.Mid(1);
newFilename = AppendPaths(newVolume, fname);
/// Load file history with volume substitution if file not found
void FileHistoryLoadWithVolumeSubstitution(wxFileHistory& fileHistory, wxConfigBase& config, const wxString& newVolume, const wxString& oldVolume)
int i = 0;
while ((i < fileHistory.GetMaxFiles()) && config.Read(buf, &historyFile) && (!historyFile.empty()))
if (GetFilenameSubstitutingVolume(historyFile, filename2, newVolume, oldVolume))
// Make sure the new filename is reflected in the config file, so it matches
// the external drive we will save on exit.
historyFile = wxEmptyString;
for (i = 0; i < (int) files.GetCount(); i++)
Dealing with unplugged drives
A problem common with portable apps is what happens when a user unplugs the drive when the application still has open documents that on the drive. When the application tries to save the document and/or its settings, the result may be confusing error messages and suboptimal behaviour, possibly including infinite loops if the application refuses to exit without saving. It’s a good idea to wrap your file-writing code inside a function that prompts the user to retry, abort or ignore a failed save operation – Writer’s Café also gives the user the opportunity to save the file in a different location in case of hardware failure.
Self-installation onto an external drive
Writer’s Café has a wizard that allows the user to install the application to an external drive, using its own standard for directory organisation that allows for multiple executables for different operating systems, while sharing settings. However this is only convenient for users if they don’t mind installing the program to a local drive in the first place, albeit temporarily. But it’s worth considering (for the non-PortableApps.com case) if you don’t want to have a separate external-drive installer on each platform you support. The Writer's Café wizard can install its own files to the external drive, or it can download the latest version (for multiple platforms) from the web site.
Setting the correct executable name
PortableApps.com relies on the executable containing a user-friendly application name for display in its menu, so add a
VERSIONINFO block to your .rc file if you haven’t already. In the example below, the
symbols.h file contains version strings for the application that can be changed centrally.
VALUE "CompanyName", "Anthemion Software Ltd.\000"
VALUE "FileDescription", "Writer's Café\000"
VALUE "FileVersion", wcVERSION_NUMBER_RC_STRING
VALUE "InternalName", "Writer's Café\000"
VALUE "LegalCopyright", "(c) Anthemion Software Ltd.\000"
VALUE "LegalTrademarks", "\000"
VALUE "OriginalFilename", "WritersCafe.exe\000"
VALUE "ProductName", "Writer's Café\000"
VALUE "ProductVersion", wcVERSION_NUMBER_RC_STRING
VALUE "Comments", "\000"
; These values below can be removed at will
; VALUE "PrivateBuild", "Your Private Build\000"
; VALUE "SpecialBuild", "Your Special Build\000"
VALUE "Translation", 0x0409 0x04E4
More on PortableApps.com installation
OK, so your application now has the ability to run from, and store all its data on, an external drive instead of a local drive. Now you can create an setup program to install the application to the correct locations in the user’s PortableApps.com suite.
First you need to download the Nullsoft Scriptable Install System (NSIS), from http://nsis.sourceforge.net/Main_Page. You also need to install the following plugins to the NSIS Plugins directory:
FindProc.zip. Make sure the DLLs from each file go into the NSIS
Plugin directory and not a subdirectory, or NSIS will fail to compile your script.
Now create a new build directory where your NSIS scripts and application files will go. When you have established the installer has compiled correctly and installs the right files to the right locations, you can set about automating the process by copying application files and installer scripts to this build directory and updating version numbers if necessary.
An application that has been installed into the PortableApps.com suite typically has a location
<drive>\PortableApps\WritersCafePortable. Writer’s Café uses a directory layout like this, conforming to the PortableApps spec:
There are other possible directories, but these are the ones that Writer’s Café uses. Note that the Writer’s Café executable is directly under
WritersCafePortable, and the only other file here is
startup.cnf. All the read-only application resources are kept under
The contents of
startup.cnf is exactly as shown earlier.
App\AppInfo we put
appicon.ico. At the time of writing, these aren’t used – the PortableApps.com menu simply scans the subdirectories for .exe files and extracts the application name and icon from the executable – but may be used in future.
appinfo.ini looks like this:
Publisher=Anthemion Software Ltd.
Description=Writer's Cafe is a suite of writing tools for fiction authors.
App\WritersCafe we place all the resources (translations, image files etc.) that normally accompany the executable. In
Other\Source we place all the necessary NSIS scripts. For Writer’s Café, these are:
PortableApps.comInstallerConfig.nsh from this page:
and the other scripts from the Source directory of other apps in the PortableApps.com suite.
There are just a few things to alter in
PortableApps.comInstallerConfig.nsh. The following block contains the lines that I edited for Writer’s Café:
;== Basic Information. Basic information about the portable app
!define NAME "Writer's Cafe Portable"
!define SHORTNAME "WritersCafePortable"
!define VERSION "220.127.116.11"
!define FILENAME "WritersCafe_Portable_2.18"
!define CHECKRUNNING "writerscafe.exe"
!define CLOSENAME "Writer's Cafe Portable "
Now all we need to do is run the NSIS compiler on
PortableApps.comInstaller.nsi, and it will create
WritersCafe_Portable_2.18.paf.exe. This file can be run directly or it can be invoked from the PortableApps.com menu, under Options, and your application name should appear in the menu.
If you already have a script that builds your application installer, you should find it’s not much work at all to add PortableApps.com support. As an indication, here’s the relevant part of my own release script using the MSYS shell interpreter. Basically it populates the directory structure just described in a fresh directory, copying the program files and scripts to the relevant locations before invoking the NSIS compiler. The
doreplace function uses
sed to replace strings such as version numbers with the appropriate values.
# Make PortableApps.com version
# We assume that we've already done the Windows Setup version, so
# the image directory is populated.
if [ -d "$PORTABLEAPPSDIR" ]; then
rm -f -r "$PORTABLEAPPSDIR"
if [ ! -d "$SETUPIMAGEDIR" ]; then
echo *** No $SETUPIMAGEDIR.
cp $APPDIR/scripts/portableapps/*.nsh $APPDIR/scripts/portableapps/*.nsi $APPDIR/scripts/portableapps/*.bmp "$PORTABLEAPPSDIR/Other/Source"
cp "$APPDIR/scripts/portableapps/appinfo.ini" "$PORTABLEAPPSDIR/App/AppInfo"
cp "$SETUPIMAGEDIR/writerscafe.ico" "$PORTABLEAPPSDIR/App/AppInfo/appicon.ico"
doreplace "$PORTABLEAPPSDIR/App/AppInfo/appinfo.ini" "s/%VERSION%/$VERSION/g"
doreplace "$PORTABLEAPPSDIR/Other/Source/PortableApps.comInstallerConfig.nsh" "s/%VERSION%/$VERSION/g"
cp -r "$SETUPIMAGEDIR" "$PORTABLEAPPSDIR/App/WritersCafe"
mv "$PORTABLEAPPSDIR/App/WritersCafe/writerscafe.exe" "$PORTABLEAPPSDIR/writerscafe.exe"
echo "[Settings]" > "$PORTABLEAPPSDIR/startup.cnf"
echo "ConfigLocation=%APPDIR%/Data/settings.cnf" >> "$PORTABLEAPPSDIR/startup.cnf"
echo "AppSupportFilesDir=%APPDIR%/App/WritersCafe" >> "$PORTABLEAPPSDIR/startup.cnf"
echo "RunningFromExternalDrive=1" >> "$PORTABLEAPPSDIR/startup.cnf"
# Now invoke installation compiler
echo "Compiling NSIS PortableApps.com installer..."
"$NSISCOMPILER" //V2 "$PORTABLEAPPSDIR/Other/Source/PortableApps.comInstaller.nsi"
if [ -f "$PORTABLEAPPINSTALLER" ]; then
echo Created $PORTABLEAPPINSTALLER.
echo "*** Warning - did not create $PORTABLEAPPINSTALLER."
We've seen how to:
Have fun adapting your projects for use on external drives, and - with a bit of luck - attracting new customers as a result!
The PortableApps.com folk are in talks with USB drive manufacturers and it will be interesting to see how the project progresses, especially with expansion to take into account commercial applications and multiple platforms.