# pywinauto: text-based Windows GUI automation from Python

> pywinauto drives Windows dialogs and controls through the Win32 API or MS UI Automation, addressing widgets by name instead of screen coordinates. It suits Windows-only test suites and desktop task scripts, and it is the wrong tool for anything that has to run on macOS or Linux.

**pywinauto/pywinauto** — Windows GUI Automation with Python (based on text properties)

- Repository: https://github.com/pywinauto/pywinauto
- Website: http://pywinauto.github.io/
- Stars: 6,189 · Forks: 785
- Language: Python
- License: BSD-3-Clause
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/pywinauto-pywinauto

## What pywinauto automates, and who ends up using it

pywinauto is a set of Python modules for automating the Microsoft Windows GUI. The README puts the simplest case plainly: it can send mouse and keyboard actions to windows dialogs and controls, and it goes further by reading text data out of them. That second half is the reason to pick it over a coordinate-clicking tool. When a control exposes its name, you can assert on the label a user would see instead of on a screenshot.

The audience is narrow and specific. QA engineers maintaining regression suites for a packaged Windows application. Developers scripting repetitive desktop work, such as installing an MSI, exporting a report from a legacy client, or walking a wizard that has no command-line equivalent. The repository's examples directory reflects that range: install_7zip.py and uninstall_7zip.py, get_winrar_info.py, mspaint.py, win10_calculator.py, list_windows_updates.py, wireshark.py. These are tasks where the only interface is a window.

It is a poor fit for anything browser-based, and the README makes no claim otherwise. The supported technologies are Win32 API and MS UI Automation. A web app rendered inside a browser process presents a different tree, and pywinauto is not the layer that reads it.

## Two backends, two different trees

The core design decision is that pywinauto does not have one way of seeing a window. It has two backends, chosen per application object.

The default is backend="win32", built on the Win32 API. It is the older path and the one that works with classic controls: buttons, edits, list views, menus. The alternative is backend="uia", built on MS UI Automation, which is what modern frameworks expose. If a dialog comes back with almost no named controls under win32, that is usually the signal to try uia.

The README's UI Automation example shows the practical consequence. It launches explorer.exe, then connects to a process it did not start:

```python
from pywinauto import Desktop, Application

Application().start('explorer.exe "C:\\Program Files"')

# connect to another process spawned by explorer.exe
# Note: make sure the script is running as Administrator!
app = Application(backend="uia").connect(path="explorer.exe", title="Program Files")
```

Two details in those lines matter more than the rest of the example. The comment about Administrator is not decorative: connect() reaches into another process, and Windows will refuse that across integrity levels. And Desktop(backend='uia') is a separate access path that does not rely on any process id, which is how the example reaches a properties dialog that explorer.exe opened in yet another process.

User input emulation is split out. The mouse and keyboard modules work on both Windows and Linux, according to the README, so input synthesis is not tied to the Windows backends even though control inspection is.

## Installing pywinauto and driving Notepad

The README gives two package-manager routes. With pip:

```bash
pip install -U pywinauto
```

Or with conda:

```bash
conda install -c conda-forge pywinauto
```

Installing manually pulls in pyWin32 and comtypes on Windows, and python-xlib on Linux. Pillow is optional and only needed for capture_as_image(), the method that snapshots a control. The README also notes that running the project's own unit tests requires Pillow and coverage, invoked as python ./pywinauto/unittests/testall.py.

For a first real script, the README's Notepad example is the smallest thing that exercises the whole idea: start a process, address a menu, click a button, type into an edit control.

```python
from pywinauto.application import Application
app = Application().start("notepad.exe")

app.UntitledNotepad.menu_select("Help->About Notepad")
app.AboutNotepad.OK.click()
app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
```

What you should see: Notepad opens, the About dialog appears and closes, and the text lands in the edit area. The attribute names are the interesting part. UntitledNotepad, AboutNotepad and Edit are not variables you declared. They are resolved at runtime from window titles and control text, which is why the scripts read like a description of the screen. When a name does not resolve, the library raises rather than silently clicking somewhere else, and that failure is your cue to inspect the window properly.

## Finding the right control name before you write the test

The step the README's short example hides is discovery. You cannot guess that a control is called Edit or ItemsView; you have to ask the running application. The UI Automation example does exactly that with one call:

```python
Properties = Desktop(backend='uia').Common_Files_Properties
Properties.print_control_identifiers()
Properties.Cancel.click()
Properties.wait_not('visible') # make sure the dialog is closed
```

print_control_identifiers() dumps the control tree with the names pywinauto will accept. This is the loop you actually work in: launch the app, connect with the backend you think is right, print the identifiers, then write selectors against what came back. The README points to a Getting Started Guide that covers the Spy and Inspect tools for the same purpose, viewing a window's structure outside Python.

Two smaller mechanisms in that snippet are worth copying. wait_not('visible') waits for the dialog to disappear, which is how you avoid asserting on a window that is still closing. And the README shows get_item('Common Files') for reaching an item inside an ItemsView, because list contents are not addressable by attribute name the way a button is.

If the identifier dump is a wall of unnamed panes, stop. That is the honest signal that the application does not expose a usable automation tree, and no amount of selector tuning will fix it.

## Where pywinauto breaks down

The biggest limitation is stated by the project itself. pywinauto automates the Microsoft Windows GUI. The README says the library "tends to be cross-platform" and floats Linux in 2018 and macOS in 2019, but that is a stated intention, not a shipped capability, and the release history does not show it arriving. Treat any Windows-only assumption as permanent when you plan the suite.

Privilege boundaries are the second failure mode, and the README flags it in a code comment rather than in prose. connect() into a process running at a higher integrity level fails unless your script runs as Administrator. That is not a bug to work around; it is how the operating system isolates processes, and it means an automation script inherits the deployment constraints of the app it drives.

The third is structural. Text-based addressing only works when the target exposes text. Applications that render their own interface, or that draw to a canvas, present a tree with few or no named controls, and pywinauto has nothing to bind to. The README's own examples lean toward applications with conventional widgets: 7-Zip, WinRAR, Paint, the Windows calculator, Wireshark, media players. That list is not a coincidence.

Finally, the maintenance picture is worth reading straight. The repository is not archived, and the last push was on 2026-05-23. The most recent release is 0.6.9 from 2025-01-06, labelled "The Last Python 2.7 Compatible Release"; before that, 0.6.8 dates to 2019-10-27 and 0.6.7 to 2019-07-06. The README describes the project as "a hobby project for all of us", with feature work happening "during out-of-office hours". Plan for a library that moves in bursts.

## pywinauto compared with PyAutoGUI

The comparison people reach for is PyAutoGUI, and the difference is the addressing model. PyAutoGUI works from screen coordinates and images: you tell it to move the mouse to a position or find a picture on screen, and it acts there. pywinauto works from control identity: you name a window and a control, and the library resolves that to a live handle before acting.

That distinction decides which one survives a UI change. Move a button ten pixels and a coordinate script clicks the wrong place, while a selector on the button's text still resolves. Conversely, a game or a custom-drawn canvas has no control names at all, and only the coordinate approach can reach it.

The two are not exclusive. The README notes that pywinauto's mouse and keyboard modules work on both Windows and Linux, so input synthesis is available independently of the Windows backends. The realistic split is that pywinauto owns the Windows desktop case where the application exposes a control tree, and a coordinate-based tool covers everything else.

## Licence, upgrade cost and what the README leaves open

pywinauto is distributed under the BSD 3-clause licence starting from version 0.6.0. Versions 0.5.4 and earlier were under the LGPL v2.1 or later. The setup.py header carries the standard three clauses: source redistributions must retain the copyright notice and disclaimer, binary redistributions must reproduce them in the documentation, and the names of pywinauto and its contributors may not be used to endorse derived products without permission. For most commercial use the practical effect is that you keep the notice and do not imply endorsement. That is a description of the licence text, not legal advice; have counsel read it if the distinction matters to your product.

Upgrade cost is low in the sense that the install is a single pip or conda command with no service to run. It is higher in the sense that the release cadence is uneven. Going from 0.6.8 to 0.6.9 spanned more than five years, and the 0.6.9 label ties that release to Python 2.7 compatibility rather than to new automation capability. A team adopting this should pin a version and read the changelog before moving, because there is no steady stream of patch releases to absorb a regression.

The README is silent on several things a maintainer would want. There is no documented rollback procedure for a failed automation run, no stated support window for older releases, and no compatibility matrix mapping Windows versions to backends. The cross-platform statements are framed as intentions with target years that have passed. Those gaps are not fatal for a Windows-only suite, but they are the questions to answer internally before the library ends up in a release pipeline.

## Conclusion

Adopt pywinauto when the application under test is a Windows desktop program and your checks need to read control text rather than click at pixel offsets; the uia backend plus print_control_identifiers is the fastest way to learn whether a given dialog exposes enough structure to automate. Do not adopt it for cross-platform suites or for Electron and web UIs, where the Win32 and UI Automation trees are thin or absent. Before writing a suite, run the target app, connect with the matching backend, and print the control identifiers for the window you intend to drive. If that listing is mostly unnamed panes, the project will cost more than it returns.

## FAQ

### What are the key differences between PyAutoGUI and pywinauto?

pywinauto addresses windows and controls by their text properties through the Win32 API or MS UI Automation, so a selector follows a control when it moves. PyAutoGUI-style coordinate and image automation acts on screen positions instead, which is the only option when an application exposes no control tree.

### How do I install pywinauto?

The README gives two routes: pip install -U pywinauto, or conda install -c conda-forge pywinauto. A manual install needs pyWin32 and comtypes on Windows, and python-xlib on Linux.

### Where can I find the documentation for pywinauto?

The README links a Short Intro on ReadTheDocs and a Getting Started Guide covering core concepts and the Spy and Inspect tools. It also points to the pywinauto tag on StackOverflow and a mailing list for questions.

### What is pywinauto in Python?

It is a set of Python modules for automating the Microsoft Windows GUI. The README describes it as able to send mouse and keyboard actions to windows dialogs and controls, with support for reading text data out of them.

### Is pywinauto open source?

Yes. It is distributed under the BSD 3-clause licence from version 0.6.0 onward; versions 0.5.4 and earlier used the LGPL v2.1 or later. The source is on GitHub at pywinauto/pywinauto.

### Is pywinauto free?

The README lists no paid tier or licence fee; installation is through pip or conda, and the project is distributed under the BSD 3-clause licence from 0.6.0 onward. The README does mention donations and stars as ways to support development.

## Sources

- [License: BSD-3-Clause](https://github.com/pywinauto/pywinauto/blob/master/LICENSE)
- [Project website](http://pywinauto.github.io/)
- [pywinauto/pywinauto on GitHub](https://github.com/pywinauto/pywinauto)
- [README](https://github.com/pywinauto/pywinauto/blob/master/README.md)
- [Releases](https://github.com/pywinauto/pywinauto/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pywinauto-pywinauto
