Windows 10 的 Windows 体验分数





5.00/5 (13投票s)
Windows 10 的替代 Windows 体验分数工具
引言
我启动这个项目是因为我想检查我当前 Windows 10 Pro 安装中的 Windows 体验评分,并且意识到用于显示该评分的系统工具已不再包含在操作系统中。然而,基准测试工具 WinSAT.exe 仍然存在于 %windows%system32 目录中。它可以通过命令行使用“formal”参数运行来生成新的基准测试,该基准测试存储在 Windows\Performance\WinSAT\DataStore 目录下的 *.xml 文件中。可以通过读取 XML 代码来查看结果,并且在线上有多种方法可以在管理工具中查看结果,例如。虽然其他人已经编写了模仿旧 Windows 评分显示的查看器,但我希望自己编写一个,以便更好地学习如何处理 XML 文件,并作为一个编程练习。
背景
Windows 体验评分是 Windows Vista 中首次出现的内置系统基准测试。虽然有许多更复杂的基准测试工具,但它仍然可以对整个系统组件进行快速(约 1 分钟)测试,并对整个 PC 给出汇总分数,而且该功能包含在操作系统中是免费的。虽然以前版本的 Windows 可以按需显示此分数,但查看该工具在 Windows 10 中消失了。
Using the Code
我编写的程序使用一个简单的类将 *.xml 数据文件的内容转换为一个扁平数据库,该数据库可以作为一个 List<>
对象进行操作。它使用 XmlReader
类沿 XML 字符串的节点树进行导航,将每个节点存储为一个“xitem
”对象,其中包含一个标题字符串(如果存在,则为上一级层次结构中的节点名称)、一个名称字符串和一个数据字符串(即“value
”属性)。由于 XML 节点也可能包含属性字符串,因此这些属性存储在 xitem
的内部 List
中,其中每个属性都有一个名称和一个值(使用 xattribute class
)。WinSAT 基准测试中的相关数据主要存储在文本节点类型中,但 XmlNodeTypes
,如 XmlDeclaration
、DocumentType
、EntityReference
、Comment
、ProcessingInstruction
、CDATA
和 Comment
也存储在数据库中。使用后进先出(LIFO)堆栈来保存每个节点的名称,当遇到 EndElement XmlNode.Type
时,该节点将从堆栈中“弹出”,从而在数据库中保留文档的层次结构顺序。一旦 XML 文件被解析,就可以使用两个方法来搜索特定的目标数据:string
GetdataValue(string header, string name)
和 string
GetAttributeValue(string header, string name, int number)
。当搜索 List<xitem>
对象时,方法 bool ItemContainsData(int itemnumber)
非常有用。例如,对于 WinSAT XML 文件,可以通过以下方式读取整体系统分数:string systemscore = GetDataValue("WinSPR","SystemScore")
,并通过以下方式读取评估运行的 date
和 time
:string lastupdatetext = GetAttributeValue("SystemEnvironment","ExecDateTOD",1)
。这种方法应适用于存储数据的任何 XML 文件,但请注意,名称和值的排列方式并非标准化,因此在读取 XML 源作为文本文件以定位您想要处理的数据后,必须针对每个应用程序进行自定义。该程序将从 XML 中提取的基准测试结果显示为旧版本 Windows 中的样式。“重新运行基准测试”按钮会生成一个新进程,从命令行运行 WinSAT.exe 以生成新的基准测试 *.xml 文件。进程完成后,一个监视 DataStore
目录的后台 FileSystemWatcher
会通知主窗体,主窗体然后更新。它还会收集 DataStore
目录中所有旧评估的列表,以便根据需要进行查看和比较。
这是用于将 XML 节点存储在 List<>
对象中的两个类
/// <summary>
/// XATTRIBUTE CLASS
/// Stores an xml attribute
/// </summary>
[Serializable]
public class xattribute
{
public string aname;
public string avalue;
}
/// <summary>
/// XITEM CLASS
/// Stores xml node
/// </summary>
[Serializable]
public class xitem
{
public string header;
public string name;
public List<xattribute> attributes = new List<xattribute>();
public string data;
}
此方法使用 XmlReader
类导航基准测试 XML 源文件的节点,并将其转换为 xitem
对象列表。
// GETXMLFROM FILE()
// Fills in itemlist "XI" from specified file
private void GetXmlFromFile(string filename)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
XmlReader reader = XmlReader.Create(filename, settings);
XI.Clear();
int count = 0;
int x = 0;
bool AddToXi = false;
while (reader.Read())
{
xitem dataitem = new xitem();
AddToXi = false;
switch (reader.NodeType)
{
case XmlNodeType.Element:
dataitem.name = reader.Name.ToString();
if (!HeaderStack.IsEmpty())
{
dataitem.header = HeaderStack.GetLastItem(); // current header
HeaderStack.Push(dataitem.name); // header next subnode
// is this item's name
}
else
{
dataitem.header = "";
HeaderStack.Push(dataitem.name); // header next subnode
// is this item's name
}
dataitem.data = reader.Value; // prior code data="";
AddToXi = true;
break;
case XmlNodeType.Text:
XI[XI.Count - 1].data = reader.Value.ToString();
break;
case XmlNodeType.CDATA:
XI[XI.Count - 1].data = reader.Value.ToString();
break;
case XmlNodeType.ProcessingInstruction:
dataitem.name = reader.Name;
dataitem.data = reader.Value;
dataitem.header = "PROCESSING INSTRUCTION";
AddToXi = true;
break;
case XmlNodeType.Comment:
dataitem.name = reader.Name;
dataitem.data = reader.Value;
dataitem.header = "COMMENT";
AddToXi = true;
break;
case XmlNodeType.XmlDeclaration:
dataitem.header = "XML DECLARATION";
dataitem.name = reader.Name;
dataitem.data = reader.Value;
AddToXi = true;
//sb.Append("<?xml version='1.0'?>");
break;
case XmlNodeType.Document:
break;
case XmlNodeType.DocumentType:
dataitem.name = "<!DOCTYPE " + reader.Name + " " + reader.Value;
dataitem.header = "";
dataitem.data = reader.Value.ToString();
AddToXi = true;
break;
case XmlNodeType.EntityReference:
dataitem.name = "ENTITY REFERENCE";
dataitem.data = reader.Name.ToString();
dataitem.header = "";
AddToXi = true;
break;
case XmlNodeType.EndElement:
HeaderStack.Pop();
break;
}
if (reader.HasAttributes)
{
for (x = 0; x < reader.AttributeCount; x++)
{
reader.MoveToAttribute(x);
xattribute xa = new xattribute();
xa.aname = reader.Name;
xa.avalue = reader.Value;
dataitem.attributes.Add(xa);
}
}
if (AddToXi)
{
XI.Add(dataitem);
count++;
}
}
}
一旦进入 xitem
的 List
,就可以使用以下方法搜索特定信息以进行显示
// GET DATA VALUE FROM XML NODE IN LIST
private string GetDataValue(string header, string name)
{
string result = "";
int x = 0;
for (x = 0; x < XI.Count; x++)
{
if (XI[x].header == header && XI[x].name == name)
{
result = XI[x].data;
}
}
return result;
}
和
// GET ATTRIBUTE FROM XML NODE IN LIST
private string GetAttributeValue(string header, string name, int number)
{
string result = "";
int x = 0;
for (x = 0; x < XI.Count; x++)
{
if (XI[x].header == header && XI[x].name == name)
{
if (XI[x].attributes.Count >= number)
{
result = XI[x].attributes[number - 1].avalue;
break;
}
}
}
return result;
}
关注点
系统文件重定向处理
编写此代码是我第一次接触 Windows 32/64 位文件重定向问题。如果您不了解此过程,您将因为即使可以在资源管理器中看到文件,但仍无法以编程方式读取或运行 64 位 Windows 10 安装的 system32 目录中的系统文件(如 WinSAT.exe)而感到困惑。发生这种情况是因为,通常情况下,64 位 Windows 10 安装中的 %system32% 中的系统文件实际上是 32 位文件的 64 位版本,其文件名与 32 位安装中的文件名相同。如果您在 64 位 Windows 10 环境中运行 32 位应用程序,当您尝试访问 %system32% 目录时,操作系统会假设您实际上想要 32 位版本的任何系统 EXE 或 DLL,并将您的程序“重定向”到 SysWow64 文件夹,其中 32 位版本的操作系统系统文件已移至 64 位 Windows 中。然而,WinSAT.exe 只存在于一个位置,即原始的 %system32% 目录。出于某种原因,它未在 SysWow64
中复制。因此,尝试使用 File.Exists("C:\\Windows\\system32\\WinSAT.exe")
或 Process.StartInfo
访问它将失败。以 64 位模式运行的 Windows 10 提供了一个“虚拟”文件夹“sysnative”,您可以使用它来代替“system32”来访问旧的 32 位系统文件文件夹,但我发现这很难处理。
虽然您可以简单地将 WinSAT.exe 复制到另一个非重定向文件夹,但最简单的方法是,无论它位于哪个普通文件夹中,都可以访问它,具体取决于运行的 Windows 10 版本,方法是禁用文件夹重定向(如果当前环境是 64 位 Windows),使用此代码片段来确定正在运行哪个版本
private static bool is64BitProcess = (IntPtr.Size == 8); // true if running as a 64bit process.
private static bool is64BitOperatingSystem =
is64BitProcess || InternalCheckIsWow64(); // true if current Windows OS is 64bit,
// used for redirection handling
// Detect 32 or 64 bit OS
// Credits:
// MICROSOFT: Raymond Chen
// http://blogs.msdn.com/oldnewthing/archive/2005/02/01/364563.aspx
[DllImport("kernel32.dll", SetLastError = true,
CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWow64Process(
[In] IntPtr hProcess,
[Out] out bool wow64Process
);
private static bool InternalCheckIsWow64()
{
if ((Environment.OSVersion.Version.Major == 5 &&
Environment.OSVersion.Version.Minor >= 1) ||
Environment.OSVersion.Version.Major >= 6)
{
using (Process p = Process.GetCurrentProcess())
{
bool retVal;
if (!IsWow64Process(p.Handle, out retVal))
{
return false;
}
return retVal;
}
}
else
{
return false;
}
}
// Handle Folder Redirection in Windows 64
// Credits:
// http://tcamilli.blogspot.com/2005/07/disabling-wow64-file-system.html
// https://stackoverflow.com/questions/29149512/
// process-start-for-system32-applications-the-system-cannot-find-the-file-specif
/// <summary>
/// Use to enable and disable file redirection in Win 64
/// </summary>
public class Wow64Interop
{
[DllImport("Kernel32.Dll", EntryPoint = "Wow64EnableWow64FsRedirection")]
public static extern bool EnableWow64FSRedirection(bool enable);
}
并使用此路径访问 WinSAT.exe 文件
public static string ExePathFor64BitApplication = Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.Windows), @"system32\winsat.exe");
然后将 WinSAT 作为单独的进程启动以创建 benchmark xml
Process n = new Process();
n.StartInfo.FileName = ExePathFor64BitApplication;
if (is64BitOperatingSystem)
{
Wow64Interop.EnableWow64FSRedirection(false);
n.Start();
ID = n.Id;
Wow64Interop.EnableWow64FSRedirection(true);
}
else
{
n.Start();
ID = n.Id;
ProcessHandle = n.Handle;
}
监视 Datastore 文件夹以获取新的 Winsat 创建的 XML 文件
我遇到的另一个有趣的编程点是如何确定 WinSAT.exe 是否已完成运行并创建了新的基准测试文件,以便我可以自动刷新应用程序窗口以显示新数据。我的解决方案是创建一个封装 FileSystemWatcher
对象的包装器类,该类可以在 BackGroundWorker DoWork()
方法中实例化,并在 FileSystemWatcher.Changed
事件触发时退出。然后,BackGroundWorker.Completed
方法会刷新窗体以显示新的基准测试。
/// Call from a Background Worker DoWork() instance with Path and File extension filter.
/// Watches for File Create
/// </summary>
/// <param name="Path">Used for Folder to watch.</param>
/// <param name="Filter">Used for file extension to watch for as in *.xml.</param>
/// <returns>Nothing, DoWork uses() Completed property to terminate</returns>
public class FSWatcher
{
// Constructor
public FSWatcher(string Path,string Filter)
{
watcher = new FileSystemWatcher(Path,Filter);
watcher.Created += watcher_changed;
watcher.NotifyFilter = NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName;
}
// Destructor
~FSWatcher()
{
watcher.Dispose();
}
//Public status accessor
public bool Completed
{
get
{
return completed;
}
}
// Public start method
public void Start()
{
completed = false;
watcher_start();
}
private FileSystemWatcher watcher;
private void watcher_changed(object sendwer, FileSystemEventArgs e)
{
watcher.EnableRaisingEvents = false;
completed = true;
}
private void watcher_start()
{
watcher.EnableRaisingEvents = true;
}
private bool completed = false;
}
// BackgroundWorker DoWork()
private void bwDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
FSWatcher FSW = new FSWatcher(DataStorePath, "*.*");
FSW.Start();
while (!FSW.Completed)
{
// Wait for completion
}
}
// BackgroundWorker completed
private void bwCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Thread.Sleep(3000); // Ensure file creation is completed
// and file is unlocked
PopulateDataFileList();
PopulateAssessmentFileNames();
if (AssessmentFileNames.Count > 0)
{
ParseXml(AssessmentFileNames[AssessmentFileNames.Count - 1]);
}
btnGetBenchMark.Enabled = true;
}
摘要
通过重新创建 Windows Experience 工具,以便我能够更轻松地检查我自组装 PC 的系统基准测试,我学到了更多关于常用于存储程序和硬件安装数据的 XML 文件结构的知识。拥有加载和搜索这些文件的简单方法可能很有用,但请记住,数据在节点中的存储方式因创建应用程序而异,因此必须先将 XML 作为文本文件读取,才能找到您正在查找的信息所在的位置。一旦找到,就可以轻松显示和操作 WinSAT 的测试结果等项目。
历史
- 1.0.1.0 2010-04-11: 首次发布