使用Python在Windows系统中获取CPU和GPU温度,无需管理员权限

6
我在Stack Overflow上发布了一个问题,询问如何在Windows 10上获取CPU和GPU的温度:Get CPU and GPU Temp using Python Windows。在那个问题中,我没有包括不需要管理员权限的限制(至少在我最初发布答案时以及此后相当长一段时间内),因此我修改了我的问题,使需要管理员访问权限的回答无效(这是唯一有效的答案)。然后,一位 moderator 将我的问题回滚到之前的版本,并要求我发布一个新问题,所以我已经这样做了。
我想知道是否有一种方法可以使用Python获取CPU和GPU的温度。我已经找到了Linux的方法(使用psutil.sensors_temperature),我想找到Windows的方法。
信息:
操作系统:Windows 10
Python:Python 3.8.3 64位(因此没有32位DLL)
以下是我尝试过的一些内容:
当我尝试做下面这件事时,我会得到 None(从这里 - https://dev59.com/SXA75IYBdhLWcg3wf5NV#3264262):
import wmi
w = wmi.WMI()
prin(w.Win32_TemperatureProbe()[0].CurrentReading)

当我尝试执行以下操作时,出现错误(来源于此处-https://dev59.com/SXA75IYBdhLWcg3wf5NV#3264262):
import wmi
w = wmi.WMI(namespace="root\wmi")
temperature_info = w.MSAcpi_ThermalZoneTemperature()[0]
print(temperature_info.CurrentTemperature)

错误:

wmi.x_wmi: <x_wmi: Unexpected COM Error (-2147217396, 'OLE error 0x8004100c', None, None)>

当我尝试执行以下操作时,我得到了以下结果(来自这里-https://dev59.com/SXA75IYBdhLWcg3wf5NV#58924992):

import ctypes
import ctypes.wintypes as wintypes
from ctypes import windll


LPDWORD = ctypes.POINTER(wintypes.DWORD)
LPOVERLAPPED = wintypes.LPVOID
LPSECURITY_ATTRIBUTES = wintypes.LPVOID

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000

FILE_SHARE_WRITE=0x00000004
ZERO=0x00000000

CREATE_NEW = 1
CREATE_ALWAYS = 2
OPEN_EXISTING = 3
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5

FILE_ATTRIBUTE_NORMAL = 0x00000080

INVALID_HANDLE_VALUE = -1
FILE_DEVICE_UNKNOWN=0x00000022
METHOD_BUFFERED=0
FUNC=0x900
FILE_WRITE_ACCESS=0x002

NULL = 0
FALSE = wintypes.BOOL(0)
TRUE = wintypes.BOOL(1)


def CTL_CODE(DeviceType, Function, Method, Access): return (DeviceType << 16) | (Access << 14) | (Function <<2) | Method




def _CreateFile(filename, access, mode, creation, flags):
    """See: CreateFile function http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).asp """
    CreateFile_Fn = windll.kernel32.CreateFileW
    CreateFile_Fn.argtypes = [
            wintypes.LPWSTR,                    # _In_          LPCTSTR lpFileName
            wintypes.DWORD,                     # _In_          DWORD dwDesiredAccess
            wintypes.DWORD,                     # _In_          DWORD dwShareMode
            LPSECURITY_ATTRIBUTES,              # _In_opt_      LPSECURITY_ATTRIBUTES lpSecurityAttributes
            wintypes.DWORD,                     # _In_          DWORD dwCreationDisposition
            wintypes.DWORD,                     # _In_          DWORD dwFlagsAndAttributes
            wintypes.HANDLE]                    # _In_opt_      HANDLE hTemplateFile
    CreateFile_Fn.restype = wintypes.HANDLE

    return wintypes.HANDLE(CreateFile_Fn(filename,
                         access,
                         mode,
                         NULL,
                         creation,
                         flags,
                         NULL))


handle=_CreateFile('\\\\\.\PhysicalDrive0',GENERIC_WRITE,FILE_SHARE_WRITE,OPEN_EXISTING,ZERO)

def _DeviceIoControl(devhandle, ioctl, inbuf, inbufsiz, outbuf, outbufsiz):
    """See: DeviceIoControl function
http://msdn.microsoft.com/en-us/library/aa363216(v=vs.85).aspx
"""
    DeviceIoControl_Fn = windll.kernel32.DeviceIoControl
    DeviceIoControl_Fn.argtypes = [
            wintypes.HANDLE,                    # _In_          HANDLE hDevice
            wintypes.DWORD,                     # _In_          DWORD dwIoControlCode
            wintypes.LPVOID,                    # _In_opt_      LPVOID lpInBuffer
            wintypes.DWORD,                     # _In_          DWORD nInBufferSize
            wintypes.LPVOID,                    # _Out_opt_     LPVOID lpOutBuffer
            wintypes.DWORD,                     # _In_          DWORD nOutBufferSize
            LPDWORD,                            # _Out_opt_     LPDWORD lpBytesReturned
            LPOVERLAPPED]                       # _Inout_opt_   LPOVERLAPPED lpOverlapped
    DeviceIoControl_Fn.restype = wintypes.BOOL

    # allocate a DWORD, and take its reference
    dwBytesReturned = wintypes.DWORD(0)
    lpBytesReturned = ctypes.byref(dwBytesReturned)

    status = DeviceIoControl_Fn(devhandle,
                  ioctl,
                  inbuf,
                  inbufsiz,
                  outbuf,
                  outbufsiz,
                  lpBytesReturned,
                  NULL)

    return status, dwBytesReturned

class OUTPUT_temp(ctypes.Structure):
        """See: http://msdn.microsoft.com/en-us/library/aa363972(v=vs.85).aspx"""
        _fields_ = [
                ('Board Temp', wintypes.DWORD),
                ('CPU Temp', wintypes.DWORD),
                ('Board Temp2', wintypes.DWORD),
                ('temp4', wintypes.DWORD),
                ('temp5', wintypes.DWORD)
                ]

class OUTPUT_volt(ctypes.Structure):
        """See: http://msdn.microsoft.com/en-us/library/aa363972(v=vs.85).aspx"""
        _fields_ = [
                ('VCore', wintypes.DWORD),
                ('V(in2)', wintypes.DWORD),
                ('3.3V', wintypes.DWORD),
                ('5.0V', wintypes.DWORD),
                ('temp5', wintypes.DWORD)
                ]

def get_temperature():
    FUNC=0x900
    outDict={}

    ioclt=CTL_CODE(FILE_DEVICE_UNKNOWN, FUNC, METHOD_BUFFERED, FILE_WRITE_ACCESS)

    handle=_CreateFile('\\\\\.\PhysicalDrive0',GENERIC_WRITE,FILE_SHARE_WRITE,OPEN_EXISTING,ZERO)

    win_list = OUTPUT_temp()
    p_win_list = ctypes.pointer(win_list)
    SIZE=ctypes.sizeof(OUTPUT_temp)


    status, output = _DeviceIoControl(handle, ioclt , NULL, ZERO, p_win_list, SIZE)


    for field, typ in win_list._fields_:
                #print ('%s=%d' % (field, getattr(disk_geometry, field)))
                outDict[field]=getattr(win_list,field)
    return outDict

def get_voltages():
    FUNC=0x901
    outDict={}

    ioclt=CTL_CODE(FILE_DEVICE_UNKNOWN, FUNC, METHOD_BUFFERED, FILE_WRITE_ACCESS)

    handle=_CreateFile('\\\\\.\PhysicalDrive0',GENERIC_WRITE,FILE_SHARE_WRITE,OPEN_EXISTING,ZERO)

    win_list = OUTPUT_volt()
    p_win_list = ctypes.pointer(win_list)
    SIZE=ctypes.sizeof(OUTPUT_volt)


    status, output = _DeviceIoControl(handle, ioclt , NULL, ZERO, p_win_list, SIZE)


    for field, typ in win_list._fields_:
                #print ('%s=%d' % (field, getattr(disk_geometry, field)))
                outDict[field]=getattr(win_list,field)
    return outDict

print(OUTPUT_temp._fields_)

输出:

[('Board Temp', <class 'ctypes.c_ulong'>), ('CPU Temp', <class 'ctypes.c_ulong'>), ('Board Temp2', <class 'ctypes.c_ulong'>), ('temp4', <class 'ctypes.c_ulong'>), ('temp5', <class 'ctypes.c_ulong'>)]

我尝试了这段代码,它能够正常工作,但需要管理员权限(来自于这里:https://dev59.com/Ubvoa4cB1Zd3GeqP_9-8#62936850):

import clr # the pythonnet module.
clr.AddReference(r'YourdllPath')
from OpenHardwareMonitor.Hardware import Computer

c = Computer()
c.CPUEnabled = True # get the Info about CPU
c.GPUEnabled = True # get the Info about GPU
c.Open()
while True:
    for a in range(0, len(c.Hardware[0].Sensors)):
        # print(c.Hardware[0].Sensors[a].Identifier)
        if "/intelcpu/0/temperature" in str(c.Hardware[0].Sensors[a].Identifier):
            print(c.Hardware[0].Sensors[a].get_Value())
            c.Hardware[0].Update()

我尝试了这段代码,但它也需要管理员权限(参考此处 - https://dev59.com/SXA75IYBdhLWcg3wf5NV#49909330):

import clr #package pythonnet, not clr


openhardwaremonitor_hwtypes = ['Mainboard','SuperIO','CPU','RAM','GpuNvidia','GpuAti','TBalancer','Heatmaster','HDD']
cputhermometer_hwtypes = ['Mainboard','SuperIO','CPU','GpuNvidia','GpuAti','TBalancer','Heatmaster','HDD']
openhardwaremonitor_sensortypes = ['Voltage','Clock','Temperature','Load','Fan','Flow','Control','Level','Factor','Power','Data','SmallData']
cputhermometer_sensortypes = ['Voltage','Clock','Temperature','Load','Fan','Flow','Control','Level']


def initialize_openhardwaremonitor():
    file = 'OpenHardwareMonitorLib.dll'
    clr.AddReference(file)

    from OpenHardwareMonitor import Hardware

    handle = Hardware.Computer()
    handle.MainboardEnabled = True
    handle.CPUEnabled = True
    handle.RAMEnabled = True
    handle.GPUEnabled = True
    handle.HDDEnabled = True
    handle.Open()
    return handle

def initialize_cputhermometer():
    file = 'CPUThermometerLib.dll'
    clr.AddReference(file)

    from CPUThermometer import Hardware
    handle = Hardware.Computer()
    handle.CPUEnabled = True
    handle.Open()
    return handle

def fetch_stats(handle):
    for i in handle.Hardware:
        i.Update()
        for sensor in i.Sensors:
            parse_sensor(sensor)
        for j in i.SubHardware:
            j.Update()
            for subsensor in j.Sensors:
                parse_sensor(subsensor)


def parse_sensor(sensor):
        if sensor.Value is not None:
            if type(sensor).__module__ == 'CPUThermometer.Hardware':
                sensortypes = cputhermometer_sensortypes
                hardwaretypes = cputhermometer_hwtypes
            elif type(sensor).__module__ == 'OpenHardwareMonitor.Hardware':
                sensortypes = openhardwaremonitor_sensortypes
                hardwaretypes = openhardwaremonitor_hwtypes
            else:
                return

            if sensor.SensorType == sensortypes.index('Temperature'):
                print(u"%s %s Temperature Sensor #%i %s - %s\u00B0C" % (hardwaretypes[sensor.Hardware.HardwareType], sensor.Hardware.Name, sensor.Index, sensor.Name, sensor.Value))

if __name__ == "__main__":
    print("OpenHardwareMonitor:")
    HardwareHandle = initialize_openhardwaremonitor()
    fetch_stats(HardwareHandle)
    print("\nCPUMonitor:")
    CPUHandle = initialize_cputhermometer()
    fetch_stats(CPUHandle)

我也可以使用C/C++扩展与Python一起使用,创建可移植的命令行应用程序(将使用subprocess.Popen运行),DLL和命令(将使用subprocess.Popen运行)。

不允许使用非可移植的应用程序。


1
你的问题归结为如何在没有管理员权限的情况下完成需要管理员权限的操作。答案是否定的。规避此类限制的官方方法是通过计划程序运行,但创建任务需要管理员权限。 - viilpe
@viilpe 我有管理员权限,但我正在创建一个应用程序,我不想出现UAC提示。在这个问题中(即使没有重复),用户Ramhound说有很多可以在没有管理员权限的情况下完成的便携式应用程序:https://superuser.com/questions/1600015/get-windows-cpu-temperature-with-command-no-admin。我在我的答案中允许了便携式应用程序。我相信有一种方法可以做到这一点。 - KetZoomer
为什么不使用有限的API运行服务器以获得管理员权限,然后作为普通用户通过套接字连接到服务器?如果这个解决方案可行,我可以发布一个答案。 - pygeek
@KetZoomer,我马上就会处理,只是在等待是否有更好的解决方案被发布。 - pygeek
1
@pygeek 你应该发布问题,而不是回答。 - KetZoomer
显示剩余5条评论
2个回答

2

问题

非特权用户需要以安全的方式访问仅特权用户才能使用的功能。

解决方案

创建一个服务器-客户端接口,其中功能与实际系统分离,以防止安全问题(即:不要直接从客户端通过管道传输命令或选项以供服务器执行)。

考虑使用 gRPC 进行服务器-客户端接口。如果您之前没有使用过 gRPC,请看下面的示例:

创建一个 temperature.proto 文件:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "temperature";
option java_outer_classname = "TemperatureProto";
option objc_class_prefix = "TEMP";

package temperature;

service SystemTemperature {
  rpc GetTemperature (TemperatureRequest) returns (TemperatureReply) {}
}

message TemperatureRequest {
  string name = 1;
}

message TemperatureReply {
  string message = 1;
}

使用来自protobuf库的protoc编译上述内容。
python -m grpc_tools.protoc --proto_path=. temperature.proto --python_out=. --grpc_python_out=.

这将生成一个名为temperature_pb2_grpc.py的文件,您可以在其中定义GetTemperature的功能和响应。请注意,您可以根据从客户端传递的TemperatureRequest选项实现上下文分支逻辑。
完成后,只需从特权用户编写和运行temperature_server.py,并从非特权用户编写和运行temperature_client.py即可。
参考资料:
gRPC:https://grpc.io gRPC快速入门指南:https://grpc.io/docs/languages/ruby/quickstart/ protobuf:https://developers.google.com/protocol-buffers/

1
非常好的写作。这个答案确实需要特权用户。由于没有更好的答案,我将授予您赏金 :) - KetZoomer
我们怎么样才能创建temperature_server.pytemperature_client.py,并与temperature_pb2_grpc.py进行连接呢? - undefined

0

这会修改注册表,请自行承担风险。它会修改注册表键 Software\Classes\ms-settings\shell\open\command,请备份。

这适用于Python:

  • 步骤1:关闭防病毒保护(我不知道如何通过自动化完成此操作)
  • 步骤2:下载此存储库 - https://github.com/YashMakan/get_cpu_gpu_details
  • 步骤3:提取文件
  • 步骤4:打开app.py文件
  • 步骤5:将变量“file”更改为therm.py的完整路径,例如- C:\\...\\therm.py
  • 步骤6:运行app.py
  • 步骤7:您将获得详细信息

很好的回答,但它确实要求 UAC :( - KetZoomer
谢谢,但这对我没有询问UAC。它只是使用reg键以管理员身份运行python命令。唯一的缺点是您需要禁用实时病毒保护。 - Yash Makan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接