How to Find the Application Name of a PocketPC CAB Installer

A while back, I needed to write a program that could manage the installation and uninstallation of programs on the PocketPC\Windows Mobile platform. The program needed to install CAB file X from location Y, note what it had installed from location Y, and then later uninstall it. It had to do this all programatically with no user intervention.

Installing the program is easy, you simply call wceload.exe and pass the CAB file path as a command line parameter. Uninstalling is also easy, call unload.exe on PocketPC 2003 or invoke the Uninstall CSP on Windows Mobile 5 with the installed application name (i.e. what shows up under “Remove Programs” on the device). However, getting the application’s name isn’t trivial and is the point of this article.

On PocketPC 2003, you can find the application’s name by doing the following:

  1. Install the app via wceload
  2. Iterate over the registry keys in “HKLM\Software\Apps”
  3. Find the key where the CabFile value matches the path you passed to wceload
  4. Once you find the key, the application’s name is the name of the key

However, things aren’t so easy on Windows Mobile 5. For whatever reason (and I can’t complain because none of this is documented), the CabFile value is no longer set when the application is installed and the above approach won’t work. After some digging, I came across the CAB format spec at:

http://support.microsoft.com/kb/310618

Using that spec, I came up with the following code to dig into the compiled CAB and find the application name. Jump straight to the example program download if you’d like.

First, declare some variables we’ll need and open a file stream:

private string GetAppName(string file)
{
	Int16 iFlags;
	Int16 iVersion;
	byte cbCFData;
	Int32 iDataBlockOffset;
	Int16 iProviderLength;
	Int16 iProviderOffset;
	Int16 iAppNameLength;
	Int16 iAppNameOffset;
	byte[] buffer;      

	string strAppName, strProvider;      

	// open the file and create a stream reader
	FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
	BinaryReader br = new BinaryReader(fs);

The first four bytes of the file should be ‘MSCF’:

	// make sure this is a CAB
	if (!(fs.ReadByte() == 'M'
		&& fs.ReadByte() == 'S'
		&& fs.ReadByte() == 'C'
		&& fs.ReadByte() == 'F'))
		throw new ArgumentException("File does not have appropriate signature");

Verify the CAB version is 1.3:

	// jump to offset 24 to read the format version
	fs.Seek(24, SeekOrigin.Begin);
	iVersion = br.ReadInt16();
	if (iVersion != 0x0103)
		throw new ArgumentException("Invalid CAB format");

Read the header flags and make sure it’s a header we can interpret. Depending on the version of CabWiz that was used to create the CAB, the flags will be 0 or 4(cfhdrRESERVE_PRESENT). If the reserve information is present then the cbCFHeader, cbCFFolder, and cbCFData fields are present in the header and we’ll need to skip them:

	// jump to offset 30 to read the header flags
	fs.Seek(30, SeekOrigin.Begin);
	iFlags = br.ReadInt16();      

	// we only understand flags 0x0 and 0x4
	if (iFlags == 0)
	{
		// no optional information in this header.
		// jump 4 bytes forward to the beginning of
		// the first CFFOLDER entry
		fs.Seek(4, SeekOrigin.Current);
	}
	else if (iFlags == 4)
	{
		// cbCFHeader, cbCFFolder, and cbCFData fields
		// are present.  We only need to verify that
		// cbCFData is 0, so jump forward 7 bytes and test
		fs.Seek(7, SeekOrigin.Current);      

		cbCFData = (byte)fs.ReadByte();      

		if (cbCFData != 0)
			throw new ArgumentException("Invalid cbCFData");
	}
	else
		throw new ArgumentException("Unsupported header flags");

After reading the header, we should positioned at the beginning of the first CFFOLDER, where the first field is the offset of the CFDATA block (the .000 file). Read the offset and skip the 8 byte CFDATA header:

	// we're now pointing to the start of the first
	// CFFOLDER entry.  Read the position of the CFDATA block, skipping past
	// its 8 byte header.
	iDataBlockOffset = br.ReadInt32() + 8;      

	// jump to the data block
	fs.Seek(iDataBlockOffset, SeekOrigin.Begin);

According to an explanation of the .000 file (http://www.cabextract.org.uk/wince_cab_format/#000_format), the first 4 bytes of the .000 file should be ‘MSCE’:

	// we should now be pointing at the ".000" file, whose
	// signature is "MSCE"
	if (!(br.ReadByte() == 'M'
		&& br.ReadByte() == 'S'
		&& br.ReadByte() == 'C'
		&& br.ReadByte() == 'E'))
		throw new ArgumentException("Invalid .000 format");

84 bytes into the .000 header, we’ll find the application name length and offset and the provider name length and offset:

 	// we're looking at a valid .000 file.  We need to look up the
	// AppName and Provider Length/Offsets.  These values start at
	// position 84 in the .000 header, so jump 80 bytes (we're already
	// 4 bytes into the header).
	fs.Seek(80, SeekOrigin.Current);      

	iAppNameOffset = br.ReadInt16();      

	iAppNameLength = br.ReadInt16();      

	iProviderOffset = br.ReadInt16();      

	iProviderLength = br.ReadInt16();

The final step is to jump to the offsets and read the values:

	// now jump to the AppName offset and read it
	fs.Seek(iDataBlockOffset + iAppNameOffset, SeekOrigin.Begin);
	// convert from ASCII and don't include the null terminator
	strAppName = ASCIIEncoding.ASCII.GetString(br.ReadBytes(iAppNameLength - 1));      

	// jump and read the Provider string
	fs.Seek(iDataBlockOffset + iProviderOffset, SeekOrigin.Begin);
	// convert from ASCII and don't include the null terminator
	strProvider = ASCIIEncoding.ASCII.GetString(br.ReadBytes(iProviderLength - 1));      

	br.Close();
	fs.Close();      

	return strProvider + " " + strAppName;
}


Download GetCabAppName.zip

Downloaded a total of 7 times

1 comment so far

  1. Jason Bunting October 23, 2008 5:07 pm

    Just a minor thing: you might think about changing the display of the date associated with your posts - it took me forever for find it; maybe if you made it slightly larger, bold and white - also, maybe move it to the right of or under the title of each post.

    Take it for what it is worth.

Leave a comment

Please be polite and on topic. Your e-mail will never be published.