Displaying the UAC Shield in WPF with an Adorner
Since creating an elevated managed COM object, I have, on odd occasions, given thought to how to display the UAC Shield Icon in a WPF application. After some research failed to uncover any useful information, I embarked on the task of creating a manageable method for displaying the icon. While a full general purpose solution might be nice, a button that can display the icon as needed, should be sufficient for most purposes and demonstrates a methodology to follow for other controls.
Since I limited my implementation to a button, I created the UACShieldButton derived from Button. By deriving from Button, all of the click and commanding handling were already done. I left the final decision to display the icon with the UACShieldButton class. The caller simply indicates that it desires the shield to be displayed if all other conditions, elevation state and UAC enabled operating system, are correct.
The obvious items required are a method of the calling code to indicate the desire for the shield and the shield icon itself. Since I wanted binding to work with these properties, they were implemented as dependency properties. The DesiresShield dependency property represents the calling code’s desire to have the shield displayed. The ShieldIcon dependency property is an ImageSource for the image to display. This property will default to the system shield icon, System.Drawing.SystemIcons.Shield through the default value set in the UACShieldButton’s static constructor.
If you have been following along, you have noticed I keep indicating the code utilizing the UACShieldButton class indicates a desire to have the shield displayed. By allowing the UACShieldButton class to make the final decision to display the shield, some complication is hidden from the caller and code is consolidated into one location. The shield will be displayed if the three following conditions are true: the shield is requested, the operating system’s major level is 6 or above (Vista and Server 2008 and higher) and the caller is not already elevated. The test for elevation is the only one that is not entirely straight forward. As it happens, the old Win32 API call IsUserAnAdmin supplies the needed information. Even though the documentation indicates membership in the Administrator’s group, under UAC enabled systems, it indicates that the user is currently elevated to administrator status.
The other nicety provided is the automatic handling of different ToolTips depending on elevation state. Generally, the non-elevated state indicates that the associated out-of-process COM server has not been created. Since it is not created, any state information that it would supply is unavailable. By providing for the automatic handling of the display to different ToolTips according to the elevation state, the UACShieldButton control automatically provides the user with the correct ToolTip. For example, assume you have a Service Controller Interface COM server and Start and Stop buttons to stop or start a service. Until such time as the COM server is created, the state of the service is unknown. The ToolTipNotElevated version of the ToolTip could indicate the lack of state information along with the effect of clicking the button. Once the COM server is created, the ToolTipElevated ToolTip would be displayed simply indicating the effect of clicking the button.
The only logic that the caller must provide is the setting of DesiresShield to false, once the elevation is provided for any button. You might be wondering why? The elevation done here is elevating an out-of-process COM object as opposed to the program elevation that the UACShieldButton class can detect.
The trick to easily displaying the shield is to use an Adorner. The alternative is to provide an alternate control template for the button and having created an alternate control template once, I do not relish doing it again. The UACShieldAdorner class derives from Adorner and manages all aspects of displaying the shield. The only tricky part of this code is the sizing and positioning algorithm. The shield is resized proportionally to be four pixels less than the hosting button’s height. It is then placed two pixels below and to the right of the upper left corner of the button. Since the adorner layer is the topmost layer, the shield can obscure the text or the image in the button.
While this solution is not an end-all solution to displaying UAC Shield Icons, it is a starting point to that can be easily adapted to other controls and positions algorithms.
The code is here
Musings on Wix
Like many developers, I tend to forget about the installation process. In the past, I have thrown the product over the wall and left the installation to process to someone else. I knew this practice was less than optimal, but it was what I had to work with. On my last project, I took ownership of the full process including installation. Given that I did not want to pay too much money for yet another copy of an installation-building product, I decided it was time to learn to use Wix. While I will not say that Wix is wonderful, the price is right and it is, in many ways, less frustrating than the commercial products.
Wix has the advantage that the installation builder is close to the Windows Installer database tables so the possibility of the tables not being built right is small. Wix’s advantage is also its disadvantage, in that the user must learn the Windows Installer before they can do much with Wix. I realized that the commercial GUI installation builders and the Visual Studio setup projects are masking a lot of information. I also discovered that many issues with past installations were due to a lack of knowledge about the Windows Installer because the GUI products made the whole process seem simple, which is as far from reality as can be.
On first look, starting a new installation project with Wix is an overwhelming task. As you begin to read the Wix documentation, you rapidly understand you will be creating many GUIDS and doing a lot of typing. I attempted to create an installation without reading the Windows Installer documentation. While it is possible to create a usable installation without aide of the installer documentation, having more than a glancing familiarity with the Windows Installer will certainly help you create better installations.
Wix has some tools that automate a lot of this process, but they should be avoided on the first attempt. I say avoid them, not because they are bad, but because they can easily put the user in the same place as the GUI installers; too much masking of what is really happening. Misusing the tools can cause many undesired side effects.
It took me two days to create a simple install that had sixty components, many of which were shared between the two installable features. That time included understanding how to create the Start Menu entries and populate all the fields in the Add/Remove/Programs table (keep in mind that Wix is the assembler level code of the installer and it does almost nothing for you automatically). After the two days, my install would correctly clean up upon “un-installation” so that there was no trace of the program left on the system (the complete cleanup is something that the installer documentation and the Wix developers stress). I have not yet attempted patches or upgrade installs and I suspect that I am violating installer rules such that these will not work; so more reading on major and minor upgrades.
I had to include the call of a managed code installer class (System.Configuration.Install.Installer). While this is a bit difficult, the hardest part was figuring out how to do it. The process is almost completely devoid of documentation. The first thing I had to get into my head was that calling the Installer class was really a managed custom action. As soon as you mention managed custom actions, many people form a defensive circle and tell you that you should not write one. Both the Wix developers and the Windows Installer team discourage their use, but they can be extremely useful and, when done correctly, do not violate any of the installer’s rules. We would all benefit from the Windows Installer team removing their heads from the sand and embracing managed custom actions or even better if they would create a standard action to call a managed Installer class, but I am not holding my breath. Anyway, a day later, I had the custom action working and correctly cleaning up upon product removal.