Building the Windows Experience Index Share Site – WEIshare.net

By now you've probably seen Larry's video in which he explores the WEI Share (“We” share) project and what it does. Now we're going to show you what went in to creating it, from building the desktop portion to grab the score to compiling the data to setting it up on Azure.

Creating the Silverlight App and Web Service – Peter B.

We originally set out to create a Silverlight Application that would display uploaded Windows Experience Index (WEI) Scores via a Web Service and Desktop Application. At the time, we created the application in Silverlight 3 and used a WCF Service to integrate with not only the desktop application but also the Silverlight application. Since we weren't sure exactly where the WCF Service was being hosted in relation to the Web Site, one of the things we did was make it so the location of the WCF Service can be fairly easily changed. We ended up going with Silverlight Parameters to pass in the location of the WCF Service. Doing this was fairly easy, but it took some set up. The first step involved modifying App.xaml.cs to the following:

private void Application_Startup(object sender, StartupEventArgs e)
{
    //Insert InitParams into Global Resources
    foreach (string key in e.InitParams.Keys)
    {
        this.Resources.Add(key, e.InitParams[key]);
    }
    this.RootVisual = new MainPage();
}

Once this was done, we then had to modify the code behind for the main Silverlight app in MainPage.xaml.cs, like so:

public MainPage()
{
    InitializeComponent();    if (App.Current.Resources.Contains("WCFServiceLocation")
        && !String.IsNullOrEmpty(App.Current.Resources["WCFServiceLocation"] as string))
    {
        EndpointAddress endpointAddress = new EndpointAddress(            App.Current.Resources["WCFServiceLocation"].ToString());
        serviceClient = new WindowsExperienceServiceClient(            "CustomBinding_IWindowsExperienceService", endpointAddress);
    }
    else
    {
        serviceClient = new WindowsExperienceServiceClient();
    }
    myDelay.Duration = TimeSpan.FromMilliseconds(2000);
    myDelay.Completed += new EventHandler(myDelayCompleted);
    myDelay.Begin();
    this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}

In the above example, I checked for the parameter, and finding that it existed I changed the Endpoint Address of the WCF Service (without an existing parameter, it uses the default address). After this was complete, we had to pass in a location similar to the following in the Default.aspx page's object tag for the Silverlight control:

<param name="initparams" value="WCFServiceLocation=http://<%=Request.Url.Host %>:8080/windowsexperienceservice.svc" />

This made changing the location of the WCF Service much easier, allowing the flexibility we later needed.

Within the WCF Service, we separated our code using a three layered approach utilizing separate folders for each layer “DataAccessLayer”, “BusinessLogicLayer”, and “BusinessEntities”. Within the BusinessEntities folder, we had the base classes containing each business entity. As an example, we had the WindowsAssessment class, which contained all of the properties of the data sent from the Desktop Application to the WCF Service, including scores. Below, you can see part of what this looked like:

[DataContract]
public class WindowsAssessment
{
    public Guid? WindowsAssessmentID { get; set; }
    [DataMember]
    public decimal? SystemScore { get; set; }
    [DataMember]
    public decimal? AverageScore { get; set; }
    [DataMember]
    public decimal? MemoryScore { get; set; }
    [DataMember]
    public decimal? CpuScore { get; set; }
    [DataMember]
    public decimal? CpuSubAggScore { get; set; }
    [DataMember]
    public decimal? VideoEncodeScore { get; set; }    /* additional properties... */
}

You'll notice above that [DataContract] and [DataMember] was set above the class and properties so that it could be used as part of the WCF Service.

Within the DataAccessLayer folder, we utilized Linq2Sql. We simply created a dbml file, and brought over the few assets we created in the database by dragging and dropping the tables from the Data Source into the diagram. Then, within the WindowsAssessment.cs file of the Data Access Layer, we created methods that used Linq2Sql for our basic CRUD, taking advantage of Linq to easily create paging that we can search, filter, and orderby. Finally, within the Business Logic Layer, we utilized the Business Entities and created different methods that called the DAL and also took advantage of HttpRuntime.Cache.

Once all of the layers were complete, the Desktop and Silverlight Application were free to communicate back and forth, allowing the Desktop App to send a score and the Silverlight App to display it.

Upgrading to Silverlight 4 and Azure

Unfortunately, after we completed all of this, Silverlight 4 released. We quickly moved to upgrade to Silverlight 4 and, while we were at it, also modify the app to utilize Windows Azure.

One of the first things I encountered was that the WCF Service was not going to be within the Web Site anymore. Azure has roles in which you create for your project. In this particular instance I needed 2 web roles, one for the website, one for the WCF. In the end my Solution looked like the following:

clip_image002[4]

Working with WCF as a WCF Role, I noticed that there were a few known issues, all of which were documented here: http://code.msdn.microsoft.com/wcfazure/Wiki/View.aspx?title=KnownIssues&referringTitle=Home. Luckily, with another developer's help, we were able to pinpoint some of the issues and once we addressed them, we really didn't have much more to worry about when working with WCF.

The next hurdle was moving over the database. The easiest way to do this is to use the tool, SQL Azure Migration Wizard, which can be found on CodePlex at http://sqlazuremw.codeplex.com/.

Creating the Desktop Experience – Peter J

I was tasked with creating a set of applications for showcasing user's Windows Experience Index (WEI) scores. The set of applications included a desktop application created using Windows Presentation Foundation (WPF) for retrieving the scores and other system data, and a web site using Silverlight for displaying the information.

I needed to create the desktop application. Accordingly, there were some initial pre-requisites and other considerations we had to deal with:

  • Limiting the desktop application to only run in Windows 7
  • Determining where the relevant WEI data is stored in the system
  • Retrieving the relevant information for all areas
  • Remote Service connectivity—in this case we used a WCF service
  • Not forcing the user to install/unzip anything (just download and run)

After scouring the web, I found a site (http://www.codeguru.com/cpp/w-p/system/systeminformation/article.php/c8973) with just the information I needed:

/// <summary>
/// Determines if the current version of windows is valid (Vista, 7, Server 2008)
/// </summary>
/// <returns></returns>
public static bool IsValidWindowsVersion()
{
    System.OperatingSystem osInfo = System.Environment.OSVersion;
    //6 - Vista, 7, Server 2008
    if (osInfo.Version.Major == 6)
    {
        //windows 7 = 1
        if(osInfo.Version.Minor == 1)
            return true;
    }    return false;
}

The next major hurdle to overcome was determining where all of the information for the system was stored.

I didn't really know where to start, so I started looking at MSDN and the search engines for the best way to get this information and I came across a term I had only heard in passing: WMI. Windows Managed Instruction (WMI) allows for querying the system for data, as well as other things that I did not use it for so we will skip all that. You can learn more on WMI on your own time here: http://msdn.microsoft.com/en-us/library/aa394582(VS.85).aspx. I also found a nifty tool from Microsoft called WMI Creator (http://www.microsoft.com/downloads/details.aspx?familyid=2cc30a64-ea15-4661-8da4-55bbc145c30e&displaylang=en) to help facilitate my search. I was blown away by the sheer amount of data and left with no idea of where to start. So, it was back to MSDN to figure out what happens when the Experience Index is run. This made my task much easier since the Experience Index creates its own XML files with most of the data I need.

I retrieved a list of files and used the one that was within a day of the current date/time. This was easy stuff, till I had to determine what to do if they didn't have an Experience Index. If they didn't, I'd just run the Experience Index on the fly. To do this, set the program to tell the user that they do not have a current enough score and that we'll update the information for them by running the Windows System Assessment Tool (WinSAT).

The initial version was not very good since it was modal and caused the application to not refresh until the Experience Index finished running. Needless to say, I was not happy. What I did do, though, was setup a child form that would launch the WinSAT and query every 5 seconds to see if it was still running. Here is the initial call to load the dialog windows and, if successful, check again for a valid Experience Index to post:

/// <summary>
/// Launches the dialog that handles launching WinSat and checking if it is running.
/// </summary>
private void SetupAndRunWinSAT()
{
    WinSatProgress progressWindow = new WinSatProgress();
    bool? dialogResult = progressWindow.ShowDialog();
    if (dialogResult.HasValue && dialogResult.Value)
        CheckForAndProcessScore();
}

Now we are getting into the good stuff, so I will touch on the actual running of the WinSAT and querying the system for a running process.

Launching the WinSAT is pretty simple since there is an environment variable that points to its path:

Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\WinSat.exe"

I only started a basic process and told it to run. I did not tell it to run behind the scenes because I wanted the user to be sure something was actually happening since the WinSAT can take any number of minutes depending on the system configuration. I also setup a global variable (WinsatExecutableExited) used elsewhere in the application to query to see if the process exited:

/// <summary>
/// Launches the winsat program
/// </summary>
public static bool RunWinSatProgram()
{
    bool isLaunched = false;
    WinsatExecutableExited = false;    System.Diagnostics.Process winSatProcess = new System.Diagnostics.Process();    winSatProcess.StartInfo.FileName = Helpers.Globals.WinSatExecutable;
    winSatProcess.StartInfo.Arguments = Helpers.Globals.WinSatExecutableParameters;
    winSatProcess.EnableRaisingEvents = true;    winSatProcess.Exited += new EventHandler(WinSatProcess_Exited);    try
    {
        winSatProcess.Start();
        isLaunched = true;
    }
    catch
    { }    return isLaunched;
}

You'll notice the exited event handler. I put that there so that when the process exited, it set the flag. However, I was having issues with it not always getting executed on some systems. To accommodate for this, I setup a DispatchTimer in the dialog window that would check every 5 seconds to see if the global WinsatExecutableExited flag was true or if the WinSAT process was still running. In order to check if the process is still running, I used WMI. Yes, yes I know—why, since I hadn't yet done much of anything with it, am I using WMI for this? Well, I won't bore you with details, but I found an example online and modified it to fit what I need (http://www.techimo.com/forum/applications-operating-systems/117464-need-vbulletin-script-check-see-if-process-running.html). Bet you weren't expecting that were you? (Or, maybe you were.) Long story short, I set up a method that I can pass a process name to, and it returns true/false if it is still running or null if it can't determine anything:

Neat, huh? That's what happens every time the dialog counts down until it either finishes or can't—for whatever reason—determine the status of the WinSAT.

Now that the file is generated, we want to get all of the data goodness out of it so we can post our data to the service. Since this is an XML file, it was easily loaded into a XElement and traversed as needed. Now we come to another sticky point, retrieving other system data not stored in the XML file. This is where the web and that WMI Creator application came in handy. I would look online to find what I needed, and then leveraged WMI Creator to generated my base code for me. I setup an object for the retrieved data and here's what it looks like to query the system for the memory type and speed:

/// <summary>
/// used to determine if the process is still running
/// http://www.techimo.com/forum/applications-operating-systems/117464-need-vbulletin-script-check-see-if-process-running.html
/// </summary>
/// <param name="processName"></param>
/// <returns></returns>
public static bool? IsProcessStillRunning(string processName)
{
    bool? response = null;    try
    {
        ManagementObjectSearcher searcher =
            new ManagementObjectSearcher("root\\CIMV2",
            "SELECT * FROM Win32_Process");
        foreach (ManagementObject queryObj in searcher.Get())
        {
            if(string.Compare(queryObj["Name"].ToString(), 
                processName, 
                StringComparison.OrdinalIgnoreCase) == 0)
            {
                response = true;
                break;
            }
        }        if(!response.HasValue)
            response = false;
    }
    catch (Exception ex)
    {
        System.Windows.MessageBox.Show(ex.Message + 
            Environment.NewLine + ex.StackTrace);
    }    return response;
}

Now is about where I have all of my information ready and need to send it to the server. Setting up the service connection for initial testing is no problem. It's a standard Windows Communication Foundation (WCF) service. But here comes the hard part—setting it up so the service does not need information from a config file. Back to the web we go, since I am like the other 100% lazy programmers who prefer to just add a Service reference because it is easy. Well, there are two parts to this: generating the code class and setting up the service connection information.

Generating the code class is easy—you use a command line to generate it. It was a little complex since I was generating it for asynchronous calls to the service, but this was nothing MSDN couldn't help me with. Speaking of, here's the documentation from MSDN: http://msdn.microsoft.com/en-us/library/ms730059.aspx

The other part was sending up the binding endpoint. Most of the information I needed was generated when I generated the code file form the service. Now I just needed to determine how to put it all together. MSDN to the rescue yet again: http://msdn.microsoft.com/en-us/library/ms733107.aspx

What I ended up doing was creating a CustomBinding and specifying the same information/objects the system would have created for me using the values in the config file:

/// <summary>
/// The wcf service reference member
/// </summary>
WindowsExperienceServiceClient experienceClient = null;CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(Helpers.Globals.CustomServiceBinding);
myBinding.Elements.Add(Helpers.Globals.CustomTransportBinding);
experienceClient = 
   new WindowsExperienceServiceClient(
      myBinding, 
      Helpers.Globals.CustomEndPoint);

The global bindings and endpoints:

/// <summary>
/// The binary encoding objct for the custom binding
/// </summary>
public static BinaryMessageEncodingBindingElement CustomServiceBinding
{
    get
    {
        BinaryMessageEncodingBindingElement binaryEncoding = new BinaryMessageEncodingBindingElement();
        binaryEncoding.MaxReadPoolSize = 64;
        binaryEncoding.MaxSessionSize = 2048;
        binaryEncoding.MaxWritePoolSize = 16;
        binaryEncoding.ReaderQuotas.MaxDepth = 32;
        binaryEncoding.ReaderQuotas.MaxStringContentLength = 8192;
        binaryEncoding.ReaderQuotas.MaxArrayLength = 16384;
        binaryEncoding.ReaderQuotas.MaxBytesPerRead = 4096;
        binaryEncoding.ReaderQuotas.MaxNameTableCharCount = 16384;        return binaryEncoding;
    }
}/// <summary>
/// The transport element for the custom binding
/// </summary>
public static HttpTransportBindingElement CustomTransportBinding
{
    get
    {
        HttpTransportBindingElement transportBinding = new HttpTransportBindingElement();
        transportBinding.ManualAddressing = false;
        transportBinding.MaxBufferPoolSize = 524288;
        transportBinding.MaxReceivedMessageSize = 65536;
        transportBinding.AllowCookies = false;
        transportBinding.AuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
        transportBinding.BypassProxyOnLocal = false;
        //decompressionEnabled="true" 
        transportBinding.HostNameComparisonMode =  HostNameComparisonMode.StrongWildcard;
        transportBinding.KeepAliveEnabled = true;
        transportBinding.MaxBufferSize = 65536;
        transportBinding.ProxyAuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
        transportBinding.Realm = string.Empty;
        transportBinding.TransferMode = TransferMode.Buffered;
        transportBinding.UnsafeConnectionNtlmAuthentication = false;
        transportBinding.UseDefaultWebProxy = true;        return transportBinding;
    }
}/// <summary>
/// the endpoint used for the service communication
/// </summary>
public static EndpointAddress CustomEndPoint
{
    get
    {
        return new EndpointAddress(Properties.Resources.WEIServiceEndpoint);    }
}

Yes, it is just that easy. All I had to do after that was setup the call completed handler and then call the service method and then it was all done.

Conclusion

This was a really fun and challenging project for us. While we'd worked extensively in Silverlight, the desktop development was relatively new to us. After getting our bearings, we found it pretty simple to translate our Web development skills to the desktop environment with WPF.

If you want to try this out, the download link for the source code is at the top of the article!

About The Authors

Lincoln Anderson, Peter Brownstein, and Peter Juchniewicz are all developers at web design company 352 Media Group. The company, which focuses on emerging technologies like Microsoft Silverlight Design, Windows Phone 7 App Development, and Microsoft Surface Development, has worked on other cool tools like the Coding4Fun Windows Phone 7 T-Shirt Cannon and the Tweetcraft WOW Twitter client.

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.