Skip to content

Commit

Permalink
Merge pull request #15 from AttackPattern/detect-win8-without-hal
Browse files Browse the repository at this point in the history
Improve Windows 8 version detect so it's not dependent upon a HAL.
  • Loading branch information
damieng committed Mar 29, 2014
2 parents 03bbc10 + 8dbce5e commit c9b5e2b
Showing 1 changed file with 25 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ namespace CSharpAnalytics
/// Microsoft doesn't really want you getting this information and makes it difficult.
/// The techniques used here are not bullet proof but are good enough for analytics.
/// Do not use these methods or techniques for anything more important than that.
/// (Note that this class was also published as SystemInfoEstimate on our blog)
/// </remarks>
public static class WindowsStoreSystemInformation
{
private const string ModelNameKey = "System.Devices.ModelName";
private const string ManufacturerKey = "System.Devices.Manufacturer";
private const string DeviceClassKey = "{A45C254E-DF1C-4EFD-8020-67D146A850E0},10";
private const string DisplayPrimaryCategoryKey = "{78C34FC8-104A-4ACA-9EA4-524D52996E57},97";
private const string DeviceDriverKey = "{A8B865DD-2E3D-4094-AD97-E593A70C75D6}";
private const string DeviceDriverVersionKey = DeviceDriverKey + ",3";
private const string DeviceDriverProviderKey = DeviceDriverKey + ",9";
private const string RootContainer = "{00000000-0000-0000-FFFF-FFFFFFFFFFFF}";
private const string RootContainerQuery = "System.Devices.ContainerId:=\"" + RootContainer + "\"";
private const string HalDeviceClass = "4d36e966-e325-11ce-bfc1-08002be10318";

/// <summary>
/// Build a system user agent string that contains the Windows version number
Expand Down Expand Up @@ -58,7 +57,7 @@ public static async Task<string> GetSystemUserAgent()
/// Format a ProcessorArchitecture as it would be expected in a user agent of a browser.
/// </summary>
/// <returns>String containing the format processor architecture.</returns>
private static string FormatForUserAgent(ProcessorArchitecture architecture)
static string FormatForUserAgent(ProcessorArchitecture architecture)
{
switch (architecture)
{
Expand Down Expand Up @@ -96,6 +95,7 @@ public static ProcessorArchitecture GetProcessorArchitecture()
/// <summary>
/// Get the name of the manufacturer of this computer.
/// </summary>
/// <example>Microsoft Corporation</example>
/// <returns>The name of the manufacturer of this computer.</returns>
public static async Task<string> GetDeviceManufacturerAsync()
{
Expand All @@ -106,6 +106,7 @@ public static async Task<string> GetDeviceManufacturerAsync()
/// <summary>
/// Get the name of the model of this computer.
/// </summary>
/// <example>Surface with Windows 8</example>
/// <returns>The name of the model of this computer.</returns>
public static async Task<string> GetDeviceModelAsync()
{
Expand All @@ -116,6 +117,7 @@ public static async Task<string> GetDeviceModelAsync()
/// <summary>
/// Get the device category this computer belongs to.
/// </summary>
/// <example>Computer.Desktop, Computer.Tablet</example>
/// <returns>The category of this device.</returns>
public static async Task<string> GetDeviceCategoryAsync()
{
Expand All @@ -130,38 +132,29 @@ public static async Task<string> GetDeviceCategoryAsync()
/// <returns>Version number of Windows running on this computer.</returns>
public static async Task<string> GetWindowsVersionAsync()
{
// There is no good place to get this. The HAL driver version number will work
// unless you're using a custom HAL... We could try three different places in the
// future (e.g. USB drivers, System timer) and let it tie-break.
var halDevice = await GetHalDevice(DeviceDriverVersionKey);
if (halDevice == null || halDevice.Properties[DeviceDriverVersionKey] == null) return null;

var versionParts = halDevice.Properties[DeviceDriverVersionKey].ToString().Split('.');
return string.Join(".", versionParts.Take(2).ToArray());
// There is no good place to get this so we're going to use the most popular
// Microsoft driver version number from the device tree.
var requestedProperties = new[] { DeviceDriverVersionKey, DeviceDriverProviderKey };

var microsoftVersionedDevices = (await PnpObject.FindAllAsync(PnpObjectType.Device, requestedProperties, RootContainerQuery))
.Select(d => new { Provider = (string)d.Properties.GetValueOrDefault(DeviceDriverProviderKey),
Version = (string)d.Properties.GetValueOrDefault(DeviceDriverVersionKey) })
.Where(d => d.Provider == "Microsoft" && d.Version != null)
.ToList();

var versionNumbers = microsoftVersionedDevices
.GroupBy(d => d.Version.Substring(0, d.Version.IndexOf('.', d.Version.IndexOf('.') + 1)))
.OrderByDescending(d => d.Count())
.ToList();

var confidence = (versionNumbers[0].Count() * 100 / microsoftVersionedDevices.Count);
return versionNumbers.Count > 0 ? versionNumbers[0].Key : "";
}

/// <summary>
/// Attempt to find the HAL (Hardware Abstraction Layer) device for this computer.
/// </summary>
/// <param name="properties">Additional property names to obtain for the HAL.</param>
/// <returns>PnpObject of the HAL with the additional properties populated.</returns>
private static async Task<PnpObject> GetHalDevice(params string[] properties)
{
var actualProperties = properties.Concat(new[] { DeviceClassKey, DeviceDriverProviderKey });
var rootDevices = (await PnpObject.FindAllAsync(PnpObjectType.Device, actualProperties, RootContainerQuery));
return rootDevices.FirstOrDefault(rootDevice => IsMicrosoftHal(rootDevice.Properties));
}

/// <summary>
/// Determine if this device represents a Microsoft-provided HAL.
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
private static bool IsMicrosoftHal(IReadOnlyDictionary<string, object> properties)
static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
{
return
properties.Any(p => p.Value.ToString().Equals(HalDeviceClass) && p.Key.Equals(DeviceClassKey.Replace(',', ' '))) &&
properties.Any(p => p.Key.Equals(DeviceDriverProviderKey.Replace(',', ' ')) && p.Value.Equals("Microsoft"));
TValue value;
return dictionary.TryGetValue(key, out value) ? value : default(TValue);
}

[DllImport("kernel32.dll")]
Expand Down

0 comments on commit c9b5e2b

Please sign in to comment.