Scott Hanselman had the idea that him and I should get together and put together all the best practices for writing installers, specifically for .NET applications. Clearly this is a good idea, although it’s one where all the information is difficult to organize since it’s never really come together at any single point in time (at least, for me!). So, I will periodically post things that I’ve had to deal with in my installer, and eventually we’ll get around to organizing it all. Hopefully πŸ™‚

Today’s post will cover a difficult to diagnose problem you may run across if you are installing side-by-side assemblies ("SxS") in Windows Vista. Note that in this case "assembly" doesn’t refer to a .NET assembly, but rather a "native" assembly installed into the side-by-side repository. You can poke around in it by going to C:\windows\winsxs. Even if you aren’t explicitly installing SxS assemblies, something else you depend on may be.

Anyway, Paint.NET version 3.10 incorporated Dean Ashton‘s DDS file type plugin, which makes use of Simon Brown‘s "Squish" library. The latter is written in C++ and sits in three DLL’s: one each for x86, x86 with SSE2, and x64. I added some multithreaded optimizations to the Squish code which then pulled in a dependency on OpenMP — something that had to be installed into the SxS repository. Visual Studio automatically figured out what to add to my MSI in order to get things installed right, so that part was easy enough.

Most applications out there won’t need to install OpenMP, but they quite often need to install things like the Visual C++ Runtime DLL’s. And if you’re installing the .NET Framework, then that installer has things it puts into the SxS cache as well.

Some people started reporting some problems when installing. They were all able to give a screenshot that showed a wonderfully cryptic error message:

"An error occurred during the installation of assembly ‘Microsoft.VC90.OpenMP,version="9.0.30729.1",publicKeyToken="1fc8b3b9a1e18e3b",processorArchitecture="x86",type="win32"’. Please refer to Help and Support for more information."

(Side bar rant: Why do these errors always say to refer to Help and Support? It never has any useful information!)

This then caused the generic 1603 "setup failed" error. They were always running Windows Vista, and I had no idea what was going on here. Fortunately a member of the forum, "wolf5", managed to find the solution, although it was several months after the problem was originally discovered:

I found a solution that might be the fix:

Go check in windows services that the service named "Windows Modules Installer" is up and running. Mine was disabled. Enabling it removed the VC90 error. ( i also had problems with windows update because of it and a few other VC90 installations ive tried (vcredist_x86.exe for vs.net 2008).

I don’t know why the service was set to Disabled, but my guess is that some "tweak" program was used or they followed the advice of one of those "optimization" guides. In any case, it doesn’t matter – it’s their computer and their business. "Windows Modules Installer" is better known by its EXE’s name: TrustedInstaller, and if it’s disabled then even things like Windows Update will have trouble working right.

Why can’t we be proactive about this? Anything I can do to easily improve the success rate of my installer is something I should probably do. My setup wizard is written in C#, but it is launched from a small EXE written in C called SetupShim who’s job is to check for major dependencies like Windows Installer and the .NET Framework. If TrustedInstaller is disabled, then those installers will fail as well, and so that’s ultimately where I needed to place the fix.

Fortunately this is very easy to do. All you need to do is call into the SCM (Service Control Manager), query the configuration for Trusted Installer, and then set it to Manual if it’s in the Disabled state.

Here’s the C code for ensuring that TrustedInstaller is running before you launch into your install flow, written in good old fashioned C. It requires administrator privilege to succeed.


hr = EnsureServiceIsNotDisabled(L"TrustedInstaller");

HRESULT EnsureServiceIsNotDisabled(const WCHAR* szServiceName)
{
    HRESULT hr = S_OK;
    DWORD dwError = ERROR_SUCCESS;
    BOOL bResult = TRUE;

    // Open SCM
    SC_HANDLE hSCManager = NULL;
    if (SUCCEEDED(hr))
    {
        hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);

        if (NULL == hSCManager)
        {
            dwError = GetLastError();
            hr = HRESULT_FROM_WIN32(dwError);
        }
    }

    // Open service
    SC_HANDLE hService = NULL;
    if (SUCCEEDED(hr))
    {
        hService = OpenServiceW(hSCManager, szServiceName, SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);

        if (NULL == hService)
        {
            dwError = GetLastError();
            hr = HRESULT_FROM_WIN32(dwError);
        }
    }

    // Query the service’s configuration
    BYTE rgbQueryServiceConfig[8000];
    ZeroMemory(rgbQueryServiceConfig, sizeof(rgbQueryServiceConfig));
    QUERY_SERVICE_CONFIG *pQueryServiceConfig = (QUERY_SERVICE_CONFIG *)rgbQueryServiceConfig;

    if (SUCCEEDED(hr))
    {
        DWORD dwCbBytesNeeded = 0;
        bResult = QueryServiceConfigW(hService, pQueryServiceConfig, sizeof(rgbQueryServiceConfig), &dwCbBytesNeeded);

        if (!bResult)
        {
            dwError = GetLastError();
            hr = HRESULT_FROM_WIN32(dwError);
        }
    }

    // If the configuration is Disabled, then set it to be Manual
    if (SUCCEEDED(hr) && SERVICE_DISABLED == pQueryServiceConfig->dwStartType)
    {
        bResult = ChangeServiceConfigW(
            hService,
            SERVICE_NO_CHANGE,
            SERVICE_DEMAND_START, // "Manual"
            SERVICE_NO_CHANGE,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL);

        if (!bResult)
        {
            dwError = GetLastError();
            hr = HRESULT_FROM_WIN32(dwError);
        }
    }

    // Clean up
    if (NULL != hService)
    {
        CloseServiceHandle(hService);
        hService = NULL;
    }

    if (NULL != hSCManager)
    {
        CloseServiceHandle(hSCManager);
        hSCManager = NULL;
    }

    return hr;
}

And that’s it. You’ve now increased the success rate of your installer by a fraction of 1%. (It’s always that fraction that’s important though!)

Advertisements