65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows 10 的 Windows 体验分数

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2019 年 11 月 5 日

CPOL

6分钟阅读

viewsIcon

15195

downloadIcon

561

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,如 XmlDeclarationDocumentTypeEntityReferenceCommentProcessingInstructionCDATAComment 也存储在数据库中。使用后进先出(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"),并通过以下方式读取评估运行的 datetimestring 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++;
                }
            }
        }

一旦进入 xitemList,就可以使用以下方法搜索特定信息以进行显示

// 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: 首次发布
© . All rights reserved.