I'm trying to write a Python script on Windows 10 that finds a window by its title and then moves/resizes it using pywin32. I have the following code:
import win32gui
# Global variables
found_hwnd = None
partial_title = None # We'll set this before calling EnumWindows
def enum_windows_callback(hwnd, lParam):
global found_hwnd, partial_title
if win32gui.IsWindowVisible(hwnd):
window_title = win32gui.GetWindowText(hwnd)
if partial_title and partial_title.lower() in window_title.lower():
found_hwnd = hwnd
# Returning False stops the enumeration
return False
# Continue enumerating
return True
def find_window_by_title(title_substring):
global found_hwnd, partial_title
found_hwnd = None
partial_title = title_substring
win32gui.EnumWindows(enum_windows_callback, None)
return found_hwnd
def move_and_resize_window(hwnd, x, y, width, height):
if hwnd is None:
print("No valid window handle provided.")
return False
win32gui.MoveWindow(hwnd, x, y, width, height, True)
return True
if __name__ == "__main__":
# Example usage:
hwnd = find_window_by_title("Cha")
if hwnd:
print(f"Found window with HWND: {hwnd}")
success = move_and_resize_window(hwnd, 100, 100, 800, 600)
if success:
print("Window moved and resized successfully.")
else:
print("Failed to move and resize the window.")
else:
print("No window found containing 'Notepad' in its title.")
When running the above code, sometimes I get an error:
pywintypes.error: (126, 'EnumWindows', 'The specified module could not be found.')
This happens when I try to find a known window by title. Strangely, if I look for a title that doesn't exist, it exits cleanly with "No window found." But for certain existing windows, it throws the error.
What could cause EnumWindows to throw "The specified module could not be found" (error 126) intermittently when using pywin32? Is there something about the environment, the way the callback is structured, or pywin32 setup that I’m missing?
Any insights or troubleshooting steps would be greatly appreciated.
What I've tried:
It's a mix of issues:
Yours (primarily):
Not complying with [MS.Learn]: EnumWindows function (winuser.h) which states (emphasis is mine):
If EnumWindowsProc returns zero, the return value is also zero. In this case, the callback function should call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
[SO]: C++: Best way to get Window Handle of the only window from a process by process id, process handle and title name (@IInspectable's answer) contains an example (C)
The proper way of passing data to / from functions is via arguments, and not relying on globals (this is not related to the error, but rather a generic programming guideline)
PyWin32: [GitHub]: mhammond/pywin32 - Last error wrongly set by some modules (scheduled for the next release (v309)).
Check [SO]: How to change username of job in print queue using python & win32print (@CristiFati's answer) (at the end) for possible ways to go further
Corrected (relevant parts of) your code.
code00.py:
#!/usr/bin/env python
import sys
import win32api as wapi
import win32gui as wgui
wapi.SetLastError(0) # Work around PyWin32 bug
def callback_first_window(hwnd, l_param):
if wgui.IsWindowVisible(hwnd):
partial_title = l_param[0]
window_title = wgui.GetWindowText(hwnd)
if partial_title and partial_title.lower() in window_title.lower():
print(f" - callback: [{partial_title.lower()}], [{window_title.lower()}]")
l_param[1] = hwnd
wapi.SetLastError(0)
# Returning False stops the enumeration
return False
# Continue enumerating
return True
def find_window_by_title(title_substring):
l_param = [title_substring, 0]
res = wgui.EnumWindows(callback_first_window, l_param)
return l_param[1]
def main(*argv):
text = argv[0] if argv else "not existing"
hwnd = find_window_by_title(text)
print(f"Window (text: [{text}]): {hwnd}")
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
(py_pc064_03.10_test1_pw32) [cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q079262076]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> [prompt]> dir /b code00.py [prompt]> [prompt]> python ./code00.py xxxxx Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 - callback: [xxxxx], [administrator: c:\windows\system32\cmd.exe - sopr.bat - python ./code00.py xxxxx] Window (text: [xxxxx]): 13179592 Done. [prompt]> [prompt]> python ./code00.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Window (text: [not existing]): 0 Done.
Notes:
Passing the window text part as a command line argument is a bit tricky (as seen), since the Cmd window will change its title so that it contains the command being run, and as a result the callback will always return that Cmd window (if no other window is enumerated before it)
Callback (and find_window_by_title) can be easily modified to return all windows matching the specified criteria, instead of stopping at the 1st