We have a winner!

Huge thanks from me even though I wasn’t the OP because I've wanted to know how 
to do this for a while and just started to get my feet wet in this arena!!!  I 
now have a VERY GOOD EXAMPLE of how to start looking at doing a complex task 
like this with all the nested references.  UGH!

In [16]: get_idle_time(2)  # session id = 2
Out[16]: 0.0156271

Please note that line wraps (and or a missing newline) in the previous email 
may cause copy/paste issues in Eryk's code around these lines:

winsta.WinStationQueryInformationW.restype = wintypes.BOOLEAN
# this needs to be a newline
winsta.WinStationQueryInformationW.argtypes = (
    wintypes.HANDLE, # ServerHandle
    wintypes.ULONG,  # SessionId
    ctypes.c_long,   # WinStationInformationClass
    wintypes.LPVOID, # pWinStationInformation
    wintypes.ULONG,  # WinStationInformationLength
    wintypes.PULONG, # pReturnLength
)

I HOPE THIS HELPS AND THANK YOU VERY MUCH!

Steven
-----Original Message-----
From: Eryk Sun <eryk...@gmail.com> 
Sent: Wednesday, June 22, 2022 1:28 PM
To: Steven Manross <ste...@manross.net>
Cc: python-win32@python.org
Subject: Re: [python-win32] Need a value from pywin32

On 6/21/22, Steven Manross <ste...@manross.net> wrote:
>
> class WinStationInformation(ctypes.Structure):
>     __fields__ = [
>         ('ConnectState', ctypes.c_long),
>         ('WinStationName', ctypes.wintypes.WCHAR),
>         ('LogonId', ctypes.c_ulong),
>         ('ConnectTime', ctypes.wintypes.LARGE_INTEGER),
>         ('DisconnectTime', ctypes.wintypes.LARGE_INTEGER),
>         ('LastInputTime', ctypes.wintypes.LARGE_INTEGER),
>         ('LogonTime', ctypes.wintypes.LARGE_INTEGER),
>         ('Status', ctypes.c_int()),
>         ('Domain', ctypes.wintypes.WCHAR * (17 + 1)),
>         ('UserName', ctypes.wintypes.WCHAR * (20 + 1)),
>         ('CurrentTime', ctypes.wintypes.LARGE_INTEGER),
>     ]

The above definition is incorrect for `WinStationName` and `Status`.
Defining the PROTOCOLSTATUS type for `Status` is tedious. Note also that the 
ctypes attribute to set is `_fields_`, not `__fields__`, so the above struct is 
actually defined with no fields (i.e. zero size).

Here's an example that defines a get_idle_time() function, based on the 
difference between the current time and the last input time in the session.

import ctypes
from ctypes import wintypes

winsta = ctypes.WinDLL('winsta', use_last_error=True)

WINSTATIONNAME_LENGTH = 32
DOMAIN_LENGTH = 17
USERNAME_LENGTH = 20
MAX_THINWIRECACHE = 4

SERVERNAME_CURRENT = None
LOGONID_CURRENT = -1

# WINSTATIONINFOCLASS
WinStationInformation = 8

# WINSTATIONSTATECLASS
State_Active = 0
State_Connected = 1
State_ConnectQuery = 2
State_Shadow = 3
State_Disconnected = 4
State_Idle = 5
State_Listen = 6
State_Reset = 7
State_Down = 8
State_Init = 9

class TSHARE_COUNTERS(ctypes.Structure):
    __slots__ = ()
    _fields_ = (
        ('Reserved', wintypes.ULONG),
    )

class PROTOCOLCOUNTERS(ctypes.Structure):
    __slots__ = ()
    class SPECIFIC(ctypes.Union):
        __slots__ = ()
        _fields_ = (
            ('TShareCounters', TSHARE_COUNTERS),
            ('Reserved', wintypes.ULONG * 100),
        )
    _fields_ = (
        ('WdBytes', wintypes.ULONG),
        ('WdFrames', wintypes.ULONG),
        ('WaitForOutBuf', wintypes.ULONG),
        ('Frames', wintypes.ULONG),
        ('Bytes', wintypes.ULONG),
        ('CompressedBytes', wintypes.ULONG),
        ('CompressFlushes', wintypes.ULONG),
        ('Errors', wintypes.ULONG),
        ('Timeouts', wintypes.ULONG),
        ('AsyncFramingError', wintypes.ULONG),
        ('AsyncOverrunError', wintypes.ULONG),
        ('AsyncOverflowError', wintypes.ULONG),
        ('AsyncParityError', wintypes.ULONG),
        ('TdErrors', wintypes.ULONG),
        ('ProtocolType', wintypes.USHORT),
        ('Length', wintypes.USHORT),
        ('Specific', SPECIFIC),
    )


class THINWIRECACHE (ctypes.Structure):
    __slots__ = ()
    _fields_ = (
        ('CacheReads', wintypes.ULONG),
        ('CacheHits', wintypes.ULONG),
    )


class RESERVED_CACHE(ctypes.Structure):
    __slots__ = ()
    _fields_ = (
        ('ThinWireCache[', THINWIRECACHE * MAX_THINWIRECACHE),
    )


class CACHE_STATISTICS(ctypes.Structure):
    __slots__ = ()
    class SPECIFIC(ctypes.Union):
        __slots__ = ()
        _fields_ = (
            ('ReservedCacheStats', RESERVED_CACHE),
            ('TShareCacheStats', wintypes.ULONG),
            ('Reserved', wintypes.ULONG * 20),
        )
    _fields_ = (
        ('ProtocolType', wintypes.USHORT),
        ('Length', wintypes.USHORT),
        ('Specific', SPECIFIC),
    )


class PROTOCOLSTATUS(ctypes.Structure):
    __slots__ = ()
    _fields_ = (
        ('Output', PROTOCOLCOUNTERS),
        ('Input', PROTOCOLCOUNTERS),
        ('Cache', CACHE_STATISTICS),
        ('AsyncSignal', wintypes.ULONG),
        ('AsyncSignalMask', wintypes.ULONG),
    )


class WINSTATIONINFORMATION(ctypes.Structure):
    __slots__ = ()
    _fields_ = (
        ('ConnectState', ctypes.c_long),
        ('WinStationName', wintypes.WCHAR * (WINSTATIONNAME_LENGTH + 1)),
        ('LogonId', wintypes.ULONG),
        ('ConnectTime', wintypes.LARGE_INTEGER),
        ('DisconnectTime', wintypes.LARGE_INTEGER),
        ('LastInputTime', wintypes.LARGE_INTEGER),
        ('LogonTime', wintypes.LARGE_INTEGER),
        ('Status', PROTOCOLSTATUS),
        ('Domain', wintypes.WCHAR * (DOMAIN_LENGTH + 1)),
        ('UserName', wintypes.WCHAR * (USERNAME_LENGTH + 1)),
        ('CurrentTime', wintypes.LARGE_INTEGER)
    )


winsta.WinStationQueryInformationW.restype = wintypes.BOOLEAN 
winsta.WinStationQueryInformationW.argtypes = (
    wintypes.HANDLE, # ServerHandle
    wintypes.ULONG,  # SessionId
    ctypes.c_long,   # WinStationInformationClass
    wintypes.LPVOID, # pWinStationInformation
    wintypes.ULONG,  # WinStationInformationLength
    wintypes.PULONG, # pReturnLength
)


def get_idle_time(session_id=LOGONID_CURRENT,
                  server_handle=SERVERNAME_CURRENT):
    info = WINSTATIONINFORMATION()
    rlen = wintypes.ULONG()
    if not winsta.WinStationQueryInformationW(
                server_handle, session_id, WinStationInformation,
                ctypes.byref(info), ctypes.sizeof(info), ctypes.byref(rlen)):
        raise ctypes.WinError(ctypes.get_last_error())
    if info.ConnectState == State_Disconnected:
        last_input_time = info.DisconnectTime
    else:
        last_input_time = info.LastInputTime
    if not last_input_time:
        return None
    return (info.CurrentTime - last_input_time) / 1e7

---

Note that the `winsta` WinDLL instance is configured to capture the last error 
value (i.e. use_last_error=True). Thus if
WinStationQueryInformationW() fails, the relevant OSError exception is 
ctypes.WinError(ctypes.get_last_error()).
_______________________________________________
python-win32 mailing list
python-win32@python.org
https://mail.python.org/mailman/listinfo/python-win32

Reply via email to