← Back to blog

·4 min read·Daniel Diaz

Bulk changes in SAP S/4HANA with Python and SAP GUI Scripting — no RPA licence, no ABAP

How I automated mass updates in SAP S/4HANA from Python using the SAP GUI Scripting API: dry-run, per-row validation, change log, and when this approach makes sense.

Every data quality team has a version of this task: “these 400 accounts need the same field corrected”. In SAP that means opening the record, changing the field, saving, and doing it again 399 times. It is slow, it is boring, and — this is the part that matters — it produces new errors while you fix the old ones. After the second afternoon of this I wrote a tool. This is how it works and what I would tell someone who wants to build the same thing.

The options I did not take

What I wanted was in between: no new systems, no licences, but real code with tests, validation and a log.

SAP GUI Scripting, driven from Python

SAP GUI Scripting is an automation interface built into the SAP GUI for Windows. When your admins enable it (server-side parameter sapgui/user_scripting, plus the client option), the running GUI exposes a COM object you can control: sessions, windows, fields, buttons, grids. Anything a user can click, a script can click — with the same permissions as the logged-in user, which is exactly what you want for auditability.

From Python the only dependency is pywin32. A minimal, generic example — open a transaction, fill a field, press Enter, read the status bar:

import win32com.client

sap = win32com.client.GetObject("SAPGUI").GetScriptingEngine
session = sap.Children(0).Children(0)          # first connection, first session

session.findById("wnd[0]/tbar[0]/okcd").text = "/nXD03"   # transaction code
session.findById("wnd[0]").sendVKey(0)                     # Enter

session.findById("wnd[0]/usr/ctxtRF02D-KUNNR").text = "0000123456"
session.findById("wnd[0]").sendVKey(0)

status = session.findById("wnd[0]/sbar").text             # "Customer 123456 displayed" or an error
print(status)

The findById paths come from the GUI’s own recorder or from the scripting tracker; you read them once and keep them in a small map. That is all the “magic” there is. The real work is everything around it.

What makes it usable by other people

The first version was a script only I could run. The version other departments use has four things the script did not:

  1. Excel in, Excel out. A template with one row per record: key, field, new value. The tool reads it, executes it, and writes the outcome next to each row (“changed”, “skipped: already had that value”, “error: record locked”). Nobody has to read a console.
  2. Dry-run first. Same code path, but it stops before saving: it navigates, reads the current value and reports what would change. Every batch runs in dry-run before it runs for real, and the report is what the requester signs off on.
  3. Per-row validation against the GUI. Spreadsheets go stale. Before writing, the tool compares the value on screen with the “old value” column; if they differ, the row is skipped and flagged. This single check has prevented more damage than any other feature.
  4. A change log. Before/after value, record key, user, timestamp, batch id. It makes the batch auditable, and it makes it reversible: a batch can be replayed backwards.

Plus the boring but decisive part: error messages in plain language, because the user is a colleague from Revenue or Finance, not a developer.

What it taught me about data quality

When this approach makes sense — and when it does not

It makes sense for occasional, medium-sized batches (tens to a few thousand records) on objects you do not have a proper mass-maintenance path for, in a team without an RPA platform. It does not make sense for high-volume recurring loads (use the proper integration), for changes that touch financial postings (get the right transaction and approvals), or if the GUI screens change often (the paths will drift).

If you are in that middle ground, a few hundred lines of Python and a spreadsheet template will save your team real afternoons — as long as you build the dry-run and the log before you build the clicks.