|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWith 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 " Overview of the TestsFortunately, 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 CompatibilityUser Account Control (UAC) and ElevationVista 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 " Test Case 1: Verify all of the application's executables contain an embedded manifest that define its execution levelTo add a manifest to an executable, create a text file (the manifest) in the following format: <?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: $(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe -manifest
$(ProjectDir)$(TargetName).exe.manifest
-outputresource:$(TargetDir)$(TargetFileName);#1
The use of the generic Saving FilesFascia 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.
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 " 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/UninstallOur 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:
The transform files can be applied using msitran.exe from the Microsoft Platform SDK for Windows Server 2003 R2. Use the " 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. ReliabilityTest Case 30: Verify the application is Restart Manager AwareIf 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: DataInterface.RestartManager.Global.RegisterApplicationForRestart
("SomeCommandLineArgs");
When Fascia is restarted, it will be supplied with the command line arguments that were specified in the Fascia responds to a restart message by saving state and allowing the shutdown to go ahead. To respond to a restart message, override the 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 checksThe 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 " 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 expectedThe 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 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 ProcessI wrote two assemblies that wrap some of the functionalities that are required when preparing an application for Vista certification. DataInterface.Controls.Vista
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>
/// The control on which to display the shield
/// Indicates whether to show or hide the shield
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>
///
///
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 In order to get the shield to show on a Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
DataInterface.RestartManger
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>
/// The windows message to be checked
/// <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);
}
}
}
VistaDemoVistaDemo is a demo application that shows how to use the two assemblies described above. It demonstrates four aspects:
Points of InterestThe 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
| ||||||||||||||||||||