Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Certified For Vista: How to ensure an application gets certified.

4.89/5 (81 votes)
16 Apr 200712 min read 2   740  
Do you want your applications to be approved by Microsoft to run on Windows Vista? This article explains how.
Screenshot - VistaCertification.gif

Introduction

With the recent release of the new Microsoft Windows Vista operating system, there is a new certification program that allows us to endorse our applications with a "Certified for Vista" logo as long as it passes a strict testing process. There are 32 tests that the application needs to pass in order to gain the certification. When preparing our application (Fascia – a C# .NET (managed) application) for certification, there were a number of issues and things to consider. It was hard to find the information required to overcome many of the difficulties, so I have put together this article in order to summarise what I have learnt in order that it may assist with future certification attempts.

I have included some code samples that refer to some functionality from the "DataInterface" namespace. The relevant assemblies in this namespace are discussed towards the end of this article. The source code for these assemblies and a demo program that uses them are available in the download.

Overview of the Tests

Fortunately, the full test scripts and the tools required to perform the tests are freely available. It is therefore possible to run all the tests before submitting the application to give a degree of confidence about whether your application will pass or not. There are a number of helpful resources available on the internet to assist in the preparation process. I have summarised these at the end of this article. The tests are split into three sections:

  • Security and Compatibility
  • Install/Uninstall
  • Reliability

Security and Compatibility

User Account Control (UAC) and Elevation

Vista will not let applications perform administrative tasks unless an administrator confirms that it is OK. Even if you are logged on to Vista as the administrator, you still need to confirm administrative tasks as they happen (by entering your password). This process is known as elevation. This is relevant to developers of Vista applications since we need to decide which tasks in our application need administrative privileges. We should try and avoid doing anything that requires administrative privileges and those tasks that do require an administrator need to be separated into distinct executables. All your executables must contain a manifest that indicates the execution level that is required to run them. I explain how to add the manifest to your executables in the text for Test Case 1.

The main executable must have a manifest with a requested execution level of "asInvoker" (the other options are "requireAdministrator" and "highestAvailable"). This means that it can be run without administrator privileges. If the main executable needs administrator privileges straight away and for most of its tasks (this is very unusual), you must apply for a special waiver from Microsoft. If the main program needs to do something that requires administrator privileges, it must shell another executable that has a manifest with a requested execution level of "requireAdministrator". The button or other control that shells the "requireAdministrator" program must be denoted with the Vista shield icon. I'll explain how to do that later.

Test Case 1: Verify all of the application's executables contain an embedded manifest that define its execution level

To add a manifest to an executable, create a text file (the manifest) in the following format:

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="2.0.2.0" processorArchitecture="X86" name="Fascia"

type="win32"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

Save this file as <executablename>.exe.manifest. Use the tool mt.exe to add the manifest to your compiled executable. It is preferable to have Visual Studio run mt.exe after compilation, using a Post-Build event. The post-build command line should be:

plain
$(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe -manifest 
            $(ProjectDir)$(TargetName).exe.manifest
 -outputresource:$(TargetDir)$(TargetFileName);#1

The use of the generic $(ProjectDir) and $(TargetName) etc. mean that this command line may be copied "as is" to any project.

Saving Files

Fascia saves many files to different locations on the disk at runtime. As well as saving files that are created by the user, it also saves log files, debug files, configuration files and exception reports. There are a couple of rules that are enforced by the Vista certification tests and by Vista itself.

  • The application must only install files to the application folder (a sub-directory of Program Files) or the user's AppData directories by default. If the install is "per-machine", then there is no correct user AppData folder. In this case, user data must be written during the first run of the program instead of during installation.
  • At runtime, files should only be created in the (.NET)
    • Application.LocalUserAppDataPath (for user specific files) or in the
    • Application.CommonAppDataPath (for files that apply to all users), modified for Vista as follows:

C#
if(DataInterface.Controls.Vista.Global.RunningVistaOrLater()) publicDir =
Application.CommonAppDataPath.Replace("ProgramData", "Users\\Public");

For Fascia, this meant going through all the parts of the code that create or modify files and ensure that only these two locations were used. There was an exception to this rule with Fascia. Fascia has automatic update functionality, that results in application files in the "Program Files" directory being updated. This requires administrator privileges, so that part had to be separated into a separate executable marked with a "requireAdministrator" manifest.

Test Case 5: Verify application installed executables and files are signed (Req:1.3)

In order to sign the dlls, we had to obtain an Authenticode certificate from Verisign. This is comprised of a certificate file and a private key file. Use the SignTool.exe program to sign the dlls. We keep our files on a secure server and run a batch file as a post-build event to sign the dlls.

We have two third-party dlls that are not signed. You need to apply for a waiver for such files, by sending the waiver application form to swlogo@microsoft.com. There is a link on the "Innovate on Windows Vista" partner website to the waiver form (see useful resources).

Install/Uninstall

Our application installer was built using Visual Studio 2005. This ensures that it meets the first requirement; that it uses Windows Installer. Visual Studio compiles an installation project to a ".msi" file. A ".msi" file is actually a set of (database) tables that contain data. These raw tables and their contents can be viewed and edited using the "Orca.exe" tool.

Orca has some in-built verification tests that are performed in Test Case 12. These Internal Consistency Evaluators (ICE) ensured that our installation was clean and didn't, for example, attempt to install the same file twice.

Surprisingly, a ".msi" file created with Visual Studio won't pass all the tests for Vista certification. A number of changes need to be made using Orca. Fortunately, Orca can be used to create a transform file that can be applied each time you build the msi file to make the changes that are required. I created two transform files:

  • AddMsiRMFileInUse.mst – this creates a dialog required by Test Case 25 that handles files in use during installation. See this post in the MSDN forum for information about this.
  • VistaPatch2.mst - this does the following:
    • In table AdvtExecuteSequence, drop the only row which has a GUID and a condition set to UpgradeCode.
    • In table CustomAction, add this row: MyTargetDir, 51, ARPINSTALLLOCATION, [TARGETDIR]. This ensures that the install location is written to the registry for Test Case 19.
    • In table InstallExecuteSequence, add this row: MyTargetDir, , 798. This ensures that the install location is written to the registry for Test Case 19.
    See http://www.creativedocs.net/blog/ for information about this.

The transform files can be applied using msitran.exe from the Microsoft Platform SDK for Windows Server 2003 R2. Use the "-a" option as in the following example:

plain
Msitran.exe -a VistaPatch2.mst Fascia.msi

The final action that has to be taken is to sign the msi file with the Authenticode certificate.

Reliability

Test Case 30: Verify the application is Restart Manager Aware

If our application needs to be shutdown by Vista as a result of another installation or update, the Vista Restart Manager comes into play. Applications must register for a restart message when they start, using the following code:

C#
DataInterface.RestartManager.Global.RegisterApplicationForRestart
                        ("SomeCommandLineArgs");

When Fascia is restarted, it will be supplied with the command line arguments that were specified in the RegisterApplicationForRestart call.

Fascia responds to a restart message by saving state and allowing the shutdown to go ahead. To respond to a restart message, override the WndPrc function of the main window as shown in the following example:

C#
protected override void WndProc(ref Message m)
{
   base.WndProc(ref m);

   if(DataInterface.RestartManager.Global.IsRestartMessage(m))
   {
       //This application is about to be shut down, so save state...
       if (this.InvokeRequired)
           this.BeginInvoke(new MethodInvoker(this.SaveState));
       else
           this.SaveState();
   }
}

When Fascia restarts, it detects that it is a restart manager initiated restart by inspecting the command line arguments that were supplied when registering for a restart. The state is restored after the user logs in.

Test Case 31: Verify application does not break into a debugger with the specified AppVerifier checks

The most recent version of the test cases includes a note at the end of Test Case 31 that says that this test case is not applicable to fully managed applications. Since this note was not added until I had finished preparing Fascia for the certification, I spent time ensuring that Fascia would pass this test case anyway. It is reassuring that Fascia is stable enough to pass this test even if it is not a requirement of a managed application.

After fixing several areas of the code that caused a failure of this test, I found that my final failure was fixed by ensuring that the executable was built in release mode. It could be that for managed applications built in release mode; these tests won't fail, although I haven't verified this. I would suggest you build all your managed assemblies in release mode if you too want to see whether your application will pass this test.

There is a known issue if you have any images on your forms that have transparency. This test will fail if you do have such images. I had to replace the images that had transparent sections with ones that didn't have any transparency.

The test specifies that you use the "AppVerifier" tool to run certain automatic tests on the executable (Exceptions, Handles, Heaps, Locks, Memory and TLS from the Basics checks, DangerousAPIs and DirtyStacks from the Miscellaneous checks). The difficulty comes in debugging any of the errors, since the behaviour is very different when running from Visual Studio. I found that I could only narrow down the specific lines of code that were causing failures by using MessageBox and/or commenting out large areas of code. The failures were ultimately fixed by performing more null checking, especially in areas of code that were surrounded by empty exception handling such as:

C#
Try
{
    DoSomethingThatMightGoWrong();
}
Catch{}

Clearly this is bad programming practice anyway, but should certainly be avoided in applications that you want certified.

Test Case 32: Verify that the application only handles exceptions that are known and expected

The notable thing about this test is that if you build an application that has absolutely no try-catch blocks, it will pass. Exceptions must not be caught without being re-thrown, since they need to be thrown so that the Windows Error Reporting can pick them up. Fascia catches exceptions and sends them to a static function that can send a report to a central web service that allows us to diagnose customer problems efficiently. It uses a similar idea to Windows Error Reporting, but was causing the test to fail, since Windows Error Reporting must be used by an application that is a candidate for being certified for Vista. The solution was to call throw in the catch block of any exception handler, to ensure that the exceptions were rethrown. Incidentally, if you call throw(ex) where ex is a specific exception that has been caught, the test still fails. You must call throw with no arguments.

The application must be registered on the winqual site in order to be eligible for Windows Error Reporting.

The source code

.NET Assemblies to Assist in the Certification Process

I wrote two assemblies that wrap some of the functionalities that are required when preparing an application for Vista certification.

DataInterface.Controls.Vista

DataInterface.Controls.Vista contains controls that are specific to Vista, but also run on Windows XP. It contains two controls; the CommandLink and the ShieldButton. The CommandLink control allows you to create a Vista command link style button. These are the link buttons you see when Vista asks you to confirm elevation. You can see these by clicking on any Vista administrative task such as changing the system time and date. The ShieldButton control is a normal Windows button that has a ShowShield boolean property that indicates whether or not to display the Vista shield icon on the button. Useful static code in the assembly is contained in the Global class as follows:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace DataInterface.Controls.Vista
{
    /// <summary>
    /// Class that contains static functions for Vista control functionality
    /// </summary>
    public class Global
    {
        internal const int BS_COMMANDLINK = 0x0000000E;
        private const uint BCM_SETNOTE = 0x00001609;
        private const uint BCM_SETSHIELD = 0x0000160C;

        /// <summary>
        /// Override for SendMessage for setting the shield icon
        /// </summary>
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        internal static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, 
                        IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Override for SendMessage for setting note text
        /// </summary>
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, 
                        IntPtr wParam, string lParam);

        /// <summary>
        /// Shows or hides the Vista shield icon on the specifed control
        /// </summary>
        /// <param name="ctrl" />The control on which to display the shield</param />
        /// <param name="showShield" />Indicates whether to show or hide the shield</param />
        public static void SetShield(Control ctrl, bool showShield)
        {
            SendMessage(new HandleRef(ctrl, ctrl.Handle), BCM_SETSHIELD, 
        IntPtr.Zero, new IntPtr(showShield ? 1 : 0));
        }

        /// <summary>
        /// Shows command link style note text on the specified control
        /// </summary>
        /// <param name="ctrl" /></param />
        /// <param name="NoteText" /></param />
        public static void SetNote(Control ctrl, string NoteText)
        {
            SendMessage(new HandleRef(ctrl, ctrl.Handle), BCM_SETNOTE, 
                        IntPtr.Zero, NoteText);
        }

        /// <summary>
        /// Returns true if the operating system is Vista or later
        /// </summary>
        /// <returns></returns>
        public static bool RunningVistaOrLater()
        {
            return System.Environment.OSVersion.Version.Major > 5;
        }
    }
}

To show a shield icon on a button control, the FlatStyle has to be set to FlatStyle.System and the SetShield method needs to be called in the Global class. Use the RunningVistaOrLater function to check whether the application is running under Windows Vista before applying any of the Vista styles. The SetNote method is used to display the note on a CommandLink button.

In order to get the shield to show on a ShieldButton, the following two lines of code must be run when the application first starts:

C#
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

DataInterface.RestartManger

DataInterface.RestartManager contains code that is used to make an application "RestartManager aware". It wraps the required functionality from the "Kernel32.dll" API. Here is the code:

C#
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using DataInterface.Controls.Vista;

namespace DataInterface.RestartManager
{
    public class Global
    {
        private const Int32 WM_QUERYENDSESSION = 0x0011;
        private const Int32 ENDSESSION_CLOSEAPP = 0x1;

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern uint RegisterApplicationRestart
                (string pszCommandline, int dwFlags);

        /// <summary>
        /// Returns true if the specified windows message is a restart message
        /// </summary>
        /// <param name="msg" />The windows message to be checked</param />
        /// <returns>True if it is a restart message</returns>
        public static bool IsRestartMessage(System.Windows.Forms.Message msg)
        {
            bool ret = false;
            if (msg.Msg == Global.WM_QUERYENDSESSION)
            {
                if ((msg.LParam.ToInt32() & Global.ENDSESSION_CLOSEAPP) ==
                        Global.ENDSESSION_CLOSEAPP)
                    ret = true;
            }
            return ret;
        }

        /// <summary>
        /// Registers the currently running application for a restart message.
        /// </summary>
        /// <param name="restartCommandLine" />
    /// The application will be restarted with this command line string</param />
        public static void RegisterApplicationForRestart(string restartCommandLine)
        {
            //Can only do this in Vista
            if(DataInterface.Controls.Vista.Global.RunningVistaOrLater())
                RegisterApplicationRestart(restartCommandLine, 0);
        }
    }
}

VistaDemo

VistaDemo is a demo application that shows how to use the two assemblies described above. It demonstrates four aspects:
  • The RunningVistaOrLater() function is used in the form's constructor in order to determine the text to display on the label at the top of the form.
  • The ShieldButton. This is displayed on the form and has the ShowShield property set to true. This will display the shield icon when running in Vista.
  • The CommandLink button. This is displayed on the form and has the Note property set. This note will be visible when running in Vista.
  • The Restart Manager functionality. The application registers with the restart manager in the Main() function, using specific command line arguments. The Main() function checks the command line arguments to see if the application was started by the restart manager. The WndPrc function of the form is overridden in order to detect when the restart manager is closing the program. At this point, we should (quickly) save state.

Points of Interest

The hardest part of preparing Fascia for the certification was discovering all the information required. Hopefully this article has given you a headstart in preparing your application.

Useful Resources

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here