When using win32api.setConsoleCtrlHandler(), I'm able to receive shutdown/logoff/etc events from Windows, and cleanly shut down my app.
However, this only works when running the app under python.exe (i.e., it has a console window), but not under pythonw.exe (no console window).
Is there an equivalent way in Windows to receive these events when you have no console and no window to receive them? Or, is there a programmatic way to hide the console window?
To be clear - my goal is to be able to successfully receive Windows shutdown/logoff/etc events, without having any kind of console window showing.
EDIT:
I've been playing around, and I've gotten quite a bit further. I wrote a piece of test code for this. When I do a taskkill /im pythonw.exe - it will receive the message.
However, when I do a shutdown, restart, or logoff on Windows, I do not get any messages.
Here's the whole thing:
""" Testing Windows shutdown events """
import win32con
import win32api
import win32gui
import sys
import time
def log_info(msg):
""" Prints """
print msg
f = open("c:\\test.log", "a")
f.write(msg + "\n")
f.close()
def wndproc(hwnd, msg, wparam, lparam):
log_info("wndproc: %s" % msg)
if __name__ == "__main__":
log_info("*** STARTING ***")
hinst = win32api.GetModuleHandle(None)
wndclass = win32gui.WNDCLASS()
wndclass.hInstance = hinst
wndclass.lpszClassName = "testWindowClass"
messageMap = { win32con.WM_QUERYENDSESSION : wndproc,
win32con.WM_ENDSESSION : wndproc,
win32con.WM_QUIT : wndproc,
win32con.WM_DESTROY : wndproc,
win32con.WM_CLOSE : wndproc }
wndclass.lpfnWndProc = messageMap
try:
myWindowClass = win32gui.RegisterClass(wndclass)
hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
myWindowClass,
"testMsgWindow",
0,
0,
0,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.HWND_MESSAGE,
0,
hinst,
None)
except Exception, e:
log_info("Exception: %s" % str(e))
if hwnd is None:
log_info("hwnd is none!")
else:
log_info("hwnd: %s" % hwnd)
while True:
win32gui.PumpWaitingMessages()
time.sleep(1)
I feel like I'm pretty close here, but I'm definitely missing something!
The problem here was that the HWND_MESSAGE window type doesn't actually receive broadcast messages - like the WM_QUERYENDSESSION and WM_ENDSESSION.
So instead of specifying win32con.HWND_MESSAGE for the "parent window" parameter of CreateWindowEx(), I just specified 0.
Basically, this creates an actual window, but I never show it, so it's effectively the same thing. Now, I can successfully receive those broadcast messages and shut down the app properly.
If you don't have a console, setting a console handler of course can't work. You can receive system events on a GUI (non-console) program by making another window (doesn't have to be visible), making sure you have a normal "message pump" on it serving, and handling WM_QUERYENDSESSION -- that's the message telling your window about shutdown and logoff events (and your window can try to push back against the end-session by returning 0 for this message). ("Windows Services" are different from normal apps -- if that's what you're writing, see an example here).
Related
Source code
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
app = Application(backend='uia').start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
win = app['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.wait('ready')
win.menu_select("Setup->Refresh Serial and Ethernet")
win.top_window().print_control_identifiers(filename="file.txt")
# win.top_window().OKButton.click_input() ---------This is what I hope to do
else
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
Problem Statement
I had to run this application with elevation rights. The above is my code. The problem is I can't identify the window (view in output image) that pops up after selection from menu. I need to close the window. Please excuse the line
win.top_window().print_control_identifiers(filename="file.txt")
It was meant write the identifiers into a text file because the structure of this code does not display the outputs for me to view. However, since nothing is appended, I guess pywinauto couldn't identify the dialog.
For a clearer understanding, please view the image (input) of when it selects the menu.
Input
Now, it pops up with this dialog (output)
Output
I've also used spy to identify the caption and it gives:
(Handle: 004E07D4,
Caption: Information,
Class: #32770(Dialog),
Style: 94C801C5)
Other things I've tried:
Besides using win.topwindow() to identify the dialog, I've used
win[Information].OKButton.click_input()
win[Information].OK.click_input()
win[Information].OK.close()
win[Information].OK.kill(soft=false)
win.Information.OKButton.click_input()
win.Information.OK.click_input()
win.Information.OK.close()
win.Information.OK.kill(soft=false)
app[Information] ...... curious if I could discover the new window from original application
I've also send keys like enter, space, esc & alt-f4 to close the dialog with libraries like keyboard, pynput & ctypes. It still doesn't work.
Link to download the same application: http://downloadt.advantech.com/download/downloadsr.aspx?File_Id=1-1NHAMZX
Any help would be greatly appreciated !
I finally found a thread that demonstrated the way multi thread works to solve this issue. I tried it myself and it works. It's a little different as a few parts of the code have depreciated. Here is the link to the solution:
How to stop a warning dialog from halting execution of a Python program that's controlling it?
Here are the edits I made to solve the problem:
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
def __init__(self, window_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
def run(self):
while True:
try:
handles = windows.find_windows(title=self.window_name)
except windows.WindowNotFoundError:
pass
else:
for hwnd in handles:
app = Application()
app.connect(handle=hwnd)
popup = app[self.window_name]
popup.close()
if self.quit_event.is_set():
break
time.sleep(1)
quit_event = threading.Event()
mythread = ClearPopupThread('Information', quit_event)
mythread.start()
application = Application(backend="uia").start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
time.sleep(2)
win = application['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.menu_select("Setup->Refresh Serial and Ethernet")
quit_event.set()
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
The best thing is this solution works for every other dialog that halts the main script from working & I could use them to do different actions like clicking buttons, inserting values, by adding more multi threads.
I'm trying to automate an application (let's call it app.exe) by sending keystrokes to it. When its main window is open and has focus, doing ALT+F opens the File menu. I have inspected all the messages posted to the window with Spy++, and I have carefully studied all the parameters.
Then I tried to replicate exactly the same behaviour with PostMessage (to be sure I'm not sending to the wrong window, I'm even looping on all hWnd associated to the relevant process ID):
import win32con, win32gui, win32process, win32api, subprocess, time
def get_hwnds_for_pid(pid):
def callback (hwnd, hwnds):
if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid:
hwnds.append(hwnd)
return True
hwnds = []
win32gui.EnumWindows(callback, hwnds)
return hwnds
app = subprocess.Popen(["notepad.exe"])
time.sleep(2.0)
for hwnd in get_hwnds_for_pid(app.pid):
win32gui.SetForegroundWindow(hwnd)
win32api.PostMessage(hwnd, win32con.WM_SYSKEYDOWN, 0x12, 0x20380001) # ALT
win32api.PostMessage(hwnd, win32con.WM_SYSKEYDOWN, 0x46, 0x20210001) # F
time.sleep(1.0)
win32api.PostMessage(hwnd, win32con.WM_KEYUP, 0x46, 0xC0210001)
win32api.PostMessage(hwnd, win32con.WM_KEYUP, 0x12, 0x20380001)
Problem: this works when the app is notepad.exe. But it fails with another particular application I'm using (a regular Win32 application, which uses QWidget for the main window).
To be more precise, I see the _ underline flashing under the first letter of the menu during one second (so the simulated ALT is recognized ; the usual behaviour in Windows application is that the underlined F appears when you hold the ALT button down) but the F is not recognized.
Once again, I have sent with PostMessage exactly the same messages to the window as what I have observed with Spy++ when I did the hotkey manually with the real keyboard.
Question: what could prevent a given software app.exe to receive messages posted/injected from another application (=here, my Python script)?
Backstory: I'm creating an application using Electron and am currently attempting to run a function when the computer is locked/unlocked.
After much trial and error I finally managed to get the following python code working. The code prints either Locked or Unlocked on the screen when the relevant codes are fired. I now need to run the python script from Node JS so that I can run more functions when the events fire.
import win32con
import win32gui
import win32ts
import time
print("Test")
WM_WTSSESSION_CHANGE = 0x2B1class WTSMonitor():
className = "WTSMonitor"
wndName = "WTS Event Monitor"
def __init__(self):
wc = win32gui.WNDCLASS()
wc.hInstance = hInst = win32gui.GetModuleHandle(None)
wc.lpszClassName = self.className
wc.lpfnWndProc = self.WndProc
self.classAtom = win32gui.RegisterClass(wc)
style = 0
self.hWnd = win32gui.CreateWindow(self.classAtom, self.wndName,
style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
0, 0, hInst, None)
win32gui.UpdateWindow(self.hWnd)
win32ts.WTSRegisterSessionNotification(self.hWnd, win32ts.NOTIFY_FOR_ALL_SESSIONS)
def start(self):
win32gui.PumpMessages()
def stop(self):
win32gui.PostQuitMessage(0)
def WndProc(self, hWnd, message, wParam, lParam):
if message == WM_WTSSESSION_CHANGE:
self.OnSession(wParam, lParam)
def OnSession(self, event, sessionID):
if event == 7:
print("Locked")
if event == 8:
print("Unlocked")
print(event)
myststa(event)
WTSMonitor().start()
The Node code:
const { spawn } = require('child_process');
let py = spawn('python',['locked.py'])
py.stdout.on('data', data => console.log('data : ', data.toString()))
py.on('close', ()=>{
})
When I run python from the console using "Python locked.py" I see the test message printed. However, when running using node locked.js the script looks like it's running but never prints to the console.
The other thing to mention is that if I comment out the final line WTSMonitor().start() then I can see the test message print to the node console.
Before getting down actual troubleshooting, what's main reason to use python to invoke w32api? First you can do it via electron / nodejs itself, moreover electron will have a specific event in powerMonitor directly (https://github.com/electron/electron/blob/3a0640993ba9748ced9f9cd00de5dbfe7651f788/docs/api/power-monitor.md#event-lock-screen-macos-windows) doesn't necessarily need 3rd party codes.
I am trying to run a python script in Windows to send an F5 command to a Google Chrome v53 window. But unfortunately nothing happens. But it works like a charm with Internet Explorer v11 (after changing the Class Name).
Winspector detects the Class Name of the Google Chrome window
Python finds the window and sends the command
No message gets logged as send to the process
Python code
import win32gui, win32api, win32con
hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", None)
print hwnd
# Sending command to the main windows also does not work
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
hwnd = win32gui.FindWindowEx(hwnd, None, "Chrome_RenderWidgetHostHWND", None)
print hwnd
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
Python output
1181972
0
EDIT: Included hidden windows
There seems to be more windows, that have the same Class Name. Therefore, I am now searching not only by the Class Name, but also by the Text. The first attempt did only find a window, which did not have any children. Therefore the second line of the output had a 0.
Altered Python code
import win32gui, win32api, win32con
hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", "Stack Overflow - Google Chrome")
print hwnd
# Sending command to the main windows also does not work
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
hwnd = win32gui.FindWindowEx(hwnd, None, "Chrome_RenderWidgetHostHWND", None)
print hwnd
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
Python output
1247302
1509536
EDIT: SetForegroundWindow solution
The solution for the problem was first to set the window to foreground by win32gui.SetForegroundWindow(Handle) method and then to send the key commands.
But still, no commands are passed to the window and the website is not refreshed.
You can use this
import pyautogui
pyautogui.hotkey('f5')
This will simulate your refresh key, sending the signal to chrome.
In case you don't want the chrome window to be made active, then when you START chrome, start it by typing this in the command prompt:
start /B chrome.exe
Then close chrome. It will run in the background. When the work is done, go Task Manager and close chrome.
OR you could go to Chrome>Advanced>System and see the first option.
How can I check whether a Windows OS workstation is locked? (e.g. Win+L or choosing the lock option after Ctrl+Alt+Del.)
I want something like ctypes.windll.user32.isWorkstationLocked().
This code worked today for me on four different Windows 7 and 10 machines, try something similar:
import ctypes
import time
user32 = ctypes.windll.User32
time.sleep(5)
#
#print(user32.GetForegroundWindow())
#
if (user32.GetForegroundWindow() % 10 == 0): print('Locked')
# 10553666 - return code for unlocked workstation1
# 0 - return code for locked workstation1
#
# 132782 - return code for unlocked workstation2
# 67370 - return code for locked workstation2
#
# 3216806 - return code for unlocked workstation3
# 1901390 - return code for locked workstation3
#
# 197944 - return code for unlocked workstation4
# 0 - return code for locked workstation4
#
else: print('Unlocked')
Edit: Also, this one works today:
import subprocess
import time
time.sleep(5)
process_name='LogonUI.exe'
callall='TASKLIST'
outputall=subprocess.check_output(callall)
outputstringall=str(outputall)
if process_name in outputstringall:
print("Locked.")
else:
print("Unlocked.")
A hack I discovered to get around to see if Windows 10 is locked is to look at the running processes using psutil. You then search to see whether or not LogonUI.exe is running. This process only runs when a user has a locked session.
Note: If you use "switch users" this process will be shown as running and this workaround will not work. Windows actually spawns multiple LogonUI.exe processes, one per logged on locked user. It is only useful when there is only one person logged on at a time.
import psutil
for proc in psutil.process_iter():
if(proc.name() == "LogonUI.exe"):
print ("Locked")
You can get the window on top, when the session is locked, the function return 0.
import ctypes
user32 = ctypes.windll.User32
def isLocked():
return user32.GetForegroundWindow() == 0
Something like this should do the trick:
import time
import ctypes
user32 = ctypes.windll.User32
OpenDesktop = user32.OpenDesktopA
SwitchDesktop = user32.SwitchDesktop
DESKTOP_SWITCHDESKTOP = 0x0100
while 1:
hDesktop = OpenDesktop ("default", 0, False, DESKTOP_SWITCHDESKTOP)
result = SwitchDesktop (hDesktop)
if result:
print "Unlocked"
time.sleep (1.0)
else:
print time.asctime (), "still locked"
time.sleep (2)
From the LockWorkStation() documentation:
There is no function you can call to determine whether the workstation is locked.
Not a Python limitation, but the system itself.
What works for me on Windows 10 Pro is getting the foreground window:
whnd = win32gui.GetForegroundWindow()
(_, pid) = win32process.GetWindowThreadProcessId(whnd)
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid)
filename = win32process.GetModuleFileNameEx(handle, 0)
window_text = win32gui.GetWindowText(whnd)
This returns Windows Default Lock Screen as window title and C:\Windows\SystemApp\Microsoft.LockApp_<randomcharacters>\LockApp.exe as filename when locked.
However, as James Koss mentioned, GetForeGroundWindow will return 0 if the user is typing their password. There are also other (non-locked) situations where the current ForegroundWindow is 0, so this cannot be relied upon.
Hi Check these 4 lines..
returns the application name which is on the screen.. if window is locked returns the string - Windows Default Lock Screen.
from win32gui import GetWindowText, GetForegroundWindow
import time
time.sleep(5)
# lock the system or open the application for a check
print(GetWindowText(GetForegroundWindow()))
Based on #Stardidi answer, this worked for me (Windows 10 Pro):
import time
import win32gui
import win32api
import win32con
import win32process
while True:
time.sleep(1)
_, pid = win32process.GetWindowThreadProcessId(win32gui.GetForegroundWindow())
try:
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid)
filename = win32process.GetModuleFileNameEx(handle, 0)
except Exception as _e:
filename = "LockApp.exe"
del _e
current_status = "locked" if "LockApp" in filename else "unlocked"
There is no easy answer here, but you can do this via Session Tracking.
From the LockWorkStation() documentation:
There is no function you can call to determine whether the workstation is locked. To receive notification when the user logs in, use the WTSRegisterSessionNotification function to receive WM_WTSSESSION_CHANGE messages. You can use session notifications to track the desktop state so you know whether it is possible to interact with the user.
Begin by registering session notifications to a window of your program.
def register(handle: HWND) -> bool:
"""
#param handle: handle for your message window.
When registered, Windows Messages related to session event changes will be
sent to the message window.
#returns: True is session tracking is successfully registered.
Blocks until Windows accepts session tracking registration.
Every call to this function must be paired with a call to unregister.
https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification
"""
# OpenEvent handle must be closed with CloseHandle.
eventObjectHandle: HANDLE = ctypes.windll.kernel32.OpenEventW(
# Blocks until WTS session tracking can be registered.
# Windows needs time for the WTS session tracking service to initialize.
# must ensure that the WTS session tracking service is ready before trying to register
SYNCHRONIZE, # DWORD dwDesiredAccess
False, # BOOL bInheritHandle - sub-processes do not need to inherit this handle
# According to the docs, when the Global\TermSrvReadyEvent global event is set,
# all dependent services have started and WTSRegisterSessionNotification can be successfully called.
# https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification#remarks
"Global\\TermSrvReadyEvent" # LPCWSTR lpName - The name of the event object.
)
if not eventObjectHandle:
error = ctypes.WinError()
log.error("Unexpected error waiting to register session tracking.")
return False
registrationSuccess = ctypes.windll.wtsapi32.WTSRegisterSessionNotification(handle, NOTIFY_FOR_THIS_SESSION)
ctypes.windll.kernel32.CloseHandle(eventObjectHandle)
if registrationSuccess:
log.debug("Registered session tracking")
else:
error = ctypes.WinError()
if error.errno == RPC_S_INVALID_BINDING:
log.error(
"WTS registration failed. "
"Waited successfully on TermSrvReadyEvent to ensure that WTS is ready to allow registration. "
"Cause of failure unknown. "
)
else:
log.error("Unexpected error registering session tracking.")
return registrationSuccess
def unregister(handle: HWND) -> None:
"""
This function must be called once for every call to register.
If unregistration fails, session tracking may not work properly until the session can be unregistered in a new instance.
https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsunregistersessionnotification
"""
if ctypes.windll.wtsapi32.WTSUnRegisterSessionNotification(handle):
log.debug("Unregistered session tracking")
else:
error = ctypes.WinError()
log.error("Unexpected error unregistering session tracking.")
In your Window Message handler for the window, when you receive WM_WTSSESSION_CHANGE, handle WTS_SESSION_UNLOCK and WTS_SESSION_LOCK events to track the state of Windows being locked.
A similar answer here might give more context on handling windows messages.