A couple of weeks ago, I had a chance to sit down with Gov Maharaj (whose name rhymes with Orange), a developer in the Windows Fundamentals Dev team, and chat about debugging and mitigating application compatibility issues. You can view our discussion on Channel 9: http://channel9.msdn.com/Showpost.aspx?postid=377306.
I had my worldview challenged today.
I had gone on record saying that, if you are deploying to Standard Users, then UAC really can't hurt you - it can only help you.
But then I saw something I really did not understand - two identical machines, side by side. Both of them logged in as a standard user. One machine had UAC turned on, the other had it turned off. And there was a piece of software that only worked with UAC turned off.
Weird.
After poking around with tools for a while, I just went on in with the debugger. And I noticed that the application was launching another application, and that this application would have triggered installer detection, and their group policy was configured to automatically deny elevation requests. However, I then verified their group policy that installer detection was turned off, so that couldn't be it.
Hmm... it must be manifested. While that wouldn't explain why it worked with UAC turned off, it only took a second to shim it up with RunAsInvoker (which trumps a manifest) and have a go - sure enough it worked!
So, I brought the exes over to a machine that had dev tools on it so I could crack them open and look for the manifests I was sure were there, and indeed they were. But they weren't manifested as requireAdministrator - they were manifested as highestAvailable. That's strange - that should work as a standard user, since you don't generate a split token and highest available is just you as you are.
So, why would a highestAvailable manifest cause an app to fail as a standard user? Well, the answer is that it shouldn't. The problem was that their standard users weren't actually standard users.
You see, I flipped around in their group policy and noticed that they had the UserRightsAssessment set so that the Users group had Back up files and directories and Restore files and directories.
Yowzers. This adds SeBackupPrivilege and SeRestorePrivilege to the token. These are pretty ridiculously powerful privileges. They let you read and write any file you want without an AccessCheck. To quote the DDK, each privilege "obviates the need for any ACL-based security check." And there is really no reason to have ACLs on your files giving you the illusion of security if you simply allow everyone to bypass them on a whim, eh? We recognize this, and we then split the token.
So, their "standard users" were not exactly standard users - they were generating split tokens, and this highestAvailable-manifested app was having the elevation request automatically denied. Problem solved - time to fix that policy. (There's not point in having it, now is there, since they'll never be able to elevate to the token that actually contains their privileges?)
And my worldview was able to continue. Crisis averted.
As part of my job, I debug and mitigate application compatibility issues with Windows Vista for customers who are migrating to the operating system. Along the way, I also get my fair share of Office 2007 questions (on which I am no expert) since these two products are, more often then not, being deployed together. (We call this DOVO - Desktop Optimization with Vista and Office. We like acronyms here at Microsoft.)
I came across an interesting issue today. My customer had an application which, for Office 2003, had created a new toolbar, and then used VBA to disable every other toolbar on the system (using the CommandBars object). They were calling out each CommandBar by name, so the process was inherently somewhat fragile. And, sure enough, on Office 2007, some things weren't disabled.
Now, there were a couple of different approaches we could take. One would be to just find out which CommandBar objects were left out, and snag them as well. The other is to just disable everything and only enable what you specifically want.
Then, on one of our internal discussion lists, somebody (who clearly knows Office better than I do) suggested just customizing the ribbon.
You know, because what is the value of having 9 tabs sitting up there when eight of them contain only controls that are grayed out? (And also because having everything crammed together on the Add-Ins tab with their tiny little icons looks like ... erm ... looks not so great.)
So, my next question was - how hard is that to set up?
And boy, I can't believe how easy it is.
First, I created a new docm document. I saved it to the desktop, and renamed it document.docm.zip. So, now I can open it like a Zip file.
I then created a new folder on my desktop called customUI, and created an XML document inside of it called customUI.xml. I grabbed the schema for the customUI XML from here: http://www.microsoft.com/downloads/details.aspx?FamilyId=15805380-F2C0-4B80-9AD1-2CB0C300AEF9&displaylang=en. I installed the xsd into the Visual Studio 2008 schema cache, and cooked up a quick and dirty XML file:
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" > <ribbon startFromScratch="true"> <tabs> <tab id="WorkInstruction" label="Work Instruction"> <group id="SampleGroup1" label="Sample Group"> <button id="Button" imageMso="HappyFace" label="Insert Company Name" size="large" onAction="ThisDocument.InsertCompanyName" /> </group> </tab> </tabs> </ribbon></customUI>
The startFromScratch attribute in the ribbon object is the key to clearing out everything that's there, so only the things you specifically add show up (which is a far better user experience than seeing all of the controls but having none of them work, in my opinion).
I then dragged that customUI folder into the zip file (really my docm file).
Then, I browsed to the rels folder, and edited the .rels xml file to add a relationship for the customUI so it became the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/> <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail" Target="docProps/thumbnail.wmf"/> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/> <Relationship Id="someID" Type="http://schemas.microsoft.com/office/2006/relationships/ui/extensibility" Target="customUI/customUI.xml" /></Relationships>
I then inserted this back into the zip file, and renamed it back to a docm file.
I had to add a bit of code to handle the button click, so I just took one of the samples and used its code:
Sub InsertCompanyName(ByVal control As IRibbonControl) Dim MyText As String Dim MyRange As Object Set MyRange = ActiveDocument.Range MyText = "Microsoft Corporation" MyRange.InsertBefore (MyText)End Sub
And that was it. Less than half an hour, and I had a totally customized ribbon that used the more foolproof method of starting from scratch and then adding stuff than the method of starting with everything and disabling things by name until I had gotten rid of everything that I can think of.
Seriously, it was so cool to see this with such a minimal investment of time. Half an hour, tops. Makes me wonder why our own internal Office tools are still sitting in the add-ins tab...
Here was an interesting little conversation that took place on one of our internal discussion lists today:
One person was trying to back up his documents directory. So, he'd use Robocopy to copy the files over. For example, he would use the following command line:
robocopy c:\users\<username>\documents e:\documentsbackup-january
Then, he'd click over to this directory in Windows Explorer, and he couldn't see e:\documentsbackup-january, he would see e:\documents.
Huh?
To find out what is going on, you'll have to view some hidden system files. From Windows Explorer, select Organize -> Folder and Search Options. On the View tab, select the option to show hidden files and uncheck the option to hide protected operating system files. Now, in the documents directory, right click on desktop.ini and select edit. Here is mine:
[.ShellClassInfo]LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-21770IconResource=%SystemRoot%\system32\imageres.dll,-112IconFile=%SystemRoot%\system32\shell32.dllIconIndex=-235
The LocalizedResourceName is what is making things interesting here. No matter what you call this directory, Explorer is going to display the LocalizedResourceName.
That's handy when you don't live in the US. You see, on Windows Vista, we don't give different names to directories based on locale. c:\program files is always c:\program files. It doesn't matter what locale you are sitting in. But if you don't speak English, you'd probably prefer to see this in your own language. That's what this desktop.ini option will do for you.
This is a change from what we did in the past. Say you are from Germany. On Windows XP, you had c:\programme. That's not where program files actually live any more. To keep applications working, we have a directory junction there, and to keep explorer showing you want you understand, we have the localized name.
But this can mess you up when you're making copes that include this desktop.ini and you want to rename it. Delete the desktop.ini file, and things will probably begin to make more sense to you.
Updated 09-February-2009: Compiled specifically targeting x86, so that you can shim this on x64 builds of Windows.
Updated 18-April-2008: I did a clean build of this on Visual Studio 2008 before posting, and I forgot about the built-in automatic manifests. These manifests disable some of the shims, and also turn off the installer detection. Updating the attachment to remove these manifests.
The Stock Viewer Shim Demo Application is an application I have been using to demonstrate Mitigating Windows Vista Application Compatibility Issues Using Shims at various conferences and with various customers. By popular demand, I am posting the sample application.
The primary user interface is lifted directly from windowsclient.net - the good code is theirs, the (intentionally) bad code is mine.
Please note that the application was developed purely to support my scripted demos and labs - I can make no promises about how it behaves when you are not following the script.
I have packaged the demo as an MSI because I need to flip some registry bits to set up the control panel applet. Feel free to explore the MSI prior to installation to review the registry settings that I use. The installer drops all of the sample applications, registers the control panel applet, drops the demo guide as an XPS file, and sets up start menu shortcuts.
For those of you who speak WIX, here is the entire installer:
<?xml version="1.0" encoding="UTF-8"?><Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="{0AF2176E-E9E4-4f9e-A573-905466C09047}" Name="Stock Viewer Shim Demo Application" Language="1033" Version="1.0.0.0" Manufacturer="Microsoft Corporation" UpgradeCode="98382F9A-CE19-4026-8457-4FD433A61889"> <Package Description="Stock Viewer Shim Demo Application" Manufacturer="Microsoft Corportion" Comments="This is a Stock Viewer application with intentional errors, used to demonstrate using shims to resolve these issues." InstallerVersion="200" Compressed="yes" />
<WixVariable Id="WixUILicenseRtf" Value=".\license.rtf" /> <UIRef Id="WixUI_Minimal" />
<Media Id='1' Cabinet='stockvwr.cab' EmbedCab='yes' VolumeLabel='StockVwr'> </Media> <Condition Message='This sample only runs on Windows Vista and above.'>VersionNT >= 600</Condition>
<Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="INSTALLLOCATION" Name="StockViewer" >
<Component Id="StockViewer" Guid="{0C384418-76B8-43de-95C5-796D7636A525}"> <File Id="DWM_CRD" Assembly="no" Name="DWM Compositing Rendering Demo.exe" Source="..\StockViewer\bin\Release\DWM Compositing Rendering Demo.exe" DiskId="1" /> <File Id="OLAF" Assembly="no" Name="OLAF.exe" Source="..\StockViewer\bin\Release\OLAF.exe" DiskId="1" /> <File Id="StockVwr" Assembly="no" Name="StockViewer.exe" Source="..\StockViewer\bin\Release\StockViewer.exe" DiskId="1" > <Shortcut Id="startMenuStockViewer" Directory="ProgramMenuDir" Name="Stock Viewer" WorkingDirectory="INSTALLLOCATION" /> </File> <File Id="StkVwrCP" Assembly="no" Name="StockViewerControlPanel.exe" Source="..\StockViewer\bin\Release\StockViewerControlPanel.exe" DiskId="1" /> <File Id="StkVwrUO" Assembly="no" Name="StockViewerUpdateOrders.exe" Source="..\StockViewer\bin\Release\StockViewerUpdateOrders.exe" DiskId="1" /> <File Id="StkVwrUp" Assembly="no" Name="StockViewerUpdater.exe" Source="..\StockViewer\bin\Release\StockViewerUpdater.exe" DiskId="1" /> <File Id="StockIco" Assembly="no" Name="Stocks.ico" Source="..\StockViewer\bin\Release\Stocks.ico" DiskId="1" /> <File Id="LabGuide" Assembly="no" Name="Mitigating Application Issues Using Shims - Lab Guide.xps" Source="..\Mitigating Application Issues Using Shims - Lab Guide.xps" DiskId="1"> <Shortcut Id="startMenuLabGuide" Directory="ProgramMenuDir" Name="Lab Guide" WorkingDirectory="INSTALLLOCATION" /> </File> <RegistryKey Id="CplNamespaceKey" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\Namespace\{DF5BF128-5283-4d39-8BC0-EC09F526E673}" Action="createAndRemoveOnUninstall" > <RegistryValue Id="CplNamespaceValue" Value="StockViewerApplet" Type="string" Action="write" /> </RegistryKey> <RegistryKey Id="CplClassIdKey" Root="HKCR" Key="CLSID\{DF5BF128-5283-4d39-8BC0-EC09F526E673}" Action="createAndRemoveOnUninstall" > <RegistryValue Id="CplClassIdValue" Value="StockViewerApplet" Action="write" Type="string" /> <RegistryValue Id="CplAppNameValue" Name="System.ApplicationName" Value="Microsoft.Demos.StockViewerApplet" Action="write" Type="string" /> <RegistryValue Id="CplAppCategoryValue" Name="System.ControlPanel.Category" Value="1,8" Type="string" Action="write" /> <RegistryValue Id="CplLocalizedStringValue" Name="LocalizedString" Value="Stock Viewer Settings" Type="expandable" Action="write" /> <RegistryValue Id="CplInfoTipValue" Name="InfoTip" Value="Sets Stock Viewer Settings" Type="expandable" Action="write" /> <RegistryKey Id="CplDefaultIconKey" Key="DefaultIcon" Action="createAndRemoveOnUninstall" > <RegistryValue Id="CplDefaultIconValue" Value="%ProgramFiles%\StockViewer\Stocks.ico" Type="expandable" Action="write" /> </RegistryKey> <RegistryKey Id="CplShellKey" Key="Shell\Open\Command" Action="createAndRemoveOnUninstall" > <RegistryValue Id="CplShellValue" Value="%ProgramFiles%\StockViewer\StockViewerControlPanel.exe" Type="expandable" Action="write" /> </RegistryKey> </RegistryKey> </Component> <Directory Id="Data" Name="Data"> <Component Id="StockViewerData" Guid="{3517F0AE-CDC6-48b7-83F9-BC53F32E5CBB}"> <File Id="SPY" Assembly="no" Name="spy.xml" Source="..\StockViewer\bin\Release\Data\spy.xml" DiskId="1" /> </Component> </Directory> </Directory> </Directory> <Directory Id="ProgramMenuFolder" Name="Programs" > <Directory Id="ProgramMenuDir" Name="Stock Viewer" /> </Directory> </Directory>
<Feature Id="StockViewerAll" Title="All Stock Viewer Files" Level="1"> <ComponentRef Id="StockViewer" /> <ComponentRef Id="StockViewerData"/> </Feature>
</Product></Wix>
2007 was really a fun year - I had a blast.
I finally started to get some traction documenting the shims provided in Windows - hopefully you like some of the changes Liz made in ACT 5.0.2 to add good documentation here. I've been talking about it all over the world, and there is still more to come. For internal folks, there are some new sessions I am cooking up for TechReady 6, and for external folks, you'll see the same thing at TechEd IT Forum 2008.
I sure did spend a lot of time on airplanes. I was all over the world, and I managed to reach elite status on 3 different airlines.
I forgot to toss my Marriott and Hilton cards on the pile when I took this picture. I actually ended up spending more nights in a Marriott than I did in any other location, including my house. If they had such a thing as a Double Platinum award, I would have gotten it.
I had a blast speaking at TechEd 2007 and TechEd IT Forum Europe 2007, not to mention two TechReady events. I ended up with some decent reviews, and a lot of constructive feedback on improving my speaking. I also learned that I'm likely to never end up being the top rated speaker at these tip tier events because Mark Russinovich keeps showing up to take that role. Curse you and your personal awesomeness, Mark! :-)
Obviously I'm kidding - I still attend every one of Mark's speeches I can to listen and learn. I sat in on a fantastic class with John Robbins, who promptly asked me what I was doing there, to which my response was that I wanted to learn to be at least as good at teaching people to debug as he is. I clearly still have a ways to go, so I'm still listening to everyone that I can.
I received my first Ship-It Award for ACT 5.0. Folks on product teams are probably saying "whatever, call me when you reach 50" but folks in the field are probably scratching their heads saying, "We can get those? How did you do that?"
I had some great fun collaborating with my friend Aaron, who is definitely not a UI designer, although he writes better code than I do in pretty much every other regard. He's got some really exciting things cooking for 2008, and I am fortunate enough to be able to tag along for a few of them. Speaking of, I really need to get back to work on something...
Here's to a fantastic 2008!
I've been on vacation for a couple of weeks, which has given me some down-time to do some reading and thinking.
Of course, what I have thought about may not always be what the author intended for me to think about, but it tends to be the books with unexpected inspiration that I will remember the longest.
One lesson I really hope I remember: don't use any authority you may or may not have earned on one subject to push assertions on another subject you actually don't know quite enough about.
I picked this little tidbit up from The Math Instinct: Why You're a Mathematical Genius (Along with Lobsters, Birds, Cats, and Dogs). Now, overall, it was an entertaining read, with some good tidbits to think about. The author earned my trust over the course of the book, to the point where I would begin to take for granted some of his assertions.
And then he had to go and make an assertion about something he apparently knows a little bit less about: biology.
Clearly, the author is passionate about his belief in the fact of evolution. But he made the following assertion: "That's far too short a time frame for there to have been any major structural changes in the brain - evolution occurs over hundreds of thousands if not millions of years."
Evolution simply isn't time-bounded. This statement is false. Look at the evolution of the peppered moth. That has happened since the industrial revolution - far fewer than hundreds of thousands of years ago. It is the rapid evolution of bacteria that causes problems with preventing disease. With fewer necessary structures to worry about, higher mutation rates in viruses accelerate this even further.
Major structural changes in the brain are generally prevented because there is extensive error-correction for genes in humans - unbounded mutation would lead to far more ways of creating a human that simply don't work at all, so humans with error correction will be favored over humans without error correction. Furthermore, there are fewer selective pressures on brains - a person with a mutation that causes him or her to have some unique capability may leverage that to, perhaps, make more money, but the person without this advantageous mutation is going to have exactly the same opportunity in most cases to reproduce and pass on his or her genes.
These arguments spun around my head for a bit, and I considered trying to track down the author to vent my frustration for spoiling a perfectly good read with something that was so clearly wrong. But then I realized that, instead, I should find Keith Devlin and thank him instead. This is a blunder that is all too easy to make, and it served as a wake up call to pay attention. I know a little bit about Windows application compatibility, but I could always know more. I know a little bit about Shims, but the folks who write them know more. I respond to a lot of questions, and on more than one occasion I have had to send out a mea culpa owning up to something I was wrong about (which I feel is critical to do, and I hope I haven't forgotten one). And that's with subjects I genuinely am considered somewhat of an expert on. There are plenty of other subjects about which I am genuinely interested and will write about that are more of me thinking aloud than me speaking with authority. I just hope I never use the wrong tone so people quote me as authoritative when I am not. Particularly if I happen to be wrong. Nevertheless, I suspect I probably inadvertently will.
So, thanks, Keith, for giving me a few hours of truly pleasurable reading. And thanks even more for an otherwise innocuous oversimplification that made me realize just how important earning trust and authority is, and how much more important it is not to take that for granted or over-extend it, intentional or not. And for the reader, there is really only one antidote. Just keep reading. Trust but verify. Round out your perspectives. Because you really can't help it - it's a psychological shortcut to take assumptions into account that you are likely not even aware of. Me? I plan to be especially vigilant when discussing topics that may have a blur with subjects about which I actually (mostly) know what I'm talking about. Beyond that, I'm pretty casual, so I'm sure I'll make a few slips.
I'm not sure exactly how to thank Keith, since I don't actually know him, so I figured I'd just link to his books. They are genuinely entertaining and informative. Plus, they'll remind you of just how beautiful mathematics can be. I think we forget that sometimes.
Have a happy new year, and a very compatible 2008!