I posted earlier on using VirtualRegistry to provide version lies when an application is looking for the version of the operating system in the registry instead of using one of the GetVersion APIs. I could go on and on about all of the other command line arguments that do interesting things, but that is actually the subject of a forthcoming update to the help file itself, so I'm going to focus on the interesting ones here.
Probably the most interesting one is the command line argument that allows you to redirect any one key to any other key. Why is this one so compelling? Because it can resolve almost every issue you could possibly come across.
You see, the rest of the command line arguments do specific fixes. They can pretend that certain keys exist, and that they have certain values. They can redirect registry keys to other places. But you have to get lucky and happen to find one that is redirecting the keys you need to a place that works for you. In the case of version lies, this is something that comes up quite a lot, and you can completely re-use that. There are command line arguments around various versions of DirectX and codecs, all of which either redirect or pretend.
But redirection can be a form of pretending. If you want the version to say 5.0, but it says 6.0, if you know the command line NT50 that's a very effective way to do that. But, what if you didn't know this command line (which until recently is probably the case)? What if there is an existing command line for a problem you have, but you don't happen to know that one, and I haven't talked about that yet? You can achieve the same effect using redirection.
For installation, I simply repackage the application so I create a new key that contains the value 5.0. I redirect the key I'm looking for, say HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\Current Version, to HKEY_LOCAL_MACHINE\Software\My Company\Vendor\App\Nt Current Version, and then populate that key with the value of 5.0. I then redirect to this new key, and voila - I get the same version lie.
Yes, it requires me to drop a key, but you can see that I can create whatever I need, and use redirection both for redirecting to user-writeable locations as well as lying about existing values (which don't need to be user-writeable necessarily).
That being said, what's the command line to use?
ADDREDIRECT(key_to_redirect_from^key_to_redirect_to)
If you have multiple keys to redirect from and to, then you can just chain them together, separating each with the caret (^) symbol.
With this fix, there should be almost no registry issue you can't resolve somehow, as long as you don't limit your thinking on what a redirect is supposed to do. Remember, you can always repackage if you need to add some new values to redirect to.
Now, I mentioned earlier that you can resolve almost any issue that you could possibly run across. Why didn't I just say every issue?
Partially because I doubt that my own creativity can rival the combined creativity of the rest of the software developers in the world for coming up with registry issues that could cause problems in terms of application compatibility.
Also, there is a limitation to how much you can redirect. In fact, the sheer scope and power of what you can do with VirtualRegistry is not completely exposed because of one simple limitation: the number of characters the Windows Shim Infrastructure supports in a command line argument. It's simply not long enough. So, the developers on the Windows team would implement the things they needed in code (no limitation there) and assign a name to it that was nice and short, this name either being the name of the application or component, or the nature of the problem.
You may bump into the same thing, if you have a host of keys to redirect. In which case, then you may want to browse the forthcoming documentation to see if perhaps an existing command line argument will cause the keys you need redirected or lied about. Personally, I haven't experienced this problem with a customer, but it could potentially happen.
Nonetheless, you can take this incredibly powerful shim, and this incredibly flexible command line argument, and resolve a host of registry-related issues on Windows Vista.
When investigating application compatibility issues on Windows Vista, a significant percentage of the failures that I debug are not failures with the platform itself, but rather applications that would have failed as a standard user on Windows XP or earlier, and begin to fail on Windows Vista because the default is to have everyone running with at least standard-user-like privileges most of the time. So, LUA Bugs that existed before are really showing themselves on Vista because of UAC (and UAC is fixing a lot of them - it's a feature that fixes stuff, not that breaks it).
One interesting LUA Bug that catches some people off guard: apps that attempt to be single instance. An approach I have seen many times is to use the System.Diagnostic.Process class in managed code, call the GetProcesses(...) method, and then look for your name. (Doing things based on names is kind of fragile, but for the most part in the real world, while not foolproof, it will do the trick.) However, if your managed code is built on the 1.1 framework, you're going to run into an issue here.
Rather than going into the details myself, I'll let the BCL team address the problem from the System.Diagnostics.Process FAQ:
Why does the Process class have a dependency on the performance counter?
The Process class exposes performance information about processes. In order to get performance information about remote processes, we need to query performance information on a remote machine. We used the same code to get performance information about processes on a local machine in Everett. That's why the Process class has a dependency on the performance counter. However, this approach has several problems:
Performance information is not available to a non-admin account, which is not in the Performance Counter Users Group on Windows Server 2003. So the Process class could not get process performance information in this case. Getting performance data from all the processes on the machine is pretty expensive. The operating system (OS) might load lots of DLLs and it might take seconds to complete. The floppy drive light will be on when the OS tries to find the index for some performance counter. If the performance counter data was corrupted for any reason, the Process class could throw an exception while trying to convert some raw performance information into DateTime. The Process class could not be used to get process information on machines without the process performance counter. Performance counters can be disabled in Windows. See the following link for details: http://www.microsoft.com/windows2000/techinfo/reskit/en-us/default.asp?url=/windows2000/techinfo/reskit/en-us/regentry/94214.asp
The good news is that we have changed the implementation of the Process class in Visual Studio 2005 (our next release, code-named Whidbey). The Process class doesn't have a dependency on performance counter information any more (this is only true for local processes).
(emphasis mine)
Well, the good news is that 1.1 code that is recompiled for 2.0 will start working, but what if you can't just recompile the code? What can you do to achieve the same thing while only changing the implementation of the single instance detection? Well, I was going to put up some sample code, but it turns out that Steve Toub already has, so I'll just point you to his. :-)
.NET Matters: Stream Decorator, Single-Instance Apps
Now, the next question is, do you really need the application to be single instance? Does your user really want that? Obviously, if the application is already single-instance, there are probably a number of dependencies (intentional or not) on this design, but is the decision the best one? For example, it makes me crazy that I can't have two instances of Outlook. I have two Exchange servers (work and personal), and I have to close out of one completely before I can open it back up and connect to the other. I'd love to be able to connect to both with two instances of Outlook, but I can't. Single instance breaks a scenario that's personally important to me. Some people are confused that they cannot right click and launch Explorer elevated because it's important to them - single instance breaks a scenario that's personally important to them. What scenarios are you breaking, and what are you gaining in this trade-off when you do this detection?
Last time around, we were talking about version lie shims for Windows Vista with managed code. Somebody pointed out that they suspected that the same issue might be true for Visual Basic 6 applications, since version lie shims didn't appear to work there either.
Well, this is not entirely true. We can go through the same process as last time to see if we spot the Visual Basic 6 runtime, but take my word for it - we don't exclude it. What is actually going on is that we aren't shimming the Visual Basic virtual machine, by virtue of how it happens to be loaded. Since we don't have all of the addresses we use sitting in the import table, the shims aren't applied. Instead, we have to intentionally add the Visual Basic 6 virtual machine, which essentially causes the shim engine to shim up the GetProcAddress API so we can shim a dynamically loaded dll.
To take this for a test drive, I figured I'd go ahead and stick with version lies. I took some sample code for checking versions from here:
How to determine which 32-bit Windows version is being used
Now, if you look at the code, you'll immediately see an opportunity to update it, since it doesn't acknowledge the existence of Windows version 6.0. So, I went ahead and added that in, so we could see that this was Windows Vista. (Otherwise, it's not going to return anything.) If you're feeling bored, you can also differentiate between 5.1 (XP) and 5.2 (Server 2003).
Compile this code and then try to get a version lie going. You'll find that, no matter what, it always says Windows Vista. You can do so from the compatibility tab, or from within Compatibility Administrator.
Let's go in to Compatibility Administrator and fix that up. Select the VB6 exe you just compiled, don't select a layer/mode, and get to the Compatibility Fixes / Shims page. Select a version lie shim (whichever one you want), and then click on Test Run. It still says Windows Vista. Now, click on the Parameters button for this shim. In the Module Information area, type msvbvm60.dll in the the Module Name text box, then click on Add. Click on OK to dismiss this dialog. Now click on Test Run. Voila! Now, you're version lie is reaching the application!
One of the classes of shims that people find the easiest to understand are the version lie shims. In essence, we have shims that can compensate for the fact that so many developers' keyboards were shipped with a defective > key (and the failure rate of this key on developer keyboards is astonishing). They work by just returning a different value from the GetVersion(Ex) APIs, depending on which operating system you selected. It all makes sense, so people start trying to play around with it. Time and time again, I have had people try to cook up a quick C# or VB.NET application that checks the version, tried to version lie to it, and had the version lie not appear to do anything.
What's going on?
Well, if you want to dig in, you can fire up Compatibility Administrator with the (super secret) /x command line switch. This shows you more details than you would ever want to know. The CompatAdmin team did a really good job of not hiding anything you actually need to know, so the only reason you'd ever rationally want to use this switch is to check something specific such as this, and not because you'd need to in order to get work done.
OK, so now that you have CompatAdmin started, under the System Database, expand the Compatibility Fixes list. With the /x switch, you'll notice that the WinXPSP2VersionLie now has a plus sign - if you expand this, you'll see a list of modules that have a red diamond next to them. These are modules that this shim specifically excludes. Among these? The .NET Framework modules.
You see, the .NET Framework doesn't take too kindly to being lied to. They do version checks to determine how to implement certain things, and thinking they're running down-level isn't so great. So, we intentionally exclude these modules, and, consequently, we intentionally exclude the code that you're writing that these modules are JITting and executing. Not that we wanted to do so, but the infrastructure didn't give us a great way to separate these out. We either lied to everything, or we lied to nothing. It was worse to lie to everything, so we didn't.
Several people notice this because we happened to post a demo application on http://devreadiness.org (which is being phased out now) that was managed, but used a version lie. The exclusion of .NET apps was added pretty late in the cycle for this shim, after this app was written and released, and nobody went back and updated it.
So, what do you do if you want to demo? Well, if you poke around a bit longer, you'll notice that it's only the XP shims that exclude the .NET Framework. So, technically you could version lie for Windows 2000 against a managed app and it would work. However, the same logic applies. We don't explicitly block it, but the fact that we block it is because it's a bad idea not to, and it remains a bad idea not to even when we don't block it. But for a demo, you could get the version lie done.
The other thing to do is to just write native code for your demo, which is how I demo. Here's some code that will do a version check:
void VersionTest() { OSVERSIONINFO osvi; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) { TaskDialog(NULL, NULL, TEXT("Version Test"), TEXT("Tested for Windows XP and succeeded"), TEXT("This test runs a version check for version 5.1"), TDCBF_CLOSE_BUTTON, TD_INFORMATION_ICON, NULL); } else { TaskDialog(NULL, NULL, TEXT("Version Test"), TEXT("Tested for Windows XP and failed"), TEXT("This test runs a version check for version 5.1"), TDCBF_CLOSE_BUTTON, TD_ERROR_ICON, NULL); }}
For real-life applications that need a version lie, well, if the application is managed, you'll have to change the code. This is something you can't, and shouldn't, shim up.
So, if you notice the version lie isn't working, and the application is managed, then my spidey sense tells me you're trying to shim it up with an XP version lie - and that's not going to work.