Tuesday, December 12, 2006 6:31 AM bart

Windows Vista - Exploring the Windows System Assessment Tool (WinSAT) API in C#

Introduction

Time for another Windows SDK adventure in Windows Vista: enter the WinSAT API. WinSAT stands for Windows System Assessment Tool and is described as follows in the SDK:

Windows System Assessment Tool (WinSAT) assesses the performance characteristics and capabilities of a computer. Developers can use this API to develop software that can access the performance and capability information of the computer to determine the optimal application settings based on that computer's performance capabilities.

The capabilites also indicates what scenarios and applications will perform well on the computer. For example, if a software package contains a rating on its packaging, a user can use the rating on the software package and the computer's capability rating to determine if the package will run well on the computer.

I think a couple of screenshots will clarify a lot (click to enlarge):

The numeric scores like 3.7 are exactly what WinSAT delivers to you. The idea of WinSAT is to assess the system's capabilities using a simple numeric score: the higher, the better. This enables some scenarios:

  • Allowing end users to see the bottlenecks in the system; e.g. if the lowest score is the video card, that might be the first thing to consider for an upgrade.
  • Comparing machines to each other; also useful for computer manufactures to make promotion based on this score.
  • Software packages can indicate a minimal required score to run properly on the product box.
  • Programmers can query the various rating scores available to dynamically adapt the behavior of the app, for example by disabling the graphical effects if the graphical score is too low.

 

Retrieving WinSAT scores in managed code

In this post, we'll focus on retrieving WinSAT scores in managed code, in order to use these scores to make decisions in our application (bullet 3 in the list above). I'll only show how to retrieve scores, not how to interpret scores nor how to use it in decision making (which will be different in every app and should be relatively straightforward).

WinSAT is defined in %windir%\system32\WinSATAPI.dll as a COM-based library. Documentation is available in the Windows SDK and online on MSDN. We'll focus on the following interfaces:

  • IProvideWinSATResultsInfo - Gets information about an assessment, e.g. the Windows Capability rating (see screenshots above in the Introduction paragraph). We'll use this as our starting point. It contains properties to query the assessment state, assessment date and time, a friendly description of the assessment and the score information. Using the GetAssessmentInfo, detailed information can be obtained, represented as a IProvideWinSATAssessmentInfo object (see below).
  • IProvideWinSATAssessmentInfo - Gets information about an assessment (e.g. graphics, cpu, memory, disk, etc). Three properties are defined: Description, Score and Title.
  • IProvideWinSATVisuals - Used to retrieve a bitmap that represents a score graphically, as shown below. This image can be used in the application to present scores in a user-friendly and well-known fashion (since these images will appear on software packages, with downloads online, on computer systems, etc).

For sake of completeness, I've copied the winsatcominterface.h definitions of these interfaces below:

MIDL_INTERFACE("0CD1C380-52D3-4678-AC6F-E929E480BE9E")
IProvideWinSATAssessmentInfo : public IDispatch
{
public:
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_Score(
        /* [retval][out] */ __RPC__out float *score) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_Title(
        /* [string][retval][out] */ __RPC__deref_out_opt_string BSTR *title) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_Description(
        /* [string][retval][out] */ __RPC__deref_out_opt_string BSTR *description) = 0;
};

MIDL_INTERFACE("F8334D5D-568E-4075-875F-9DF341506640")
IProvideWinSATResultsInfo : public IDispatch
{
public:
    virtual /* [id] */ HRESULT STDMETHODCALLTYPE GetAssessmentInfo(
        /* [in] */ WINSAT_ASSESSMENT_TYPE assessment,
        /* [retval][out] */ __RPC__deref_out_opt IProvideWinSATAssessmentInfo **ppinfo) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_AssessmentState(
        /* [retval][out] */ __RPC__out WINSAT_ASSESSMENT_STATE *state) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_AssessmentDateTime(
        /* [retval][out] */ __RPC__out VARIANT *fileTime) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_SystemRating(
        /* [retval][out] */ __RPC__out float *level) = 0;
    virtual /* [propget][id] */ HRESULT STDMETHODCALLTYPE get_RatingStateDesc(
        /* [retval][out] */ __RPC__deref_out_opt BSTR *description) = 0;
};

MIDL_INTERFACE("A9F4ADE0-871A-42a3-B813-3078D25162C9")
IProvideWinSATVisuals : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE get_Bitmap(
        /* [in] */ WINSAT_BITMAP_SIZE bitmapSize,
        WINSAT_ASSESSMENT_STATE state,
        float rating,
        /* [out] */ __RPC__deref_out_opt HBITMAP *pBitmap) = 0;
};

Time to open Visual Studio 2005 and to create a Console Application in C#, named WinSATDemo. Next, add a few references:

  • To System.Drawing (for the "visuals" to convert the HBITMAP to a .NET BCL Bitmap).
  • To System.Windows.Forms (right, we'll do a little Windows Forms programming, but the core of the app will be a console app).
  • To the COM library for WinSAT in %windir%\system32\WinSATAPI.dll:

The result should look like this in the Solution Explorer:

Finally, you'll need to change the Build properties of the project to allow unsafe code (explanation will follow later):

On to the code now. We'll use the WINSATLib which was imported from a COM library which is relatively straightforward once you know the meaning of the various interfaces as well as the generated classes. Here we go:

1 using System; 2 using System.Collections; 3 using System.Drawing; 4 using System.Runtime.InteropServices; 5 using System.Windows.Forms; 6 using WINSATLib; 7 8 namespace WinSATDemo 9 { 10 /// <summary> 11 /// Demo of the WinSAT technology in Windows Vista. Shows how to retrieve system assessment information in managed code. 12 /// </summary> 13 class Program 14 { 15 /// <summary> 16 /// Entry point of the demo application. 17 /// </summary> 18 /// <remarks>A single-threaded apartment state is required for the COM interop. Don't remove the [STAThread] attribute.</remarks> 19 [STAThread] 20 static void Main() 21 { 22 // 23 // Provides access to assessment state. 24 // 25 CQueryWinSATClass q = new CQueryWinSATClass(); 26 27 // 28 // Check for valid state. 29 // 30 if (q.Info.AssessmentState == WINSAT_ASSESSMENT_STATE.WINSAT_ASSESSMENT_STATE_VALID 31 || q.Info.AssessmentState == WINSAT_ASSESSMENT_STATE.WINSAT_ASSESSMENT_STATE_INCOHERENT_WITH_HARDWARE) 32 { 33 // 34 // Some formatting stuff. 35 // 36 string format = "{0,-18} {1,-56} {2:N1}"; 37 string seperator = String.Format(format, new string('-', 18), new string('-', 56), new string('-', 3)); 38 39 // 40 // General rating information. 41 // 42 Console.WriteLine("{0} on {1}", q.Info.RatingStateDesc, q.Info.AssessmentDateTime); 43 Console.WriteLine(); 44 45 // 46 // Get ratings for individual assessment types. 47 // 48 Console.WriteLine(format, "Category", "Description", "Rat"); 49 Console.WriteLine(seperator); 50 IEnumerator e = Enum.GetValues(typeof(WINSAT_ASSESSMENT_TYPE)).GetEnumerator(); 51 while (e.MoveNext()) 52 { 53 IProvideWinSATAssessmentInfo i = q.Info.GetAssessmentInfo((WINSAT_ASSESSMENT_TYPE)e.Current); 54 Console.WriteLine(format, i.Title, i.Description, i.Score); 55 } 56 57 // 58 // Overall system base score. 59 // 60 Console.WriteLine(seperator); 61 Console.WriteLine(format, "Base score:", "Determined by lowest subscore", q.Info.SystemRating); 62 } 63 64 // 65 // Get bitmap with assessment rating figure in WinSAT style. 66 // 67 IntPtr t; 68 unsafe 69 { 70 /* 71 * get_Bitmap has the following unmanaged signature: 72 * HRESULT get_Bitmap( 73 * WINSAT_BITMAP_SIZE bitmapSize, 74 * WINSAT_ASSESSMENT_STATE state, 75 * float rating, 76 * HBITMAP* pBitmap 77 * ); 78 * where HBITMAP is defined as (WinDef.h): 79 * typedef HANDLE HBITMAP; 80 * 81 * The last parameter gets translated into an IntPtr which is the pointer (&t) to the pointer (t) to the bitmap. 82 */ 83 CProvideWinSATVisualsClass v = new CProvideWinSATVisualsClass(); 84 v.get_Bitmap(WINSAT_BITMAP_SIZE.WINSAT_BITMAP_SIZE_NORMAL, q.Info.AssessmentState, q.Info.SystemRating, new IntPtr(&t)); 85 } 86 87 // 88 // Display a quick-n-dirty dialog box with the WinSAT assessment rating bitmap. 89 // 90 if (t != IntPtr.Zero) 91 { 92 Form frm = new Form(); 93 frm.Text = "WinSAT Bitmap"; 94 PictureBox pict = new PictureBox(); 95 pict.SizeMode = PictureBoxSizeMode.AutoSize; 96 pict.Image = Bitmap.FromHbitmap(t); 97 frm.Controls.Add(pict); 98 frm.ShowDialog(); 99 } 100 } 101 } 102 }

A little explanation of various things:

  • Line 19 - [STAThread] - To use to COM library, you need to set this attribute, otherwise you'll end up with an InvalidCastException, like this: Unable to cast COM object of type 'WINSATLib.CQueryWinSATClass' to interface type 'WINSATLib.IQueryRecentWinSATAssessment'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{F8AD5D1F-3B47-4BDC-9375-7C6B1DA4ECA7}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
  • Line 25 - CQueryWinSATClass - This class provides access to the "recent" WinSAT assessment results, it's defined as follows by the COM import and implements IQueryRecentWinSATAssessment:
  • using System; using System.Runtime.InteropServices; namespace WINSATLib { [TypeLibType(2)] [ClassInterface(0)] [Guid("F3BDFAD3-F276-49E9-9B17-C474F48F0764")] public class CQueryWinSATClass : IQueryRecentWinSATAssessment, CQueryWinSAT { public CQueryWinSATClass(); [DispId(2)] public virtual IProvideWinSATResultsInfo Info { get; } [DispId(1)] public virtual IXMLDOMNodeList get_xml(string xPath, string namespaces); } }

    Notice the get_xml stuff which can be used to query the XML describing the assessment. Basically, assessments are kept as XML documents in %windir%\Performance\WinSAT\DataStore which can be used to query much much more detailed information about the system. I've uploaded my system's assessment information (ran during post-setup phase of Vista installation) as an example over here.

  • Line 30, 31 - WINSAT_ASSESSMENT_STATE - This enumeration contains possible states of the assessment that was queried (through q.Info, which is of type IProvideWinSATResultsInfo). Information in the SDK tells the following about the various values:

    WINSAT_ASSESSMENT_STATE_MIN
  • Minimum enumeration value for this enumeration. WINSAT_ASSESSMENT_STATE_UNKNOWN The current state of the assessment is unknown. WINSAT_ASSESSMENT_STATE_VALID The current assessment data is valid for the current computer configuration. WINSAT_ASSESSMENT_STATE_INCOHERENT_WITH_HARDWARE The hardware has changed configuration since the last time a formal assessment was run but the data is still present and should be displayed. WINSAT_ASSESSMENT_STATE_NOT_AVAILABLE No data is available because a formal WinSAT assessment has not been run on this computer. WINSAT_ASSESSMENT_STATE_INVALID The assessment data is not valid. WINSAT_ASSESSMENT_STATE_MAX Minimum enumeration value for this enumeration.

    which explains why we're only interested in the VALID and INCOHERENT_WITH_HARDWARE states. Needless to say, you'll need a rescue net for other cases as well.
  • Line 36, 37 - String formatting to display results in columns: {0,-18} means left alignment and padding to 18 characters in total; {2:N1} means displaying a numeric value with one decimal. This format string is used on line 37 to create a separator and on lines 54 and 61 to display assessment information in columns.
  • Line 42 - Over here the date/time of the assessment and the description are displayed.
  • Line 50-55 - WINSAT_ASSESSMENT_TYPE - Contains all types of assessments done by WinSAT, which can be used to query for subscore information using GetAssessmentInfo (line 53). The enumeration logic makes it possible to iterate over all possible enum values in a relatively straightforward fashion.
  • Line 61 - Displays the overall base score of the system, in a "result row" of our table.
  • Line 67-85 - Unsafe code to retrieve the bitmap with the score indicator using get_Bitmap. Recall the definition of the get_Bitmap method in the .h file:

        virtual HRESULT STDMETHODCALLTYPE get_Bitmap(
            /* [in] */ WINSAT_BITMAP_SIZE bitmapSize,
            WINSAT_ASSESSMENT_STATE state,
            float rating,
            /* [out] */ __RPC__deref_out_opt HBITMAP *pBitmap) = 0;


    The last parameter is an output parameter with a pointer to an HBITMAP, which on its turn is defined as a typedef (= synonym) for a HANDLE. Essentially, this means that we pass in a pointer to a pointer which will be changed by the WinSAT library to point to the bitmap in memory. Look at this piece of pseudocode if you don't fully get it:

    void *t = NULL; // t -> NULL
    void **p = &t; // p -> t

    getBitmap(bla, bla, bla, p); // now, for instance,  t -> 0x12345678 which contains the bitmap


    Internally, getBitmap does something like this:

    // lots of stuff omitted
    *pBitmap = pSomeBitmap; // pSomeBitmap is the pointer to the created bitmap


    We need unsafe code because we need to grab the address of a pointer (t in the pseudo-code above == t on line 67) which is done on line 84 using &t. If the call to get_Bitmap succeeds (not checked in the code; I know it's dirty coding), t will point to a location in memory where the HBITMAP lives.
  • Line 90 - Make sure the bitmap pointer was changed by the get_Bitmap call, so that we can display the image.
  • Line 92-98 - Straightforward Windows Forms code to show a form with a PictureBox control to display the image. Notice the conversion of the HBITMAP to a System.Drawing.Bitmap using the System.Drawing.Bitmap.FromHbitmap factory method.

This is what the result looks like on my machine (compare to the scores and image in the introduction pictures):

Code can be downloaded here.

Happy coding!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# WinSAT from C#

Tuesday, December 12, 2006 7:50 AM by Daniel Moth

WinSAT from C#

# Leveraging Windows Vista's Windows System Assessment Tool (WinSAT) API in Visual Basic

Wednesday, December 13, 2006 11:54 AM by AddressOf.com

Introduction There are a lot of new features in Windows Vista. In this installment, we&rsquo;ll explore

# Leveraging Windows Vista's Windows System Assessment Tool (WinSAT) API in Visual Basic

Wednesday, December 13, 2006 12:09 PM by AddressOf.com

A couple of days ago a noticed this article, downloaded the code, thought about it for a bit and realized

# Windows Vista - Exploring the Windows System Assessment Tool (WinSAT) API in C# (some reactions)

Wednesday, December 13, 2006 2:59 PM by B# .NET Blog

Yesterday I published a blog post about the WinSAT API in Windows Vista . It's always great to see others