The App Compat Guy

Chris Jackson's Semantic Consonance

July, 2009

  • The App Compat Guy

    How to Work Around Program Compatibility Assistant (PCA) JobObjects Interfering with Your JobObjects

    • 0 Comments

    I had a question come up in response to another post:

    I also have another side effect from this change.  I have a process that tracks processes via JobObjects.  With Vista I had to add a Manifest file to all my processes so PCA would not track them and I could continue to track with my custom tool.  It appears I need to modify the manifests again with a compatibility tag for Windows7.  The problem is I will have to do this for every new OS, or find an alternative to jobObjects.

    So, I figured I’d dive in to PCA. Because, believe it or not, it’s even possible for an application compatibility technology to have an application compatibility impact, as this customer discovered! (I’ve said it before – this stuff is really hard.)

    You see, the underlying problem is that application compatibility technologies in Windows have to follow the same rules as any other application running on Windows. It’s kind of like The Matrix. We have Agents on the system, trying to keep things working. They have to obey the rules of the system, though they can stretch some of the rules and have a certain mastery of them that many applications wouldn’t have (access to the source and the developers). It’s precisely because we are bound by the same rules that we can land ourselves in trouble.

    The Program Compatibility Assistant (PCA) is one such system, and in this example the rules it’s following are the rules of Job objects. Let’s work our way through this system and find out how it hurt our friend and what options he has.

    We’ll be making most of our way through the system using Process Explorer to discover what’s going on, so if you have that handy, you can follow along. (I have it as an auto-start program, personally.)

    First, we want to elevate Process Explorer if it isn’t running elevated already so we can see some details of system processes.

    If you do a handle search for PCA_ it will take you to an instance of svchost.exe, and if you mouse over the exe you’ll see that it is hosting the Program Compatibility Assistance Service [PcaSvc]. In the list of handles for that service, you’ll see a number of handles to Job objects, such as:

    \BaseNamedObjects\PCA_{EE416CB2-DD81-4EE5-A3FD-B36EA4503301}

    PCA does its monitoring through jobs, and you can see them here. What’s more, you can see them in the process list below. By default, Process Explorer will highlight processes which are enclosed in jobs with a brown color. Double click on one, and you’ll be able to see the job information:

    image

    OK, so PCA is looking after processes by adding them to a job object. What for? Well, I haven’t reversed this completely, but my (educated) guess would be so that it could leverage JOB_OBJECT_MSG_EXIT_PROCESS to perform its work after the process exits. So far, so good.

    But wait … there’s one catch. From the SDK:

    “A process can be associated only with a single job.”

    Oh, and then there is this:

    “After you associate a process with a job, the association cannot be broken.”

    Ah. Well, that kind of puts some sand in the ointment. Anything launched using ShellExecute(Ex) that doesn’t meet the PCA exclusion criteria is going to be assigned to a PCA job, and that means you can’t assign them to another job. Effectively, we just took back part of the platform from you. (Caveat – if you called ShellExecute with SEE_MASK_FLAG_NO_UI, then you would not have PCA applied.)

    OK, so what can our friend who wants to use job objects do now?

    Well, one option is to manifest everything that they want to track with a Windows 7 manifest. (Yes, there is a bitter irony that, in order to support an application compatibility technology designed to keep things unchanged working on current versions of Windows you have to change some things.) Of course, that also means that, if we changed nothing about the implementation, you’d have to do that again with every new version of Windows if you want to use job objects – not cool. So, what else can you do?

    If you can control the creation of processes, then you can wiggle right out of PCA.

    In addition to the ShellExecute escape hatch mentioned above, CreateProcess lets you specifically opt out of inheriting a job, assuming that the job allows you to (which the PCA job does). Let’s see.

    First, I can create an app that serves as my launcher. It will get the PCA job applied to it, as a non-Windows 7-manifested application. But I’m not worried about it – I’m worried about the child processes that my job is going to be watching over. So, let’s create a job object:

    HANDLE myJob;
    myJob = CreateJobObject(NULL, TEXT("JobsAndPca"));

    I’m going to elide the CreateProcess setup and cleanup work, and instead just dive right in to creating a process and assigning it to a job. Without PCA monitoring, here’s what I’d have:

    if (CreateProcess(szPath, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
        if (AssignProcessToJobObject(myJob, pi.hProcess)) {
            // Success! (I don’t get here)
        } else {
            // Failure – couldn’t add to job (I probably get here)
        }
    } else {
        // Failure – couldn’t create the process (I probably don’t get here)
    }

    So, I’ve just reproduced the failure – I can’t get in there with a job. However, if I create my process differently, I can:

    if (CreateProcess(szPath, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi)) {
        if (AssignProcessToJobObject(myJob, pi.hProcess)) {
            // Success! (I probably get here)
        } else {
            // Failure – couldn’t add to job (I probably don’t get here)
        }
    } else {
        // Failure – couldn’t create process (I probably don’t get here)
    }

    So, our final answer is:

    If you are the creator of the process you want to monitor, then you can escape the PCA job. Otherwise, you’re going to have to re-manifest for Windows 7 compatibility.

    We’re already thinking ahead to future versions of Windows, and having just told you to manifest your application for Windows 7, as the app compat team, we don’t want everything to automatically risk breaking again in future versions of Windows because our app compat stuff gets in the way, nor do we want to have to make you change your manifest every time (because, well, that kind of defeats the purpose of app compat if none of your apps are compatible without modification). How do we go about always doing right by everybody? Well, that’s a hard problem. And one that you deserve a great answer for. If the current rules require us to get in your way, then we may very well just have to change the rules…

  • The App Compat Guy

    Unraveling the Mysteries of MSI Compatibility Modes in Windows 7

    • 5 Comments

    If you right click on an executable in Windows Vista, you’ll find a Compatibility Tab, where you can set compatibility modes (layers) for that executable file. These compatibility layers are collections of shims and loader flags (depending on whether they affect the loader or if they have to intercept Windows API calls throughout the lifetime of the process). However, you’ll find that you can’t do the same thing for MSI files on Windows Vista.

    With Windows 7, however, you’ll find that you *are* able to apply a compatibility mode. Is this using the same mechanisms? How does it work?

    To illustrate, I’m going to take the installer for Bao Nguyen’s excellent Switcher application, hack it up to break it, and then use a compatibility mode to fix it.

    Before we begin, make sure you have Orca installed from the Platform SDK…

    So, starting with the original installer, right click and select Edit with Orca. From the list of tables, select LaunchCondition. There are 2: one looking for the .NET Framework 2.0, and the other looking for Windows Vista or higher. (Technically, the .NET Framework check is redundant today, but in the future, that may not be true.) Take the VersionNT check, which is correctly written as a >= check, and transform it into a bad check by removing the > character. Now, it won’t install on Windows 7. (Wow – it’s strange to be breaking something instead of fixing something.) Save the modified MSI.

    Before we fix it, let’s turn on logging so we can get a better view into what’s happening. Launch the group policy editor (Windows-r – gpedit.msc) and navigate to Local Computer Policy \ Computer Configuration \ Administrative Templates \ Windows Components \ Windows Installer. In the right hand pane, double click the Logging policy. Then, change the setting to Enabled, and modify the policy accordingly. I set mine to iwearucmpvox – gets it all (why not?). Click on OK, and now you should get more data from Windows Installer.

    Finally, let’s launch the application. It’s going to immediately gripe to you about the version number. If we look at the MSI logs that are generated (you’ll find them at %temp% – I sort by time to see the most recently generated MSI*.log file), you’ll find the following in the logs:

    MSI (c) (8C:1C) [13:03:32:807]: Doing action: LaunchConditions
    Action 13:03:32: LaunchConditions. Evaluating launch conditions
    Action start 13:03:32: LaunchConditions.
    This application requires Windows Vista.

    Which we kind of expected, since we intentionally hacked up the MSI to fail like that. (Hey – at least we’re as good at breaking things as we are at fixing them, no? :-) )

    Now let’s fix it up. Right click on the MSI, select properties, and then go to the Compatibility tab (which, in our first interesting observation, is actually present). You’ll note that only one option is enabled: running in compatibility mode for the previous version of Windows (the drop down doesn’t offer any more). And, being Windows 7, the previous version is Windows Vista. Right? Let’s find out. Select the checkbox (the dropdown contains only 1 option and is, therefore, superfluous). Now run the installer again, and – surprise! – it works again. Let’s check out what we find in the logs:

    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '601'. Its new value: '600'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.
    MSI (c) (E8:CC) [13:05:37:999]: APPCOMPAT: [DetectVersionLaunchCondition] Launch condition version successfully detected.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '600'. Its new value: '601'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '14'. Its new value: '0'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Adding SHIMFLAGS property. Its value is '512'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Adding SHIMVERSIONNT property. Its value is '600'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '601'. Its new value: '600'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Adding SHIMSERVICEPACKLEVEL property. Its value is '14'.
    MSI (c) (E8:CC) [13:05:37:999]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.

    Cool – so we have baked in APPCOMPAT to go through and change the version number to get to the previous version of Windows. Good, right? Well, good if your application is checking for Windows Vista. But the challenge that kept many people from deploying Windows Vista is compatibility with Windows XP – what happens when installers target Windows XP?

    Well, it kind of depends on whether Previous version of Windows means the previous version of Windows (Windows Vista) or a previous version of Windows (Windows XP, Windows 2000, etc.). How do we find out?

    Hack up that launch condition again!

    Go back into Orca, and change the VersionNT check to be = 501 (Windows XP). Save it, and try launching it again. (You’ll have to uninstall the old one first and then install again.) What happens?

    Yes – it works! That’s right, we’re kind of like that person you meet in a bar, ask their name, and they respond, “what do you want it to be?” We’ll try different versions until we get one that makes the launch condition succeed. You can see it in the logs:

    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '601'. Its new value: '600'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '14'. Its new value: '13'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '13'. Its new value: '12'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '12'. Its new value: '11'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '11'. Its new value: '10'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '10'. Its new value: '9'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '9'. Its new value: '8'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '8'. Its new value: '7'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '7'. Its new value: '6'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '6'. Its new value: '5'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '5'. Its new value: '4'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '4'. Its new value: '3'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '3'. Its new value: '2'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '2'. Its new value: '1'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '1'. Its new value: '0'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '600'. Its new value: '502'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '14'. Its new value: '13'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '13'. Its new value: '12'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '12'. Its new value: '11'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '11'. Its new value: '10'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '10'. Its new value: '9'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '9'. Its new value: '8'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '8'. Its new value: '7'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '7'. Its new value: '6'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '6'. Its new value: '5'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '5'. Its new value: '4'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '4'. Its new value: '3'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '3'. Its new value: '2'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '2'. Its new value: '1'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '1'. Its new value: '0'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '502'. Its new value: '501'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.
    MSI (c) (0C:F0) [13:50:10:634]: APPCOMPAT: [DetectVersionLaunchCondition] Launch condition version successfully detected.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '501'. Its new value: '601'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '14'. Its new value: '0'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Adding SHIMFLAGS property. Its value is '512'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Adding SHIMVERSIONNT property. Its value is '501'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying VersionNT property. Its current value is '601'. Its new value: '501'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Adding SHIMSERVICEPACKLEVEL property. Its value is '14'.
    MSI (c) (0C:F0) [13:50:10:634]: PROPERTY CHANGE: Modifying ServicePackLevel property. Its current value is '0'. Its new value: '14'.

    Sweetness. Not only can you pass version checks, we even do all of the work for you.

    You’ll also note that this is not a shim – it’s a modification of the properties (VersionNT and ServicePackLevel). This is baked right in to Windows Installer. You can still have shims affecting custom actions hosted by the msiexec process (and do – see http://blogs.msdn.com/cjacks/archive/2009/05/06/why-custom-actions-get-a-windows-vista-version-lie-on-windows-7.aspx).

    Now, this is more of a consumer-level fix, because consumers don’t use Orca and don’t use transforms. In the Enterprise, you can create MSI Transforms and fix it up without having to cycle through everything. But it’s still nice to be able to quickly fix something when you suspect it’s the problem. Happy fixing!

Page 1 of 1 (2 items)