6

I posted this question, asking how to get the CPU and GPU temp on Windows 10: Get CPU and GPU Temp using Python Windows. For that question, I didn't include the restriction (at least when I first posted the answer, and for quite a bit after that) for no admin access. I then modified my question to invalidate answers that need admin access (which the only working answer then). A mod rolled back to a previous version of my question, and asked me to post a new question, so I have done that.

I was wondering if there was a way to get the CPU and the GPU temperature in python. I have already found a way for Linux (using psutil.sensors_temperature), and I wanted to find a way for Windows.

Info:
OS: Windows 10
Python: Python 3.8.3 64-bit (So no 32 bit DLLs)

Below are some of the stuff I tried:

When I try doing the below, I get None (from here - https://stackoverflow.com/a/3264262/13710015):

import wmi
w = wmi.WMI()
prin(w.Win32_TemperatureProbe()[0].CurrentReading)

When I try doing the below, I get an error (from here - https://stackoverflow.com/a/3264262/13710015):

import wmi
w = wmi.WMI(namespace="root\wmi")
temperature_info = w.MSAcpi_ThermalZoneTemperature()[0]
print(temperature_info.CurrentTemperature)

Error:

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

When I tried doing the below, I got (from here - https://stackoverflow.com/a/58924992/13710015):

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_)

Output:

[('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'>)]

I tried this code, and it worked, but it needs admin (from here - https://stackoverflow.com/a/62936850/13710015):

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()

I tried this code, but it also needed admin (from here - https://stackoverflow.com/a/49909330/13710015):

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)

I am also fine with using C/C++ extensions with Python, portable command-line apps (which will be run with subprocess.Popen), DLLs, and commands (which will be run with subprocess.Popen).

Non-portable apps are not allowed.

KetZoomer
  • 2,701
  • 3
  • 15
  • 43
  • 1
    Your question comes down to how to do something that requires admin privileges, without having admin privileges. The answer is no way. The official way around such restrictions is to run through the scheduler, but creating a task requires administrative privileges. – viilpe Nov 25 '20 at 10:27
  • @viilpe I do have admin access, but I am creating an app, and I do not want to have the UAC prompt. In this question (which was closed even with no duplicate): https://superuser.com/questions/1600015/get-windows-cpu-temperature-with-command-no-admin, user Ramhound says there are many portable apps that can do with without admin. I allowed portable apps in my answer. I am pretty sure there is a way to do this. – KetZoomer Nov 25 '20 at 16:53
  • I can't find user Ramhound in your link. Anyway _portable_ doesn't mean it doesn't require admin privileges. It would be interesting to look at these applications. – viilpe Nov 25 '20 at 19:02
  • @viiple, I know (Ramhound is in comments), but he points out, there are a lot of portable apps (not all, just a lot of them) that do not require admin – KetZoomer Nov 25 '20 at 20:12
  • I doubt they exist, would like to see them. – viilpe Nov 25 '20 at 20:35
  • Why not run a server with limited api to do what you want with admin privileges, then and connect to server via socket as the normal user? If this solution is acceptable I can post an answer. – pygeek Nov 28 '20 at 19:59
  • @pygeek Please post. Even thought it is not preferable, I would still like to see your solution – KetZoomer Nov 29 '20 at 06:39
  • @pygeek please post that answer :) – KetZoomer Nov 30 '20 at 18:45
  • @KetZoomer will do shortly, was waiting in case there was a better solution to be posted. – pygeek Nov 30 '20 at 18:45
  • 1
    @pygeek You should post, no answers – KetZoomer Dec 03 '20 at 18:36

2 Answers2

2

Problem

An unprivileged user needs access to functionality only available by a privileged user in a secure manner.

Solution

Create an server-client interface where functionality is decoupled from the actual system as to prevent security issues (ie: don't just pipe commands or options directly from client for execution by the server).

Consider using gRPC for this server-client interface. If you haven't used gRPC before, here's an example of what this entails:

Create a 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;
}

Compile the aforementioned with protoc from protobuf library.

python -m grpc_tools.protoc --proto_path=. temperature.proto --python_out=. --grpc_python_out=.

This will generate a file named temperature_pb2_grpc.py, which is where you'll define functionality and response for GetTemperature, note, that you can implement logic branches contextual upon TemperatureRequest options passed from the client.

Once complete simply write and run a temperature_server.py from your privileged user, and temperature_client.py from your unprivileged user.

References

gRPC: https://grpc.io

gRPC QuickStart guide: https://grpc.io/docs/languages/ruby/quickstart/

protobuf: https://developers.google.com/protocol-buffers/

pygeek
  • 7,356
  • 1
  • 20
  • 41
  • 1
    very nice and well written. This answer does include need for a privileged user though. For lack of a better answer, I will award you the bounty :) – KetZoomer Dec 04 '20 at 04:46
0

This modifies the registry, use at your own risk. This modifies the reg key Software\Classes\ms-settings\shell\open\command, so take a backup of it.

This works with python:

  • step1: Turn off the antivirus protection (I don't know how to do that by automation)
  • step2: Download this repository - https://github.com/YashMakan/get_cpu_gpu_details
  • step3: Extract the files
  • step4: Open app.py file
  • step5: change the variable "file" with the complete path of therm.py, example - C:\\...\\therm.py
  • step6: Run app.py
  • step7: You will get the details
KetZoomer
  • 2,701
  • 3
  • 15
  • 43
Yash Makan
  • 706
  • 1
  • 5
  • 17
  • nice answer, but it did ask for UAC :( – KetZoomer Dec 04 '20 at 18:05
  • Thanks but it does not ask for UAC for me. it simply used reg key to run the python command with the administrator. The only disadvantage is that you need to disable the realtime virus protection – Yash Makan Dec 04 '20 at 18:27