Programmatically Discriminate between Upgrade or Uninstall of a CAB on Windows Mobile


Recently I've worked with a developer on an interesting issue I’ve not found any clue on the web about, and the solution is based on one of those details that you can empirically retrieve but that there are not documented anywhere, therefore on future releases may change without any warning. This was for example what happened to the ClassName of NETCF applications... see Daniel Moth's post about this: "#NETCF_AGL_". I’ve also discussed about this in a MSDN Forum post I found interesting, where the topic was something like “how to prevent the CLR to not allow a second instance of the same NETCF application to run on Windows Mobile”. As I probably wrote elsewhere, “undocumented” doesn’t mean “technically not achievable”: it means that Product Group may change it as it doesn’t have to be backward-compatible.

In this case we had an application that may have been updated at a later time: the ISV was wondering if there’s any way in the setup.dll of application’s CAB to specify, during uninstallation, if the uninstall is taking place during a version-upgrade or if it's a pure uninstallation. This is because, for example, the application's installation copies also some large files that user no longer needs after the uninstall and therefore are deleted: but it still needs them if the user is uninstalling a former version of the app in order to install a newer one. I hope I've been clear... smile_confused Things can get more complicated by the fact that the when you do an “upgrade” of the same ap

Well... we found out that there's no documented and standard way to achieve the goal, so we had to be creative - as usual... Nerd To understand how to operate, we needed to understand the actual flow when installing\uninstalling\upgrading (=installing the CAB of a newer version of the app while a older one is installed); moreover, we had to take care a particular condition, i.e. when upgrading the user is prompted with the message “The previous version of… Select Ok to continue or cancel to quit” -- and here it comes handy the “undocumented but empirically retrievable” info, that I'm going to show in a minute.

The regular flow when installing and uninstalling is:

  • Install:
    1. DLL_PROCESS_ATTACH – Setup.dll is loaded
    2. Install_Init
    3. Install_Exit
    4. DLL_PROCESS_DETACH – Setup.dll is unloaded
  • Uninstall:
    1. DLL_PROCESS_ATTACH – Setup.dll is loaded
    2. Uninstall_Init
    3. Uninstall_Exit
    4. DLL_PROCESS_DETACH – Setup.dll is unloaded

When upgrading, the flow is as follows:

  1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
  2. Install_Init
  3. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
  4. Message prompt to the user to confirm uninstall of previous version
    • Select Ok:
      1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded, *BUT* the installer doesn’t know if we’re uninstalling because of a real uninstall or an upgrade
      2. Uninstall_Init
      3. Uninstall_Exit
      4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
      5. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
      6. Install_Init
      7. Install_Exit
      8. running the exec
      9. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
    • Select Cancel:
      1. Nothing happens (setup.dll was already unloaded)

So the problem is how to let the installer know that it’s uninstalling or upgrading… the idea I had was to modify the flow this way, based on the fact that when “upgrading”, the flow involves firstly a Install_Init and secondly a Uninstall_Init; in contrast when “uninstalling” the flow doesn’t involve a first step through Install_Init:

a. Install

  1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
  2. Install_Init (query if the app is already installed (through the Uninstall CSP) and set a registry key or whatever, e.g.[HKLM\UpgradeKey]Upgrade=0 if it was not installed and 1 viceversa) –> now: Upgrade=0
  3. Install_Exit
  4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded

b. Upgrade:

  1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
  2. Install_Init (query if the app is already installed (through the Uninstall CSP) and set a registry key or whatever, e.g.[HKLM\UpgradeKey]Upgrade=0 if it was not installed and 1 viceversa) –> now: Upgrade=1
  3. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
  4. Message prompt to the user to confirm uninstall of previous version
    • Select Ok:
      1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
      2. Uninstall_Init (QUERY [HKLM\UpgradeKey]Upgrade and act accordingly) –> now: Upgrade=1 (was just set by Install_Init at point 2. of the Upgrade flow, and then it can be set back to 0)
      3. Uninstall_Exit
      4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
      5. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
      6. Install_Init
      7. Install_Exit
      8. running the exec
      9. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
    • Select Cancel:
      1. Nothing happens (setup.dll was already unloaded)

c. Uninstall

  1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
  2. Uninstall_Init (query if we’re upgrading by looking at the registry key) –> now: Upgrade=0 (it wasn’t changed by anyone)
  3. Uninstall_Exit
  4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded

To conclude, the idea was to:

  • Install_Init creates the “Upgrade” registry key (or other info) and sets 0 if the application is NOT already installed and 1 viceversa. To check if an application is already installed I think I’ve already discussed once on the Uninstall Configuration Service Provider… yes, see this post.
  • Uninstall_Init checks the value of the key and act accordingly (just as an example, if that’s an “uninstall” then remove some files that are no longer used)

 

HOWEVER… smile_confused this approach had a problem… what happens if user answers “Cancel” to the prompt “The previous version of… Select Ok to continue or cancel to quit”? Nobody can restore [HKLM\UpgradeKey]Upgrade to 0 after that Install_Init set it to 1, and future possible “Uninstalls” are considered as “Upgrades”! So basically the problem is when user firstly doesn't accept to uninstall the previous version during upgrade and then secondly she uninstalls the previous version on her own: when doing this second action, the uninstall procedure would find that the Upgrade registry key is set to 1 and therefore would consider an upgrade even if in reality it's an uninstall.

So, next question was: is there any programmatic way to know if user selects “Cancel” when prompted about uninstalling previous version? The only way I could think at was to get ahold of the WCELOAD.EXE process and invoke GetExitCodeProcess() API to retrieve its return value: the assumption was that it was different when user hits “Cancel”… it turned out that this is true, but this approach involved an external application to be launched for example in setup.dll’s DLL_PROCESS_ATTACH, that can monitor WCELOAD.EXE and check its return value during Uninstall phase… Why an external process? Because the prompt comes up EVEN BEFORE the setup.dll can handle Install_Init.

The “undocumented but empirically retrievable” info I was mentioning at the beginning is precisely the return value of WCELOAD.EXE when user hits Cancel. As I said, not being documented it may change on future releases without any notice..

And now some code please!!

I’m talking about the following in setup.dll:

#define DELETE_STR(s) \
if (NULL != s) \
delete [] s;
 
 
HINSTANCE g_hinstModule;
 
BOOL APIENTRY DllMain(
    HANDLE hModule, 
    DWORD  ul_reason_for_call, 
    LPVOID lpReserved
    )
{
    //MessageBox(NULL, TEXT("Now attach the debugger"), TEXT("Test"), MB_OK);
 
    switch (ul_reason_for_call)
    {
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            g_hinstModule = (HINSTANCE)hModule;
            break;
 
        case DLL_PROCESS_ATTACH:
              g_hinstModule = (HINSTANCE)hModule;
 
              //check if UpgCheck.exe is already available on device (1st time it won't, but in any case we don't need it)
              LPCWSTR pszFileNameWithPath = new TCHAR[MAX_PATH];
              pszFileNameWithPath = TEXT("\\Windows\\UpgCheck.exe");
              WIN32_FIND_DATA wfdFindFileData;
              HANDLE hFile = FindFirstFile(pszFileNameWithPath, &wfdFindFileData);
              if(hFile == INVALID_HANDLE_VALUE)
              {
                            DELETE_STR(pszFileNameWithPath);
                            break;
              }
              FindClose(hFile);
 
              //Launch external process that will monitor wceload.exe
              BOOL bRet;
              SHELLEXECUTEINFO sei = {0};
 
              sei.cbSize = sizeof(sei);
              sei.nShow = SW_SHOWNORMAL; 
              sei.lpFile = pszFileNameWithPath;
              sei.lpParameters = TEXT(" ");
              bRet = ShellExecuteEx(&sei);
 
              //if (!bRet)
              //     MessageBox(NULL, TEXT("Could not launch UpgCheck"), TEXT("Test"), MB_OK);
 
              DELETE_STR(pszFileNameWithPath);
              break;
    }
 
return TRUE;
}

 

And I’m talking about something similar in the wceload-monitor:

int _tmain(int argc, _TCHAR* argv[])
{
       int const MAXBUF = 32;
       HRESULT hr = E_FAIL;
       HANDLE hProcess = NULL;
       BOOL bRes = FALSE;
       DWORD dwRes = 0;
 
       LPTSTR lpBuf = new TCHAR[MAXBUF];
       ZeroMemory(lpBuf, MAXBUF - 1);
 
       //retrieve process handle of wceload.exe, until it's found
       do{
              hr = GetProcessHandleByName(TEXT("wceload.exe"), &hProcess);
              CHR(hr);
              Sleep(1000);
       } while (INVALID_HANDLE_VALUE == hProcess);
 
       //hr = LogToFile(TEXT("\r\nwceload found!\r\n"), g_pszFilename);
       //CHR(hr);
 
       //retrieve wceload.exe exit code, until it exits
       do {
              Sleep(1000);
              bRes = GetExitCodeProcess(hProcess, &dwRes);
 
              if ( !bRes )
              {
                     goto Exit; //GetLastError
              }
       } while (STILL_ACTIVE == dwRes); 
       
       hr = StringCchPrintf(lpBuf, 
              LocalSize(lpBuf) / sizeof(TCHAR),
              TEXT("ExitCode %d"),
              dwRes); //2147754005 when user select Cancel (0x80042015)
       CHR(hr);
 
       hr = LogToFile(lpBuf, g_pszFilename);
       CHR(hr);      
 
       //success
       hr = S_OK;
 
Exit:
       DELETE_STR(lpBuf);
 
       return 0;
}

 

Where the helper functions are:

// **************************************************************************
// Function Name: GetProcessHandleByName
HRESULT GetProcessHandleByName (LPCTSTR pszProcessName, LPHANDLE phProcessHandle)
{
       HRESULT hr = E_FAIL;
 
       if (pszProcessName == NULL)
              goto Exit;
 
       HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
       if (hSnapshot == INVALID_HANDLE_VALUE)
              goto Exit;
 
       *phProcessHandle = NULL;
       PROCESSENTRY32 pe;
       pe.dwSize = sizeof(pe);
 
       if (Process32First(hSnapshot, &pe))
       {
              do {
                     //log Exe name
                     hr = LogToFile(pe.szExeFile, g_pszFilename);
                     CHR(hr);
                     hr = LogToFile(TEXT("\r\n"), g_pszFilename);
                     CHR(hr);
                     
                     //compare current Exe name with passed Process Name
                     if (lstrcmpi(pszProcessName, pe.szExeFile) == 0)
                     {
                           //get the handle of the Exe name in case we reached the Exe we were looking for
                           *phProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
 
                           CloseHandle(hSnapshot);
                           return TRUE;
                     }
              } while (Process32Next(hSnapshot, &pe));
       }
 
       //Success
       hr = S_OK;
 
Exit:
       if (NULL != hSnapshot)
              //UPDATE: thanks Vino!
              //Contrarily to desktop Win32, don't invoke CloseHandle() to close the snapshot call.
              //Desktop (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx): 
              //       "[...] To destroy the snapshot, use the CloseHandle function.".
              //Windows CE\Mobile (http://msdn.microsoft.com/en-us/library/aa911386.aspx): 
              //       "[...] To close a snapshot, call the CloseToolhelp32Snapshot function."
              CloseToolhelp32Snapshot(hSnapshot);
 
       return hr;
}
 
 
// **************************************************************************
// Function Name: LogToFile 
HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename)
{
       HRESULT hr = E_FAIL;
       
       //Open the handle to the file (and create it if it doesn't exist
       HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
       if (INVALID_HANDLE_VALUE == hFile)
              goto Exit;
 
       //Set the pointer at the end so that we can append szLog
       DWORD dwFilePointer = SetFilePointer(hFile, 0, NULL, FILE_END);
       if (0xFFFFFFFF == dwFilePointer)
              goto Exit;
 
       //Write to the file
       DWORD dwBytesWritten = 0;
       BOOL bWriteFileRet = WriteFile(hFile, szLog, wcslen(szLog) * 2, &dwBytesWritten, NULL);
       if (!bWriteFileRet)
              goto Exit;
 
       //Flush the buffer
       BOOL bFlushFileBuffersRet = FlushFileBuffers(hFile);
       if (!bFlushFileBuffersRet)
              goto Exit;
 
       //Success
       hr = S_OK;
 
Exit:
       if (NULL != hFile)
              CloseHandle(hFile);
 
       return hr;
}

 

Hope this can help someone that absolutely has to distinguish if the application needs to be uninstalled or upgraded… but maybe the code above can find other meaningful usage! smile_nerd

 

Cheers,

~raffaele

Comments (14)
  1. g_govi says:

    Very interesting post. I have had a similar problem and maybe it’s interesting to know that OpenProcess (you use this API in GetProcessHandleByName) could fail and return NULL on Smartphone with two-tier Lock or Prompt security policy. I was calling it with SYNCHRONIZE instead of PROCESS_ALL_ACCESS, but it should not make any difference, since these flags are not supported in Windows CE 5.x.

    My solution was to cast the process id pe.th32ProcessID to a handle , since process ID and process handle are the same in Window CE (I’m not sure it’s documented, but it is true, at least up to CE 5):

    *phProcessHandle = (HANDLE)pe.th32ProcessID;

    This seems to work fine with the most common Smartphone security policies.

    Giuseppe

  2. Vino says:

    In the sample code, in the function GetProcessHandleByName(), shouldn’t

    CloseHandle(hSnapshot);

    actually be

    CloseToolhelp32Snapshot(hSnapshot);

    -Vino

  3. Thanks for commenting!

    And thanks for the hint… I was used to the DESKTOP-way, where CloseHandle is fine: see http://msdn.microsoft.com/en-us/library/ms686701(VS.85).aspx, for example, or even in the CreateToolhelp32Snapshot doc (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx) it’s reported "[…] To destroy the snapshot, use the CloseHandle function.".

    But you’re totally correct: on Windows CE the story is different… see the doc page for CreateToolhelp32Snapshot (http://msdn.microsoft.com/en-us/library/aa911386.aspx) "[…] To close a snapshot, call the CloseToolhelp32Snapshot function. Do not call the CloseHandle function to close the snapshot call. That generates a memory leak. "

    So thanks again and please continue validating my code!! 🙂

    ~raffaele

  4. Vino says:

    Apart from the code, I was more interested in getting rid of the message "“The previous version of… Select Ok to continue or cancel to quit”".

    We have an app packaged as a cab file. This cab has been created using the /nouninstall flag. This means the app will not appear in the ‘Remove Applications’ section. Also, we have our own uninstall logic built into the app. Also, we do not write into the registry whatsoever. Further, if we uninstall and the re-install the app, everything works fine with no prompts from wceload. This has worked fine with WM 6.0

    Come WM 6.1, using the same cab exhibits different behavior. After uninstalling the app, and then installing it again, we get the above message. I select OK. Moving on, another prompt comes up saying, ‘ The app cannot be removed. Proceed?’. Select Yes, and the cab extracts successfully. But at the end of it, yet another prompt which says, ‘The app.cab was not installed successfully.’ All this prompts and our customers are very unhappy.

    My question is there anyway I can get rid of the prompts?

    I searched the web and found http://social.msdn.microsoft.com/Forums/en-US/vssmartdevicesvbcs/thread/835784af-def1-4928-af6f-e7d8d326aa5c

    But it did not help at all. I guess the issue is still open.

    Any help/advice/suggestion is appreciated.

  5. hmm… I would say that your query deserves some tests and researches. Why not opening a call towards Microsoft Technical Support? If you’re located in EMEA, we may end up talking each other… 🙂 Remember: "get what you paid for"!! (http://blogs.msdn.com/raffael/archive/2008/05/14/get-what-you-paid-for.aspx)

  6. Victor Ronin says:

    There is one interesting workaround for this problem. It worked for WM 5.0-6.0, but it doesn’t work for 6.1 and actually I am looking a way of fixing it.

    The idea is to give different names to your application in the .inf file

    Something like:

    Cab N1:

    AppName = "FooBar 1.0"

    Cab N2 (for upgrade)

    AppName = "FooBar 1.1"

    Now, you can install/uninstall.

    In order to determine upgrade case, you can check in Setup.dll in Install_Init that there is registry key HKLM\Software\App\FooBar <version>

    And actually in this case, you can do REAL upgrade with replacing some files, instead of uninstallation + installation.

    The problem is that now you need programmatically cleanup some registry and files to remove old entry from "Remove programs" menu.

    It was enough before 6.1 to remove registry entries

    HKLM\Security\Apps\AppName

    HKLM\Software\Apps\AppName

    and director

    windowsappmgrAppName

    However, WM 6.1 saves app name at some other location too. And I can’t figure out where it is.

  7. Hi Victor, I would say you were using a unsupported way to remove entries under the "Remove Programs" list (removing registry keys), contrarily to using the UnInstall CSP, for example.

    Anyway, you may find interesting a freeware tool from SK Tools called "ssnap", which takes a snap-shot of the state of PPC that is useful for monitoring of changes on PPC, e.g. those made during installation of programs (http://www.s-k-tools.com/index.html?m_util.html). I’ve never used it but can be useful if you want to pursue with this approach…

    HTH, bye!

    ~raffaele

  8. Vino says:

    I used the MemMaid tool and I found out that after an installation, the application entry is made into the mxip_swmgmt.vol.

    Is there any way to get rid of this entry especially considering that the cab was created using the /nouninstall flag ? Here is the relevant post with what I have tried – http://forum.xda-developers.com/showthread.php?p=3769269

  9. Kit_21 says:

    Thanks for the Post!

    I implemented your solution and it seems to be working fine for most part. Only thing is that during an "upgrade", it takes a really really long time to actually install the app after clicking on "OK".

    I put in alerts and it looks like it takes a long time to move from uninstall_init to uninstall_exit (even if the uninstall methods do not do anything).

    My first hunch was that it takes this long since multiple wceload monitor processes are getting spawned after selecting "OK", so I tried making the monitor a single instance. That did not help reduce the time and for some reason the "Cancel" part stopped working as expected!:-(

    Any idea on how to speed up the install? It really feels like the install is hung (and it doesn’t show the wait cursor either:-( )

    Also, in case of an SD card, if the user decides to go ahead with the upgrade (i.e. clicks "OK") and then bails out on the screen which asks whether to install the app to Main Memory or the SD Card, the application is already uninstalled at this point. Is there a way to handle this case?

    Thanks again!

  10. redsolo says:

    Thank you very much for providing a flow on how the CAB installation process works. I had found out before, but that was by trial and error, so this is a great post that I wished I had read 6 months ago. 🙂

  11. Alex says:

    hmm, am I missing something or API has been changed since, but…

    SETUP_API codeINSTALL_INIT

    Install_Init(

       HWND        hwndParent,

       BOOL        firstCall,     // is this the first time this function is being called?

       BOOL        previouslyInstalled,

       LPCTSTR     installDir

    )

    … has "BOOL previouslyInstalled" parameter

  12. Stefano says:

    Hello,

    I found this post very helpful, as I need to do the same exact thing that is described here on a Windows CE 5 Device.

    The steps described during an update confirms what was scaring me on doing an update without bothering on what happens if something goes bad during the installation.

    In fact, what can happen is that the update starts, first uninstalls the old app and then starts the installation of the new one.

    If something goes bad at this point, what can happen is that the device will be without the app.

    So the idea was that in the setup.dll at Install Init, I start to backup all the files and reg settings that the new cab is going to modify, but to do so I want to discover the list of operations at runtime.

    I found that in the CAB there is a _setup.xml file that describes all the operations that the CAB itself is going to do during the installation.

    So I was thinking of creating a setup.dll that loads that _setup.xml file when the Installation starts and discovers at runtime which files and reg settings the cab will modify, stores those files in some persistent memory and eventually it restores those files if something bad happens during the installation.

    The point on doing this is that I can't find any idea on how to get the _setup.xml file itself and its content that is contained in the cab.

    Any info on that?

    Thanks

    Stefano

Comments are closed.

Skip to main content