Provisioning the UnInstall Configuration Service Provider fails if invoked through CAB or CPF

Imagine you want to uninstall an application on a Windows Mobile device, and you want to do that through a CAB file. There are other means, yes, (see doc) however in this particular case a CAB was required because IT admins were using SCCM (System Center Configuration Manager) to handle mobile devices remotely and wanted a CAB. Ok, no problems: it's pure XML Provisioning! Nerd It's simply a matter of targeting the UnInstall CSP (Configuration Service Provider) by using for example the following XML and package it into a CAB through makecab.exe accordingly to the documentation (remember that .CAB and .CPF are the same, the only difference being with the outputted result: in the default Messaging account's inbox for .CPF and directly through a dialog for .CAB):

 <wap-provisioningdoc> 
   <characteristic type="UnInstall"> 
      <characteristic type="AppName"> 
         <parm name="uninstall" value="1"/>    
      </characteristic> 
   </characteristic> 
</wap-provisioningdoc>

The usual steps when creating a XML Provisioning-based approach involve testing the XML through RapiConfig: we did it and XML was fine (i.e. the application gets uninstalled). Then we packaged a CAB through makecab.exe as the doc dictates and run the CAB on the device: it returned a general installation failure message ("Installation of xx.cab was unsuccessful. "). When troubleshooting the problem, we tried by compressing\not compressing the CAB (makecab.exe /D COMPRESS=OFF), then we made sure the problem was not with the long name of the CAB file (this was a problem many years ago, but no more), then with the Security Configuration and even with the formatting (ANSI\UTF-8) of the _setup.xml included in the CAB...

At the end it turned out that the problem is on Windows Mobile Devs' desk for further analysis in order to be addressed in future releases: simply put, the UnInstall CSP doesn't work when invoked through a CAB or CPF and there's simply no workaround apart from choosing a different approach to uninstall the application. If having an executable was acceptable, then we could simply run the following native code:

 LPCWSTR g_wszUninstallAppNameXml =
    L"<wap-provisioningdoc> "
    L"   <characteristic type=\"UnInstall\"> "
    L"      <characteristic type=\"AppName\"> "
    L"         <parm name=\"uninstall\" value=\"1\"/> "
    L"      </characteristic> "
    L"   </characteristic>"
    L"</wap-provisioningdoc>";

HRESULT UninstallAppName()
{
    HRESULT hr         = E_FAIL;
    LPWSTR wszOutput   = NULL;

    // Process the XML.
    hr = DMProcessConfigXML(g_wszUninstallAppNameXml, CFGFLAG_PROCESS, &wszOutput);

    // The caller must delete the XML returned from DMProcessConfigXML.
    delete [] wszOutput;

    return hr;
}

 

Or, for a managed approach:

 string XmlUninst =  "<wap-provisioningdoc>\r\n" +
                    " <characteristic type=\"UnInstall\">\r\n" +
                    "  <characteristic type=\"AppName\">\r\n" +
                    "   <parm name=\"uninstall\" value=\"1\"/>\r\n" +
                    "  </characteristic>\r\n" +
                    " </characteristic>\r\n" +
                    "</wap-provisioningdoc>";

XmlDocument configDoc = new XmlDocument();
configDoc.LoadXml(XmlUninst);
ConfigurationManager.ProcessConfiguration(configDoc, false);

 

However, as I said, in this case a CAB was required.

We came up with a simple workaround: create a CAB (through a Smart Device CAB project) and include a SETUP.DLL in it, whose Install_Init event invokes the DMProcessConfigFile with the XML Provisioning targeting the UnInstall CSP. The code for such SETUP.DLL was the following -- it's the usual one with simply a customized Install_Init:

 HINSTANCE g_hinstModule;

LPCWSTR g_wszUninstallAppNameXml = 
    L"<wap-provisioningdoc> "
    L"   <characteristic type=\"UnInstall\"> "
    L"      <characteristic type=\"AppName\"> "
    L"         <parm name=\"uninstall\" value=\"1\"/> "
    L"      </characteristic> "
    L"   </characteristic>"
    L"</wap-provisioningdoc>";

BOOL APIENTRY DllMain(
    HANDLE hModule, 
    DWORD  ul_reason_for_call, 
    LPVOID lpReserved
    )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            g_hinstModule = (HINSTANCE)hModule;
            break;
    }
    return TRUE;
}


// **************************************************************************
// Function Name: Install_Init
//
// Arguments:
//    IN HWND hwndParent  handle to the parent window
//    IN BOOL fFirstCall  indicates that this is the first time this function is being called
//    IN BOOL fPreviouslyInstalled  indicates that the current application is already installed
//    IN LPCTSTR pszInstallDir  name of the user-selected install directory of the application
//
// Return Values:
//    codeINSTALL_INIT
//    returns install status
//
// Description:  
//    The Install_Init function is called before installation begins.
//    User will be prompted to confirm installation.
// **************************************************************************
SETUP_API codeINSTALL_INIT Install_Init(
    HWND        hwndParent,
    BOOL        fFirstCall,
    BOOL        fPreviouslyInstalled,
    LPCTSTR     pszInstallDir
    )
{
    HRESULT hr         = E_FAIL;
    LPWSTR wszOutput   = NULL;

    // Process the XML.
    hr = DMProcessConfigXML(g_wszUninstallAppNameXml, CFGFLAG_PROCESS, &wszOutput);
    CHR(hr);

Error:
    // The caller must delete the XML returned from DMProcessConfigXML.
    if (wszOutput != NULL)
    {
        delete [] wszOutput;
    }
    if (hr == S_OK)
    {
        return codeINSTALL_INIT_CONTINUE;
    } 
    else
    {
        return codeINSTALL_INIT_CANCEL;
    }
}


// **************************************************************************
// Function Name: Install_Exit
// 
// Purpose: processes the push message.
//
// Arguments:
//    IN HWND hwndParent        handle to the parent window
//    IN LPCTSTR pszInstallDir  name of the user-selected install directory of 
//                              the application
//
// Return Values:
//    returns install status: codeINSTALL_EXIT
// **************************************************************************
SETUP_API codeINSTALL_EXIT Install_Exit(
    HWND    hwndParent,
    LPCTSTR pszInstallDir,      
    WORD    cFailedDirs,
    WORD    cFailedFiles,
    WORD    cFailedRegKeys,
    WORD    cFailedRegVals,
    WORD    cFailedShortcuts
    )
{
    return codeINSTALL_EXIT_DONE;
}


// **************************************************************************
// Function Name: Uninstall_Init
//
// Arguments:
//    IN HWND hwndParent  handle to the parent window
//    IN LPCTSTR pszInstallDir  name of the user-selected install directory of the application
//
// Return Values:
//    returns uninstall status: codeUNINSTALL_INIT
//
// **************************************************************************
SETUP_API codeUNINSTALL_INIT Uninstall_Init(
    HWND        hwndParent,
    LPCTSTR     pszInstallDir
    )
{
   return codeUNINSTALL_INIT_CONTINUE;
}


// **************************************************************************
// Function Name: Uninstall_Exit
//
// Arguments:
//    IN HWND hwndParent  handle to the parent window
//
// Return Values:   
//    returns uninstall status: codeUNINSTALL_EXIT
//
// **************************************************************************
SETUP_API codeUNINSTALL_EXIT Uninstall_Exit(
    HWND    hwndParent
    )
{
    return codeUNINSTALL_EXIT_DONE;
}

 

 

Cheers,

~raffaele

Comments