Setup authors: Make sure "TrustedInstaller" is enabled

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!)

8 thoughts on “Setup authors: Make sure "TrustedInstaller" is enabled

  1. OJ says:

    That “helpful” error message that you demonstrated is a lot more helpful than the error message you get when you’re using Windows XP. The Vista version at least tells you about the assembly you’re missing, XP gives you a confusing error stating something about an error in the application configuration.

    I posted about this a while ago, and strangely enough it’s one of the most frequently hit posts on my site!

    Looks like lots of people are getting this part of their installs wrong. Thanks for posting the info ๐Ÿ™‚

  2. Doug says:

    I would suggest against silently modifying the user’s system configuration. In general, apps should never make global system changes without confirmation from the user. A simple OK/Cancel dialog box should be sufficient, i.e. “Paint.NET has detected that the Windows Modules Installer service has been disabled. This prevents Paint.NET from installing correctly. Do you want to re-enable the Windows Modules Installer service?”

  3. Rick Brewster says:

    Doug — In general that’s a good rule to follow. However, in this case it’s an integral system service that, when disabled, causes all sorts of trouble. The user already double clicked on the installer, and wants to install Paint.NET … why ask them a question they probably can’t answer?

    http://blogs.msdn.com/oldnewthing/archive/2004/04/26/120193.aspx

    Bear in mind that not every user keeps a diary of every change, tweak, or “optimization” they’ve made to their system (or that their 13 year old “computer genius” kid made!).

    If my mom were presented with the dialog box you proposed, she would have absolutely no idea what to do.

  4. Olivier says:

    What about enabling the service just to install, and then restore it to its initial status after?

  5. Rick Brewster says:

    Olivier — That’s definitely a reasonable request. However, I think that’d be over-engineering things. Like I said before, it’s an integral system service that shouldn’t be disabled in the first place. Setting it to ‘manual’ will have no performance impact. The fix is only necessary on a small percentage of systems.

    If someone is really set on having the service in the disabled state, they can always reset it after installing Paint.NET.

    Plus I’m just not comfortable having code that sets an important system service to be disabled. It’s like having code that deletes files, or especially like having code that recursively deletes a directory tree … you have to be VERY careful. What if I have a bug, or the boolean I’m using to keep track of this gets a bit flipped/corrupt for some reason, and I end up disabling the service on other random systems? It would just cause more problems that it was trying to fix.

    I’d rather have a small number of users mad at me for intentionally not re-disabling the service, than a large number of people confused and helpless and mad because I accidentally disabled it.

  6. harold says:

    I would side with the “Do you want to enable the Windows Modules Installer service?” dialog. Perhaps with “ok” and “abort” instead of yes/no to make it clear that refusing will abort the installer.

    Or at least warn that it has changed its status to Manual so they can disable it again if they want to..

    Regardless of it being an “integral system service” it’s still Their computer; silently changing settings is “potentially unwanted behaviour” and may cause virus scanners to warn about it – if the anti-guys find out about it. They’ve done stranger things in the past – I might actually agree with them on this one.

Comments are closed.