Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Manual uninstaller for PocketPC

0.00/5 (No votes)
7 Jun 2003 1  
If you don't want to use CAB, this is how.

Introduction

Most CE apps use Microsoft's CABWIZ tool to make their installers and uninstallers. I think it's an ugly tool, with a clunky user-interface. This article discusses how to write a manual installer/uninstaller instead.

Actually, the short answer is "You can't." CABWIZ is still needed. However, what you can do is to use CABWIZ only as a skeleton, whose only task is to execute your own manual uninstaller. This article is therefore about that skeleton.

I have one very important piece of advice: always check errors, and display the precise error message on-screen. Use GetLastError() and FormatMessage() to get the error text. And when you display it on-screen, note precisely where the error came from. Do not lump several errors into a generic "some failure occurred" message. This is because -- I guarantee -- there will be errors in your setup program, and end-users will write to you to complain, and unless you have a complete diagnostic then you'll never know what went wrong. (I have omitted error-checking in this article for simplicity).

Background

CABWIZ is a tool that comes bundled with eMbedded Visual Tools 3.0 (free) from Microsoft. Normally, you write an .INF file and compile it with CABWIZ. This generates a .CAB file. The .CAB file can be executed on the PocketPC, whereupon it installs the application and adds an entry into the Settings > RemovePrograms.

It's easy to write an installer manually - all you have to do is copy your executable into the folder \Program, and add a shortcut to it in \Windows\Programs. And maybe a shortcut in the start menu, and maybe register some registry keys.

The problem is how to register your program so it shows up under Start > Settings > System > RemovePrograms. Well, the system populates the RemovePrograms dialog with entries from the registry keys.

HKEY_LOCAL_MACHINE\SOFTWARE\Apps\<Provider> <AppName>

Within these keys, the dialog looks for a DWORD value Instl; if it is set to 1, then the app is currently installed, and should be listed in the dialog. If it is 0, then the app used to be installed but is no longer, and so won't be listed.

When the user elects to remove an installed program, it looks in the registry key for the values CmdFile and IsvFile. The first points to a .DAT file, usually \Windows\AppMgr\<Provider> <AppName>.DAT, which was put there by the installer. It is written in a proprietary .DAT format, such as is generated by CABWIZ. The second points to a .DLL file with four particular exports, which we will discuss.

The system's built-in uninstaller executes the exported function Uninstall_Init from the DLL, then parses and executes the .DAT file, then executes Uninstall_Exit. Therefore, for our manual-uninstaller purposes, our manual-installer would have to use CABWIZ to generate a .DAT file, and to place it in that directory, and to place our .DLL there as well.

But there's a problem. There are several other related registry keys which the system placed there when it executed the initial CAB file (i.e. when it installed the software in the first place). These extra registry keys are shared between all installed applications. And they're not documented. Therefore, it is not safe to do the install manually, since we mightn't set up these registry keys properly. Instead, we must use a .CAB file to do the install.

All is not lost. We hate CAB-based installers, and even though we're forced to use them, we can at least use them in a minimal way: so that the ONLY task of the CAB-installer is to set up these registry keys, and nothing more. Then we can put the real brains of our installer inside our own application, and the real brains of our uninstaller inside our custom-written .DLL file.

Incidentally, when the user clicks the 'Remove' button, the system pauses for some time with its CE-logo hourglass (between half a second and twenty seconds, usually closer to one second) before executing the setup.dll. There's no earthly reason for this. Doh!

A brief note: Our installer will invoke the CAB purely for minimal purposes, and so we want to invoke it completely silent -- without any dialogs. Here's how:

// How to install a CAB completely silently


#include <windows.h>


int WINAPI WinMain(HINSTANCE,HINSTANCE,LPTSTR,int)
{
  // get the current application directory

  wchar_t fn[MAX_PATH]; GetModuleFileName(NULL,fn,MAX_PATH);
  wchar_t *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\') lastslash=c+1; c++;}
  wcscpy(lastslash,L"ce_setup.cab");


  // First, if cabwiz thinks that the app was already

  // installed, then it will complain. Hence we lie to it.

  // MUST USE THE CORRECT REGKEY HERE! it is

  //   Provider <space> AppName

  // as defined in ce_setup.inf and used by cabwiz.

  HKEY hkey; LONG lres;
  lres=RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                  L"SOFTWARE\\Apps\\Lu ce_setup",0,0,&hkey);
  if (lres==ERROR_SUCCESS)
  { DWORD val=0;
    RegSetValueEx(hkey,L"Instl",0,REG_DWORD,(LPBYTE)&val,
                  sizeof(val));
    RegCloseKey(hkey);
  }

  // The act of running cabwiz will also delete the cab.

  // I think that's horrible. So I make it readonly

  DWORD attr = GetFileAttributes(fn);
  if (attr==0xFFFFFFFF)
  { return MessageBox(NULL,fn,L"Not found",MB_OK);
  }
  SetFileAttributes(fn,attr|FILE_ATTRIBUTE_READONLY);

  // Now run the cab using wceload, telling it to be quiet:

  const wchar_t *cmd = L"\\windows\\wceload.exe";
  wchar_t arg[MAX_PATH+40];
  wsprintf(arg,L"/noaskdest /noui \"%s\"",fn);
  //

  PROCESS_INFORMATION pi;
  BOOL res=CreateProcess(cmd,arg,0,0,0,0,0,0,0,&pi);
  if (!res) MessageBox(NULL,L"Couldn't",L"ce_setup",MB_OK);
  else
  { WaitForSingleObject(pi.hProcess,50000);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
  }

  // Really, we should wait until the cabwiz has finished

  // before restoring the file attributes. But I put a

  // timeout of 50s in there just in case something went

  // wrong, since it's bad in a PocketPC to leave

  // blocked processes.

  SetFileAttributes(fn,attr);

  return 0;
}

Setup.dll

As discussed, when the user clicks on 'Remove', the system will invoke a custom DLL. And this is where we put the brains of our uninstaller. Here is the form of this DLL. The two exported installer-functions are as follows:

#include <windows.h>



enum codeINSTALL_INIT
{ codeINSTALL_INIT_CONTINUE=0,
  codeINSTALL_INIT_CANCEL
};
enum codeINSTALL_EXIT
{ codeINSTALL_EXIT_DONE=0,
  codeINSTALL_EXIT_UNINSTALL
};

extern "C" __declspec(dllexport) codeINSTALL_INIT
Install_Init(HWND hpar, BOOL fFirstCall,
             BOOL fPreviouslyInstalled,
             LPCTSTR pszSuggestedInstallDir)
{ // can either return "continue", or abort before we've

  // even started the install

  return codeINSTALL_INIT_CONTINUE;
}

extern "C" __declspec(dllexport) codeINSTALL_EXIT
Install_Exit(HWND hpar, LPCTSTR pszChosenInstallDir,
             WORD cFailedDirs, WORD cFailedFiles,
             WORD cFailedRegKeys, WORD cFailedRegVals,
             WORD cFailedShortcuts)
{ // can either return "okay", or "uninstall what we've just

  // installed". But we're going to return "okay" since it

  // succeeded, and then delete some dummy files. The

  // dummy files were just a workaround around a bug in

  // cabwiz, not a sign of failure.

  wchar_t buf[MAX_PATH]; wcscpy(buf,pszChosenInstallDir);
  wcscat(buf,L"\\ce_setup_dummyN.txt");
  int len=wcslen(buf)-5;
  buf[len]='1'; DeleteFile(buf);
  buf[len]='2'; DeleteFile(buf);
  buf[len]='3'; DeleteFile(buf);
  buf[len]='4'; DeleteFile(buf);
  
  // And continue with other installation work if we want:

  MessageBox(hpar,L"Installing...",L"My App",MB_OK);
  // ...


  return codeINSTALL_EXIT_DONE;
}

The ugly issue with the temporary files will be discussed below.

The two exported uninstaller-functions are given below. Note that we make use of a global variable install_dir to record the install directory. That's because this information is given to us when the system calls Uninstall_Init(), but not when it calls Uninstall_Exit() (which is when we want it).

wchar_t install_dir[MAX_PATH];
//

enum codeUNINSTALL_INIT
{ codeUNINSTALL_INIT_CONTINUE=0,
  codeUNINSTALL_INIT_CANCEL
};
enum codeUNINSTALL_EXIT
{ codeUNINSTALL_EXIT_DONE=0
};

extern "C" __declspec(dllexport) codeUNINSTALL_INIT
Uninstall_Init(HWND hpar, LPCTSTR pszInstallDir)
{ // we can either return "continue", or "abort before we

  // even start the uninstall". We will abort if our

  // application is already open. Note: but first, take

  // this opportunity to record the install_dir.

  // That's because we'll need to refer to it in

  // Uninstall_Exit, but the system doesn't to tell us it

  // again. Hence the need to remember. It's safe to 

  // remember in a global variable, since Uninstall_Init

  // and _Exit are invoked in the same instance of the DLL.

  wcscpy(install_dir,pszInstallDir);
  //

  HWND halready = FindWindow(L"MyMainClass",L"My App");    
  if (!halready) return codeUNINSTALL_INIT_CONTINUE;
  MessageBox(hpar,L"Quit program before uninstalling it",
             L"My App",MB_OK);
  return codeUNINSTALL_INIT_CANCEL;
}

extern "C" __declspec(dllexport) codeUNINSTALL_EXIT
Uninstall_Exit(HWND hpar)
{ // Here we do the main work of our uninstalling:

  MessageBox(hpar,L"Uninstalling...",L"My App",MB_OK);
  // ...

  return codeUNINSTALL_EXIT_DONE;
}


BOOL WINAPI DllMain(HANDLE, DWORD, LPVOID) {return TRUE;}

The CAB file

As discussed, our installer will execute a CAB file (silently) to set up the registry keys. This CAB file will include the setup.dll that we built in the previous section. To build the CAB file, we will use Microsoft's CABWIZ. This is the control file ce_setup.inf:

[Version]
Signature   = "$Windows NT$"
Provider    = "Lu"
CESignature = "$Windows CE$"

[CEStrings]
AppName     = "ce_setup"
InstallDir  = %CE1%

The system concatenates Provider and AppName (with a space in between), and this concatenation is what appears in the RemovePrograms dialog. The %CE1% stands for \Program Files -- other %CE#% values are listed in the online help documentation under the help topic "Destination Directory Macro Reference". You can also use e.g. InstallDir = %CE1%\Subdirectory.

[DefaultInstall]
CopyFiles = FileList
AddReg    = 
CESetupDLL  = ce_setup.dll

[FileList]
ce_setup_dummy1.txt,ce_setup_dummy.txt,
ce_setup_dummy2.txt,ce_setup_dummy.txt,
ce_setup_dummy3.txt,ce_setup_dummy.txt,
ce_setup_dummy4.txt,ce_setup_dummy.txt,
; Bug! If using a dll, then less than four files
; generate an error when running the cab. ???

[SourceDisksNames]
1 = ,"source directory",,
2 = ,"bin directory",,ARMRel

[SourceDisksFiles]
ce_setup_dummy.txt=1
ce_setup.dll=2

[DestinationDirs]
FileList = 0,%InstallDir%

Here CESetupDLL refers to the setup.dll that we built previously. [SourceDisksFiles] associates it with an index number, and CABWIZ looks under [SourceDisksNames] to find where this file is located in the development machine.

[FileList] is a list of all the files that will be installed in the PocketPC. You might expect this to be empty. But there's a bug in CABWIZ whereby, if you use a setup.dll but have less than four items, it crashes. That's why we include four dummy files (each with size 1 byte!). Anyway, our Install_Exit function deletes them again immediately after installation has finished. Doh!

To invoke CABWIZ on this .inf file, and so build our .CAB, use this command. (it's all just a single line. Write it in a batch file!)

c:\progra~1\windows ce tools\wce300\pocket pc 
    2002\support\activesync\windows ce 
    application installation\cabwiz\cabwiz.exe" ce_setup.inf /err cabwiz.log

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here