2024-02-17 03:48:29 +00:00
// SPDX-License-Identifier: BSD-3-Clause
2021-12-22 21:54:41 +00:00
//go:build windows
2014-04-18 21:28:00 +09:00
2014-12-30 22:09:05 +09:00
package host
2014-04-18 21:28:00 +09:00
import (
2017-11-09 15:44:26 -08:00
"context"
2015-08-28 07:39:20 +01:00
"fmt"
2021-10-08 09:36:28 +02:00
"strconv"
2015-08-25 23:03:25 -07:00
"strings"
2017-06-01 18:11:14 +02:00
"sync/atomic"
2018-06-06 00:09:49 +02:00
"syscall"
2015-10-13 13:02:02 -07:00
"time"
2017-02-02 16:05:11 -08:00
"unsafe"
2014-11-27 22:28:05 +09:00
2024-02-17 03:48:29 +00:00
"github.com/shirou/gopsutil/v4/internal/common"
"github.com/shirou/gopsutil/v4/process"
2018-06-18 22:17:31 +02:00
"golang.org/x/sys/windows"
2014-04-22 22:10:13 +09:00
)
var (
2014-11-27 22:28:05 +09:00
procGetSystemTimeAsFileTime = common . Modkernel32 . NewProc ( "GetSystemTimeAsFileTime" )
2018-06-06 00:09:49 +02:00
procGetTickCount32 = common . Modkernel32 . NewProc ( "GetTickCount" )
procGetTickCount64 = common . Modkernel32 . NewProc ( "GetTickCount64" )
2019-08-07 23:59:43 +02:00
procGetNativeSystemInfo = common . Modkernel32 . NewProc ( "GetNativeSystemInfo" )
2018-06-17 23:41:32 +02:00
procRtlGetVersion = common . ModNt . NewProc ( "RtlGetVersion" )
2014-04-18 21:28:00 +09:00
)
2018-06-17 23:41:32 +02:00
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
type osVersionInfoExW struct {
dwOSVersionInfoSize uint32
dwMajorVersion uint32
dwMinorVersion uint32
dwBuildNumber uint32
dwPlatformId uint32
szCSDVersion [ 128 ] uint16
wServicePackMajor uint16
wServicePackMinor uint16
wSuiteMask uint16
wProductType uint8
wReserved uint8
2015-04-20 00:05:31 +09:00
}
2019-08-07 23:59:43 +02:00
type systemInfo struct {
wProcessorArchitecture uint16
wReserved uint16
dwPageSize uint32
lpMinimumApplicationAddress uintptr
lpMaximumApplicationAddress uintptr
dwActiveProcessorMask uintptr
dwNumberOfProcessors uint32
dwProcessorType uint32
dwAllocationGranularity uint32
wProcessorLevel uint16
wProcessorRevision uint16
}
2020-09-11 14:51:20 +03:00
func HostIDWithContext ( ctx context . Context ) ( string , error ) {
2018-06-18 23:03:39 +02:00
// there has been reports of issues on 32bit using golang.org/x/sys/windows/registry, see https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612
// for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue
2018-06-18 22:17:31 +02:00
var h windows . Handle
err := windows . RegOpenKeyEx ( windows . HKEY_LOCAL_MACHINE , windows . StringToUTF16Ptr ( ` SOFTWARE\Microsoft\Cryptography ` ) , 0 , windows . KEY_READ | windows . KEY_WOW64_64KEY , & h )
2017-02-02 16:05:11 -08:00
if err != nil {
return "" , err
}
2018-06-18 22:17:31 +02:00
defer windows . RegCloseKey ( h )
const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16
const uuidLen = 36
var regBuf [ windowsRegBufLen ] uint16
bufLen := uint32 ( windowsRegBufLen )
var valType uint32
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` MachineGuid ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
2017-02-02 16:05:11 -08:00
if err != nil {
return "" , err
}
2018-06-18 22:17:31 +02:00
hostID := windows . UTF16ToString ( regBuf [ : ] )
hostIDLen := len ( hostID )
if hostIDLen != uuidLen {
return "" , fmt . Errorf ( "HostID incorrect: %q\n" , hostID )
}
2019-08-07 22:34:36 +02:00
return strings . ToLower ( hostID ) , nil
2017-02-02 16:05:11 -08:00
}
2020-09-11 14:51:20 +03:00
func numProcs ( ctx context . Context ) ( uint64 , error ) {
procs , err := process . PidsWithContext ( ctx )
if err != nil {
return 0 , err
}
return uint64 ( len ( procs ) ) , nil
2017-12-31 15:25:49 +09:00
}
func UptimeWithContext ( ctx context . Context ) ( uint64 , error ) {
2022-12-19 11:43:31 -05:00
up , err := uptimeMillis ( )
if err != nil {
return 0 , err
}
return uint64 ( ( time . Duration ( up ) * time . Millisecond ) . Seconds ( ) ) , nil
}
func uptimeMillis ( ) ( uint64 , error ) {
2018-06-06 00:09:49 +02:00
procGetTickCount := procGetTickCount64
err := procGetTickCount64 . Find ( )
if err != nil {
procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
}
r1 , _ , lastErr := syscall . Syscall ( procGetTickCount . Addr ( ) , 0 , 0 , 0 , 0 )
if lastErr != 0 {
return 0 , lastErr
2015-08-25 23:03:25 -07:00
}
2022-12-19 11:43:31 -05:00
return uint64 ( r1 ) , nil
2014-04-22 21:39:18 +09:00
}
2015-02-15 22:26:18 +09:00
2017-06-01 18:11:14 +02:00
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
2017-12-31 15:25:49 +09:00
func BootTimeWithContext ( ctx context . Context ) ( uint64 , error ) {
2024-01-11 00:45:21 +09:00
if enableBootTimeCache {
t := atomic . LoadUint64 ( & cachedBootTime )
if t != 0 {
return t , nil
}
2016-10-13 11:18:11 -04:00
}
2022-12-19 11:43:31 -05:00
up , err := uptimeMillis ( )
2015-11-23 13:14:56 -07:00
if err != nil {
return 0 , err
}
2024-01-11 00:45:21 +09:00
t := uint64 ( ( time . Duration ( timeSinceMillis ( up ) ) * time . Millisecond ) . Seconds ( ) )
if enableBootTimeCache {
atomic . StoreUint64 ( & cachedBootTime , t )
}
2017-06-01 18:11:14 +02:00
return t , nil
2015-11-23 13:14:56 -07:00
}
2017-12-31 15:25:49 +09:00
func PlatformInformationWithContext ( ctx context . Context ) ( platform string , family string , version string , err error ) {
2024-04-22 23:02:22 +09:00
platform , family , _ , displayVersion , err := platformInformation ( ctx )
if err != nil {
return "" , "" , "" , err
}
return platform , family , displayVersion , nil
}
func platformInformation ( ctx context . Context ) ( platform , family , version , displayVersion string , err error ) {
2018-06-17 23:41:32 +02:00
// GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
// RtlGetVersion bypasses this lying layer and returns the true Windows version
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
var osInfo osVersionInfoExW
osInfo . dwOSVersionInfoSize = uint32 ( unsafe . Sizeof ( osInfo ) )
ret , _ , err := procRtlGetVersion . Call ( uintptr ( unsafe . Pointer ( & osInfo ) ) )
if ret != 0 {
return
2015-08-25 23:27:25 -07:00
}
// Platform
2020-09-11 14:51:20 +03:00
var h windows . Handle // like HostIDWithContext(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx
2018-06-18 23:03:39 +02:00
err = windows . RegOpenKeyEx ( windows . HKEY_LOCAL_MACHINE , windows . StringToUTF16Ptr ( ` SOFTWARE\Microsoft\Windows NT\CurrentVersion ` ) , 0 , windows . KEY_READ | windows . KEY_WOW64_64KEY , & h )
2018-06-17 23:41:32 +02:00
if err != nil {
return
}
2018-06-18 23:03:39 +02:00
defer windows . RegCloseKey ( h )
var bufLen uint32
var valType uint32
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` ProductName ` ) , nil , & valType , nil , & bufLen )
2018-06-17 23:41:32 +02:00
if err != nil {
return
}
2018-06-18 23:03:39 +02:00
regBuf := make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` ProductName ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
if err != nil {
return
}
platform = windows . UTF16ToString ( regBuf [ : ] )
2021-10-08 09:36:28 +02:00
if strings . Contains ( platform , "Windows 10" ) { // check build number to determine whether it's actually Windows 11
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CurrentBuildNumber ` ) , nil , & valType , nil , & bufLen )
if err == nil {
regBuf = make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CurrentBuildNumber ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
if err == nil {
buildNumberStr := windows . UTF16ToString ( regBuf [ : ] )
2024-08-23 08:22:42 +00:00
if buildNumber , err := strconv . ParseInt ( buildNumberStr , 10 , 32 ) ; err == nil && buildNumber >= 22000 {
2021-10-08 09:36:28 +02:00
platform = strings . Replace ( platform , "Windows 10" , "Windows 11" , 1 )
}
}
}
}
2018-06-17 23:41:32 +02:00
if ! strings . HasPrefix ( platform , "Microsoft" ) {
platform = "Microsoft " + platform
}
2018-06-18 23:03:39 +02:00
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CSDVersion ` ) , nil , & valType , nil , & bufLen ) // append Service Pack number, only on success
if err == nil { // don't return an error if only the Service Pack retrieval fails
regBuf = make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` CSDVersion ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
if err == nil {
platform += " " + windows . UTF16ToString ( regBuf [ : ] )
}
2018-06-17 23:41:32 +02:00
}
2015-10-13 13:02:02 -07:00
2022-11-05 20:59:15 +09:00
var UBR uint32 // Update Build Revision
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` UBR ` ) , nil , & valType , nil , & bufLen )
if err == nil {
regBuf := make ( [ ] byte , 4 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` UBR ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
copy ( ( * [ 4 ] byte ) ( unsafe . Pointer ( & UBR ) ) [ : ] , regBuf )
}
2024-04-22 23:02:22 +09:00
// Get DisplayVersion(ex: 23H2) as platformVersion
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` DisplayVersion ` ) , nil , & valType , nil , & bufLen )
if err == nil {
regBuf := make ( [ ] uint16 , bufLen / 2 + 1 )
err = windows . RegQueryValueEx ( h , windows . StringToUTF16Ptr ( ` DisplayVersion ` ) , nil , & valType , ( * byte ) ( unsafe . Pointer ( & regBuf [ 0 ] ) ) , & bufLen )
displayVersion = windows . UTF16ToString ( regBuf [ : ] )
}
2015-08-25 23:27:25 -07:00
// PlatformFamily
2018-06-17 23:41:32 +02:00
switch osInfo . wProductType {
2015-08-25 23:27:25 -07:00
case 1 :
2015-08-28 07:39:20 +01:00
family = "Standalone Workstation"
2015-08-25 23:27:25 -07:00
case 2 :
2015-08-28 07:39:20 +01:00
family = "Server (Domain Controller)"
2015-08-25 23:27:25 -07:00
case 3 :
2015-08-28 07:39:20 +01:00
family = "Server"
2015-08-25 23:27:25 -07:00
}
2015-10-13 13:02:02 -07:00
2015-08-25 23:27:25 -07:00
// Platform Version
2022-11-05 20:59:15 +09:00
version = fmt . Sprintf ( "%d.%d.%d.%d Build %d.%d" ,
osInfo . dwMajorVersion , osInfo . dwMinorVersion , osInfo . dwBuildNumber , UBR ,
osInfo . dwBuildNumber , UBR )
2015-08-25 23:27:25 -07:00
2024-04-22 23:02:22 +09:00
return platform , family , version , displayVersion , nil
2015-08-25 23:27:25 -07:00
}
2017-12-31 15:25:49 +09:00
func UsersWithContext ( ctx context . Context ) ( [ ] UserStat , error ) {
2014-04-30 15:32:05 +09:00
var ret [ ] UserStat
2014-04-22 21:39:18 +09:00
2020-08-29 18:29:48 +02:00
return ret , common . ErrNotImplementedError
2014-04-22 21:39:18 +09:00
}
2017-03-19 02:05:46 +01:00
2017-12-31 15:25:49 +09:00
func VirtualizationWithContext ( ctx context . Context ) ( string , string , error ) {
2017-08-03 10:14:20 +09:00
return "" , "" , common . ErrNotImplementedError
}
2017-08-03 11:08:35 +09:00
2017-12-31 15:25:49 +09:00
func KernelVersionWithContext ( ctx context . Context ) ( string , error ) {
2024-04-22 23:02:22 +09:00
_ , _ , version , _ , err := platformInformation ( ctx )
2017-08-03 11:08:35 +09:00
return version , err
}
2019-08-07 23:59:43 +02:00
2020-09-11 14:51:20 +03:00
func KernelArch ( ) ( string , error ) {
2019-08-07 23:59:43 +02:00
var systemInfo systemInfo
procGetNativeSystemInfo . Call ( uintptr ( unsafe . Pointer ( & systemInfo ) ) )
const (
PROCESSOR_ARCHITECTURE_INTEL = 0
PROCESSOR_ARCHITECTURE_ARM = 5
PROCESSOR_ARCHITECTURE_ARM64 = 12
PROCESSOR_ARCHITECTURE_IA64 = 6
PROCESSOR_ARCHITECTURE_AMD64 = 9
)
switch systemInfo . wProcessorArchitecture {
case PROCESSOR_ARCHITECTURE_INTEL :
if systemInfo . wProcessorLevel < 3 {
return "i386" , nil
}
if systemInfo . wProcessorLevel > 6 {
return "i686" , nil
}
return fmt . Sprintf ( "i%d86" , systemInfo . wProcessorLevel ) , nil
case PROCESSOR_ARCHITECTURE_ARM :
return "arm" , nil
case PROCESSOR_ARCHITECTURE_ARM64 :
return "aarch64" , nil
case PROCESSOR_ARCHITECTURE_IA64 :
return "ia64" , nil
case PROCESSOR_ARCHITECTURE_AMD64 :
return "x86_64" , nil
}
return "" , nil
}