Damian Mehers' Blog Xamarin from Geneva, Switzerland.

21Feb/087

Configuring HTTP Namespace reservations on Vista using WIX

WIX is an XML based installer generator, from Microsoft, now hosted in Sourceforge.

As part of an installation for a Vista based product I am creating, I needed to register an http namespace.  This is required in order to listen for incoming connections.  You can do this from the command line as described here: http://msdn2.microsoft.com/en-us/library/ms733768.aspx

I spent a day or so researching, and coding a Custom Action, which worked.  As I was in the final stages of integrating it into my WIX wxs file, I discovered a much simpler way of doing it, based on the way Microsoft Media Center applications are registered in the template code created in Visual Studio 2008 by the Windows Media Center SDK.

The first thing I did was to define a property which points to the fully qualified Netsh.exe tool:

<!-- This property uses an AppSearch to attempt to  -->
<!-- locate the file %windir\Netsh.exe              -->
<Property Id="NETSH" Secure="yes">
  <DirectorySearch Id="Netshdir" Path="[SystemFolder]">
    <FileSearch Id="Netshexe" Name="Netsh.exe" />
  </DirectorySearch>
</Property>

Then I defined a couple of properties that describe the Netsh commands to register and unregister the HTTP namespace.  You'd need to replace the port, 'Test' and possibly specify something else instead of 'Users':

<Property Id="NamespaceAddCmd"
          Value="http add urlacl url=http://+:123/Test/ user=Users"/>
<Property Id="NamespaceDelCmd"
          Value="http del urlacl url=http://+:123/Test/"/>

Next I define the custom actions used when installing and uninstalling. These are based on those used for registering MCE apps. The '..._Cmd' actions set properties which are then used by the actions they target. This is because deferred actions can not access normal properties, only the CustomActionData property.  This is set by the '..._Cmd' actions when they have a Property which names the custom action whose CustomActionData they target:

<CustomAction Id="CA_RegisterNamespace_Unregister_Install_Cmd"
              Property="CA_RegisterNamespace_Unregister_Install"
              Value="&quot;[NETSH]&quot; [NamespaceDelCmd]"/>
<CustomAction Id="CA_RegisterNamespace_Unregister_Uninstall_Cmd"
              Property="CA_RegisterNamespace_Unregister_Uninstall"
              Value="&quot;[NETSH]&quot; [NamespaceDelCmd]"/>
<CustomAction Id="CA_RegisterNamespace_Register_Cmd"
              Property="CA_RegisterNamespace_Register"
              Value="&quot;[NETSH]&quot; [NamespaceAddCmd]"/>
<CustomAction Id="CA_RegisterNamespace_Rollback_Cmd"
              Property="CA_RegisterNamespace_Rollback"
              Value="&quot;[NETSH]&quot; [NamespaceDelCmd]"/>

<CustomAction Id="CA_RegisterNamespace_Unregister_Install"
              BinaryKey="WixCA" DllEntry="CAQuietExec"
              Execute="deferred" Return="ignore" Impersonate="no"/>
<CustomAction Id="CA_RegisterNamespace_Unregister_Uninstall"
              BinaryKey="WixCA" DllEntry="CAQuietExec"
              Execute="deferred" Return="ignore" Impersonate="no"/>
<CustomAction Id="CA_RegisterNamespace_Register"
              BinaryKey="WixCA" DllEntry="CAQuietExec"
              Execute="deferred" Return="check" Impersonate="no"/>
<CustomAction Id="CA_RegisterNamespace_Rollback"
              BinaryKey="WixCA" DllEntry="CAQuietExec"
              Execute="rollback" Return="ignore" Impersonate="no"/>

Finally, these are the custom actions used in the InstallExecuteSequence:

<Custom Action="CA_RegisterNamespace_Unregister_Install_Cmd"
        After="CostFinalize">
  <![CDATA[
NOT REMOVE]]>
</
Custom>
<
Custom Action="CA_RegisterNamespace_Unregister_Uninstall_Cmd"
        After="CA_RegisterNamespace_Unregister_Install_Cmd">
  <![CDATA[
REMOVE AND ($Registration.xml = 2)]]>
</
Custom>
<
Custom Action="CA_RegisterNamespace_Register_Cmd"
        After="CA_RegisterNamespace_Unregister_Uninstall_Cmd">
  <![CDATA[
NOT REMOVE]]>
</
Custom>
<
Custom Action="CA_RegisterNamespace_Rollback_Cmd"
        After="CA_RegisterNamespace_Register_Cmd">
  <![CDATA[
NOT REMOVE]]>
</
Custom>

<Custom Action="CA_RegisterNamespace_Unregister_Uninstall"
        Before="RemoveFiles">
  <![CDATA[
REMOVE AND ($Registration.xml = 2)]]>
</
Custom>

<Custom Action="CA_RegisterNamespace_Rollback"
        After="InstallFiles">
  <![CDATA[
NOT REMOVE]]>
</
Custom>
<
Custom Action="CA_RegisterNamespace_Unregister_Install"
        After="CA_RegisterNamespace_Rollback">
  <![CDATA[
NOT REMOVE]]>
</
Custom>
<
Custom Action="CA_RegisterNamespace_Register"
        After="CA_RegisterNamespace_Unregister_Install">
  <![CDATA[
NOT REMOVE]]>
</
Custom>

Just in case it is of use to anyone, here is the custom action I coded up, although it is not needed with the above code:

#include <windows.h>
#include <msi.h>
#include <msiquery.h>
#include <Http.h>
#include <Sddl.h>

#pragma comment(linker, "/EXPORT:ReserveNamespace=_ReserveNamespace@4")
#pragma comment(linker, "/EXPORT:ReleaseNamespace=_ReleaseNamespace@4")

#define BUF_LEN 256

#define LOG true

UINT ReserveOrReleaseNamespace (MSIHANDLE hInstall, bool reserve) {
    HWND parentWindow = GetForegroundWindow();

    if(LOG) MessageBox(parentWindow, L"Called", L"ReserveOrReleaseNamespace", MB_OK);

    bool scheduled = MsiGetMode(hInstall, MSIRUNMODE_SCHEDULED);
    bool rollback = MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);
    bool commit = MsiGetMode(hInstall, MSIRUNMODE_COMMIT);

    if(LOG) MessageBox(parentWindow, scheduled ? L"True" : L"False",
        L"ReserveOrReleaseNamespace is MSIRUNMODE_SCHEDULED", MB_OK);
    if(LOG) MessageBox(parentWindow, rollback ? L"True" : L"False",
        L"ReserveOrReleaseNamespace is MSIRUNMODE_ROLLBACK", MB_OK);
    if(LOG) MessageBox(parentWindow, commit ? L"True" : L"False",
        L"ReserveOrReleaseNamespace is MSIRUNMODE_COMMIT", MB_OK);

    // Initialize HTTP
    HTTPAPI_VERSION httpApiVersion = HTTPAPI_VERSION_1;
    HRESULT error = HttpInitialize(httpApiVersion, HTTP_INITIALIZE_CONFIG, 0);
    if(error != S_OK) return error;

    // Get the custom action data
    // (only thing we can pass to deferred custom actions)
    wchar_t customActionData[BUF_LEN] = L"";
    DWORD customActionDataLen = BUF_LEN;

    MsiGetProperty (hInstall, L"CustomActionData", customActionData,
        &customActionDataLen);
    if(LOG) MessageBox(parentWindow, customActionData,
        L"ReserveOrReleaseNamespace CustomActionData is", MB_OK);

    wchar_t* semicolon = wcschr(customActionData, ';');
    if(!semicolon) {
        MessageBox(parentWindow,
            L"MceFM Namespace Reservation failed because the CustomActionData was incorrect",
            L"MceFM Namespace Reservation", MB_OK);
        return E_FAIL;
    }

    // Extract the user and URL
    wchar_t* user = customActionData;
    wchar_t* url = semicolon + 1;

    *semicolon = '';

    if(LOG) MessageBox(parentWindow, url, L"ReserveOrReleaseNamespace NR_URL is", MB_OK);
    if(LOG) MessageBox(parentWindow, user, L"ReserveOrReleaseNamespace NR_USER is", MB_OK);

    // Convert the user to a SID
    PSID pSID;
    DWORD sidSize = SECURITY_MAX_SID_SIZE;
    pSID = LocalAlloc(LMEM_FIXED, sidSize);
    WCHAR domainName[BUF_LEN];
    DWORD domainNameLen = BUF_LEN;
    SID_NAME_USE sidNameUse;
    BOOL worked = LookupAccountName(NULL, user, pSID, &sidSize, domainName,
        &domainNameLen, &sidNameUse);
    if(!worked) {
        error = GetLastError();
        if(LOG) MessageBox(parentWindow, L"LookupAccountName failed",
            L"ReserveOrReleaseNamespace", MB_OK);
        return error;
    }

    // Convert the SID to a string
    LPTSTR stringSid;
    worked = ConvertSidToStringSid(pSID, &stringSid);
    if(!worked) {
        error = GetLastError();
        if(LOG) MessageBox(parentWindow, L"ConvertSidToStringSid failed",
            L"ReserveOrReleaseNamespace", MB_OK);
        LocalFree(pSID);
        return error;
    }


    // Build a security descriptor in the required format
    WCHAR securityDescriptor[BUF_LEN];
    wsprintf(securityDescriptor, L"D:(A;;GX;;;%s)", stringSid);

    // Set up the config info (url and security descriptor)
    HTTP_SERVICE_CONFIG_URLACL_SET configInfo;
    configInfo.KeyDesc.pUrlPrefix = url;
    configInfo.ParamDesc.pStringSecurityDescriptor =  securityDescriptor;

    if(reserve) {
        // Add the namespace reservation
        error = HttpSetServiceConfiguration(0, HttpServiceConfigUrlAclInfo,
            &configInfo, sizeof(HTTP_SERVICE_CONFIG_URLACL_SET), NULL);
        if(error != S_OK) {
            if(LOG) MessageBox(parentWindow, L"HttpSetServiceConfiguration failed",
                L"ReserveOrReleaseNamespace", MB_OK);
        }
    } else {
        // Remove the namespace reservation
        error = HttpDeleteServiceConfiguration(0,  HttpServiceConfigUrlAclInfo,
            &configInfo, sizeof(HTTP_SERVICE_CONFIG_URLACL_SET), NULL);
        if(error != S_OK) {
            if(LOG) MessageBox(parentWindow, L"HttpDeleteServiceConfiguration failed",
                L"ReserveOrReleaseNamespace", MB_OK);
        }
    }
    LocalFree(pSID);
    LocalFree(stringSid);

    return error;
}



extern "C" UINT __stdcall ReserveNamespace (MSIHANDLE hInstall) {
    return ReserveOrReleaseNamespace(hInstall, true);
}

extern "C" UINT __stdcall ReleaseNamespace (MSIHANDLE hInstall) {
    return ReserveOrReleaseNamespace(hInstall, false);
}

Filed under: Uncategorized 7 Comments