The App Compat Guy

Chris Jackson's Semantic Consonance

March, 2008

  • The App Compat Guy

    Why Does It Take So Long to See the UAC Prompt Sometimes? (Diagnosing Slow UAC Prompts)

    • 8 Comments

    I wanted to elevate a response to one of the comments on an earlier post to the status of a full post, so it's hopefully a bit more discoverable.

    The author writes:

    "I would't mind dismissing Yet Another Popup, if it would have the decency to pop up already.  UAC takes for-freaking-ever to ask my permission to do something I just told the computer to do.  If it happened right away, it would be no biggie, but I frequently have to wait 20, 30, 40 seconds (sometimes way, way longer--about 30 minutes for a game download and install once) before the UAC prompt on the secure desktop.  This is why I want to turn the damn thing off--because of its horrible performance!  And it's all well and good to blame this on ill-behaved apps, but who owns UAC?  That's right, Windows.  I suspect for most users UAC is just another reason why Vista comes across as one of the clunkiest Windows releases ever."

    And yes, Jason, you have a very fair point - that user experience sucks, and I hate user experiences that suck! Let's discuss.

    First, we need to determine where exactly the problem occurs, because there are two possibilities. The first is that we're having trouble transitioning to the secure desktop. Given the current implementation, this is generally caused by limitations in the graphics card drivers to support this transition. If you end up staring at a black screen for a while, then this is likely the culprit. Unfortunately, there isn't much to do about this. One option is to get a new graphics card. (Easier said than done, right?) The other is to turn off switching to the secure desktop for elevation prompts, which has a couple of issues. First, it's somewhat less secure (a malicious application could disguise the dialog by painting something in front of it, and since the boundary of a window message is the desktop any potential loopholes could be exploited to auto-elevate - let's just say we did the secure desktop thing on purpose). Second, we disable this via group policy, but home SKUs don't have group policy editing included, which means you end up resorting to an obscure registry hack (also easier said than done, right?). So, I'm really kind of hoping you don't fall into this bucket.

    The far more common bucket would be the case where everyone would be impacted with a given exe - and there is something the developers could do about this (and you can too, if you throw a shim at it). So let's discuss this one, and we will continue to try to push the software ecosystem in this direction to resolve it through policy rather than technology.

    When we need to authorize a request for elevation, we look at the binary to see if it is signed. There is a difference in the UAC prompt if the application is signed - instead of being kind of orange and scary, it's greyish and more neutral. But the fact that it's signed means we have to verify the signature. And herein lies the problem.

    Clearly signatures are a good thing, particularly for huge downloads from arbitrary sites. So we don't discourage signing - quite the opposite! But say you have a 10GB setup.exe that gets prompted for elevation due to GenericInstaller (which tries to ferret out setups by looking for heuristic evidence). That means we have to touch the entire 10GB file to verify that the binary has not been modified since it was signed - and that's a lot of disk I/O (and the reason you wait for the elevation prompt). If you are running such a huge file repeatedly, you can skip over the signature check by applying the NoSignatureCheck shim using Compatibility Administrator - this will eliminate your wait. But, if you're only running it once, it may be worth it to you to actually perform the check.

    What could the developer do? They could manifest the self-extracting setup.exe to request asInvoker. The unpacker could then launch a small application that does the setup, which is signed but small enough that the validation doesn't take long. So, instead of waiting to validate the entire self-extracting package (when you may not even need all of it) you only wait to validate the actual setup, which clearly you'd want to manifest as requireAdministrator if you are doing a per-machine installation.

    If you are noticing one particular source of exes that take a long time to pop up, my guess is they are elevating the outside package and doing a complete signature check over the entire setup package. Let us know if we need to evangelize to one particular group of folks. Is there any one source where you see this happening more frequently than other times? For this is an instance where we, as a platform, have made it possible to be either high performance or low performance. We rely on third party developers to take the high-performance path, but we don't always reach everyone to tell them how. However, we can't remove the low-performance path, so we have to continue to extend our outreach. Clearly, we still have work to do.

    As for your 30-minute experience - we have no clue. We'd have to debug that one.

  • The App Compat Guy

    The UAC Arms Race Has Begun

    • 9 Comments

    I figured this day would come, and to be honest, I'm a little bit surprised it took as long as it did for me to run into this in the wild. But this week, I finally came across an installer that somebody had devised which would check to see if UAC was turned on, and if so, it would fail out, and not install.

    Actually, it was even worse than that. It would speak condescendingly to the user, informing them that UAC was turning on, so clearly the user must be some sort of an idiot. I mean, the readme file (which we all know everyone reads thoroughly multiple times before running an installer making sure they digest every last bit of it) clearly explains how to turn UAC off, how could they have missed that, or worse, ignored their sage advice?

    Then the installer exited.

    So, I took a peek to see how the application was performing this check. A little bit of time with Process Monitor was all it took - the application was looking at HKLM\software\microsoft\windows\currentversion\policies\system\enablelua. Aha - a registry key! That makes things easier.

    So, I shimmed up the application using VirtualRegistry. Now, we don't have a command line argument for VirtualRegistry that says "lie about UAC" so I had to invent my own using ADDREDIRECT(HKLM\software\microsoft\windows\currentversion\policies\system^HKLM\software\lieaboutlua), create this key, add the DWORD value enablelua, and set the value to 0. Now, the application thinks UAC is off, and we can go on from there to fix the LUA bugs that the developer punted on fixing.

    Now, here's what I don't get. If the application really needs to have admin rights, checking for UAC being turned off is a really silly way to get that. While it doesn't make sense for most organizations to run as a standard user with UAC turned off, there are some who do. This check could lead you in completely the wrong direction. If you really need admin rights, then manifest as requireAdministrator. (Note that I can still go back and shim with RunAsInvoker to override your manifest, and then fix your LUA bugs myself, but at least you're checking the right thing then.)

    There are, of course, other ways to check to see if UAC is enabled which would have made my job harder. I won't point them out, because that'll just make my job harder if somebody chooses to do that, and the obvious one has some of the same issues as this approach. All I can ask is this: if you're going to punt on fixing bugs, at least don't block me from fixing them for you. In this case, they didn't (though it looks like they tried) - and I figured I would share how to get around this in case others run into it.

    So, the arms race has begun. Together, we can get applications running as a standard user. Because that's what our mutual customers want. But if you fight me, I'll just get trickier! :-)

  • The App Compat Guy

    Windows Vista SP1 Broke my Internet Explorer Compatibility Test Tool!

    • 19 Comments

    ...but I fixed it. And I thought you might like to know how to fix it if you run into the issue.

    So first, the problem. I was trying to launch IECTT and found this MessageBox:

    ---------------------------

    Test Tool Error

    ---------------------------

    The file size exceeds the limit allowed and cannot be saved

    ---------------------------

    OK  

    ---------------------------

    Hrm. This could be ... um ... more helpful. I have no idea what it's talking about.

    Now, the fix:

    Manually set the key HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Internet Explorer\Main\FeatureControl\Feature_Enable_Compat_Logging\iexplore.exe = (DWORD) 1. Then, browse to some web sites which generate some issues (my trusty live.com homepage did just fine for me).

    The problem exists when the Internet Explorer event log is empty, so we just need some way to make it non-empty.


    Now, let's dig into the investigation, because it's always instructive to go back over what we found and try to understand what we can learn from it.

    First, I did a quick stack trace. Here's what I saw:

    ChildEBP RetAddr 

    002bedec 79f071ac KERNEL32!RaiseException
    002bee4c 79f0a629 mscorwks!RaiseTheExceptionInternalOnly
    002bef10 7a71713e mscorwks!JIT_Throw
    002bef5c 7a659780 System_ni!System.Diagnostics.EventLog.get_OldestEntryNumber
    002bef5c 7a6597ff System_ni!System.Diagnostics.EventLog.StartListening
    002bef5c 7a655d33 System_ni!System.Diagnostics.EventLog.StartRaisingEvents
    002bef5c 03cd6018 System_ni!System.Diagnostics.EventLog.set_EnableRaisingEvents
    002befac 03cd52d0 TestTool!Microsoft.ApplicationExperience.ApplicationCompatibilityToolkit.TestTool.EventLogMonitor..ctor
    002befac 03cd040c TestTool!Microsoft.ApplicationExperience.ApplicationCompatibilityToolkit.TestTool.MainForm.InitializeDataProviders
    002befe0 03cd00b9 TestTool!Microsoft.ApplicationExperience.ApplicationCompatibilityToolkit.TestTool.MainForm..ctor
    002befe0 79e7c74b TestTool!Microsoft.ApplicationExperience.ApplicationCompatibilityToolkit.TestTool.MainForm.Main
    002beff0 79e7c6cc mscorwks!CallDescrWorker
    002bf070 79e7c8e1 mscorwks!CallDescrWorkerWithHandler
    002bf1b4 79e7c783 mscorwks!MethodDesc::CallDescr
    002bf1d0 79e7c90d mscorwks!MethodDesc::CallTargetWorker
    002bf1e4 79eefb9e mscorwks!MethodDescCallSite::CallWithValueTypes_RetArgSlot
    002bf348 79eef830 mscorwks!ClassLoader::RunMain
    002bf5b0 79ef01da mscorwks!Assembly::ExecuteMainMethod
    002bfa80 79fb9793 mscorwks!SystemDomain::ExecuteMainMethod
    002bfad0 79fb96df mscorwks!ExecuteEXE

    OK, let's take a look at the OldestEntryNumber to see what it could be doing:

    .method private hidebysig specialname instance int32 get_OldestEntryNumber() cil managed
    {
        .maxstack 3
        .locals (
            [0] int32[] numArray,
            [1] bool flag,
            [2] int32 num)
        L_0000: ldarg.0
        L_0001: call instance bool System.Diagnostics.EventLog::get_IsOpenForRead()
        L_0006: brtrue.s L_000e
        L_0008: ldarg.0
        L_0009: call instance void System.Diagnostics.EventLog::OpenForRead()
        L_000e: ldc.i4.1
        L_000f: newarr int32
        L_0014: stloc.0
        L_0015: ldarg.0
        L_0016: ldarg.0
        L_0017: ldfld native int System.Diagnostics.EventLog::readHandle
        L_001c: newobj instance void [mscorlib]System.Runtime.InteropServices.HandleRef::.ctor(object, native int)
        L_0021: ldloc.0
        L_0022: call bool Microsoft.Win32.UnsafeNativeMethods::GetOldestEventLogRecord(valuetype [mscorlib]System.Runtime.InteropServices.HandleRef, int32[])
        L_0027: stloc.1
        L_0028: ldloc.1
        L_0029: brtrue.s L_0031
        L_002b: call class System.ComponentModel.Win32Exception System.Diagnostics.EventLog::CreateSafeWin32Exception()
        L_0030: throw
        L_0031: ldloc.0
        L_0032: ldc.i4.0
        L_0033: ldelem.i4
        L_0034: stloc.2
        L_0035: ldloc.2
        L_0036: brtrue.s L_003a
        L_0038: ldc.i4.1
        L_0039: stloc.2
        L_003a: ldloc.2
        L_003b: ret
    }

    Interesting - this is just a p/invoke into the native method advapi32!GetOldestEventLogRecord. If we debug into this guy, we see that this method calls advapi32!BaseSetLastNTError(-1073739516). This is, of course, STATUS_FILE_TOO_LARGE, which is what we saw in the first place. Now, we have an API returning a different result than it did before, that API has an owner, and we can contact that owner and have him take a look.

    So, our debugging job is done. Remember, you want to look for opportunities to debug and investigate to sharpen your skills, but you also want to know when to stop. At this point, we have an owner, and we have a workaround, so we can wipe our hands and move on to the next problem.

    Updated 20-Mar-2008

    If you're having problems getting the per-user policies to take hold, you can try HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\Feature_Enable_Compat_Logging instead of HKCU. I had to do this on one configuration.

  • The App Compat Guy

    Auditing Elevation on Windows Vista

    • 1 Comments

    A little while ago, I was investigating the customer experience data on elevations which we use to identify which applications are triggering the greatest number of elevation prompts on Windows Vista (and hopefully do something about it).

    Working with a customer who is currently planning to deploy protected admin desktops, but who are interested in understanding what and how often people end up elevating, we wanted to go after the same information. In this case, we had slightly different interests than the UAC team does. While knowing the biggest culprit is interesting, we're also interested in the "long tail". We'd like to be able to do forensics ("ah, I see that you elevated 'Happyrootkit 1.0' a couple weeks ago, and now things are a bit slower...") to diagnose issues.

    So, I was poking around doing research and determined how to do this, and I was going to go through and document here what I had done. Then, I found that the "Ask the Directory Services Team" blog already has here:

    http://blogs.technet.com/askds/archive/2007/11/16/cool-auditing-tricks-in-vista-and-2008.aspx

    Nice. Less typing for me today!

    So, what did I elevate yesterday?

    Application Elevations
    C:\Windows\System32\gpscript.exe 8
    C:\Program Files\Microsoft Application Compatibility Toolkit 5\Internet Explorer Compatibility Test Tool\testtool.exe 2
    C:\Windows\System32\mmc.exe 2
    C:\Windows\System32\WerFault.exe 2
    C:\0845f22e7821553cb4\spinstall.exe 1
    C:\96c454dd8c4de0e482770aca624b3a\spinstall.exe 1
    C:\c7f3efbf0404b3b3b763b20aaebddfb8\mpsigstub.exe 1
    C:\Users\<username>\Documents\Program Files\SysinternalsSuite\Procmon.exe 1
    C:\Windows\SoftwareDistribution\Download\Install\mpas-d.exe 1
    C:\Windows\System32\dllhost.exe 1
    C:\Windows\System32\rstrui.exe 1
    C:\Windows\System32\runonce.exe 1
    C:\Windows\System32\SystemPropertiesProtection.exe 1
    C:\Windows\System32\wuauclt.exe 1

    Not too bad, given that most of the apps launched with an elevated token inherited that token from the parent process. However, it looks like my enterprise group policy clicked this back off for me yesterday, so I only got 12 hours of data rather than a long-term view. Could be for some interesting findings!

  • The App Compat Guy

    Bringing Back the Search Window

    • 6 Comments

    In Service Pack 1 of Windows Vista, we removed the Search button from the Start Menu. While we discuss how to get there in this article, I'm finding that a number of people aren't finding this. Windows-F brings up your default desktop search provider. (I still like having the full window when I'm digging around for files that aren't indexed.)

Page 1 of 1 (5 items)