Here's an interesting lesson which, quite honestly, I haven't thought about for a while. But it turns out it's rather important.

A little over a month ago, I talked about a change to Windows Vista in SP1 regarding per-user COM registration, indicating that we changed the behavior and it would now work with UAC disabled. Let's dissect how this is implemented specifically. First, I disassembled the function, and then I focused in on the CALL instructions, to find:

0:010> uf ole32!OpenClassesRootKeyExW
  251 760878ba e888ffffff      call    ole32!RegHelpSuspendImpersonate (76087847)
  257 760878c5 ff15e4110676    call    dword ptr [ole32!_imp__GetCurrentProcess (760611e4)]
  257 760878cc ff159c160676    call    dword ptr [ole32!_imp__OpenProcessToken (7606169c)]
  261 760878e1 e8c9000000      call    ole32!IsUserHiveOK (760879af)
  265 760878fd ff1554160676    call    dword ptr [ole32!_imp__RegOpenUserClassesRoot (76061654)]
  269 760f9014 e8fe430100      call    ole32!WPP_SF_d (7610d417)
  283 76087922 ff15fc160676    call    dword ptr [ole32!_imp__RegOpenKeyExW (760616fc)]
  286 760f905b e8b7430100      call    ole32!WPP_SF_d (7610d417)
  290 76087935 ff15e4130676    call    dword ptr [ole32!_imp__CloseHandle (760613e4)]
  294 760f9065 ff150c140676    call    dword ptr [ole32!_imp__GetLastError (7606140c)]
  295 760f90a1 e871430100      call    ole32!WPP_SF_d (7610d417)
  298 7608793e e811010000      call    ole32!RegHelpResumeImpersonate (76087a54)
  304 76087966 ff15fc160676    call    dword ptr [ole32!_imp__RegOpenKeyExW (760616fc)]
  307 760f90e3 e8dfd70100      call    ole32!WPP_SF_Sdd (761168c7)
  309 76087979 ff15f4160676    call    dword ptr [ole32!_imp__RegCloseKey (760616f4)]

That ole32!IsUserHiveOK looks interesting, so let's chase that down and see what we find, again just focusing in on the call statements:

0:010> uf ole32!IsUserHiveOK
  200 760879cc e83c000000      call    ole32!IsElevatedToken (76087a0d)
  205 760879e5 e83ae2ffff      call    ole32!GetTokenElevationType (76085c24)
  221 760941e8 e81a000000      call    ole32!IsUIAccessToken (76094207)

This is, indeed, interesting. We're checking IsElevatedToken, and then we're calling GetTokenElevationType. What can TokenElevationType tell us? Let's check MSDN:

TOKEN_ELEVATION_TYPE Enumeration

The TOKEN_ELEVATION_TYPE enumeration indicates the type of token linked to the token being queried by the GetTokenInformation function or set by the SetTokenInformation function.

Syntax
typedef enum  {
  TokenElevationTypeDefault   = 1,
  TokenElevationTypeFull,
  TokenElevationTypeLimited 
} TOKEN_ELEVATION_TYPE , 
*PTOKEN_ELEVATION_TYPE;
Constants
TokenElevationTypeDefault

The token does not have a linked token.

TokenElevationTypeFull

The token is linked to an elevated token.

TokenElevationTypeLimited

The token is linked to a limited token.

We can see how, using both elevation state and TokenElevationType, we can determine both the elevation state and if UAC is enabled or disabled. So, we've unraveled what's happening for COM.

But, almost immediately after posting, people began posting about exceptions to the rule, which we began investigating. The lesson here: OLE != OLE Automation. They are different things. And it turns out that OLE Automation doesn't have the fix we incorporated into COM itself. They're different code, incorporated into different DLLs, maintained by different teams. What does OLE Automation do? Let's take the same approach, disassembling the function and zeroing in on the calls it makes:

0:010> uf oleaut32!OpenClassesRootKeyExW
  150 75a49f80 e8c7feffff      call    OLEAUT32!SuspendImpersonate (75a49e4c)
  151 75a49f8b ff151412a475    call    dword ptr [OLEAUT32!_imp__GetCurrentProcess (75a41214)]
  151 75a49f92 ff157413a475    call    dword ptr [OLEAUT32!_imp__OpenProcessToken (75a41374)]
  155 75a49fad e8cafeffff      call    OLEAUT32!IsProcessElevated (75a49e7c)
  159 75a5ab15 ff153413a475    call    dword ptr [OLEAUT32!_imp__RegOpenUserClassesRoot (75a41334)]
  173 75a49fd4 ffd3            call    ebx
  178 75a49fdc ff151012a475    call    dword ptr [OLEAUT32!_imp__CloseHandle (75a41210)]
  182 75a78782 ff15ec11a475    call    dword ptr [OLEAUT32!_imp__GetLastError (75a411ec)]
  185 75a49fe8 e842000000      call    OLEAUT32!ResumeImpersonate (75a4a02f)
  191 75a4a05e ffd3            call    ebx
  192 75a4a066 ff158013a475    call    dword ptr [OLEAUT32!_imp__RegCloseKey (75a41380)]

Things look a bit different here - all we call is IsProcessElevated. If you're running as admin with UAC disabled, this returns true - and the function never calls in to determine the token elevation type. OLE Automation did not get the same update that COM did!

Now, from the Windows side of things, we already have a bug open to fix this, but since I spread the word that it's working and some of you have contacted me suggesting that it isn't, I wanted to make sure it was clear: COM was, indeed, fixed to work with per-user registrations and UAC disabled, but OLE Automation was not.

What can you do in the interim? These are the workarounds I have heard about so far:

  • Use MIDL generated proxy/stub marshalling for interfaces instead of type libraries
  • Load the type library from the COM dll directly instead of reading it from the per-user registry hive

Incidentally, for those of you who were perplexed that Live Mesh was released but didn't support running as admin with UAC disabled until Windows Vista SP1 ... what do you think the odds are that there are some per-user COM registrations in there? Hmm...