Introduction
Many SAP users still perform repetitive tasks in SAP GUI manually — logging into systems, navigating transactions, adjusting customizing parameters, or extracting reports.With the rise of AI-assisted development and automation frameworks, it is now possible to orchestrate these operations programmatically.
In this article, I present an end-to-end automation scenario that combines:
- Python
- SAP GUI Scripting
- AI-assisted coding with Codex
The solution demonstrates how an AI agent can:
- Log into SAP GUI automatically
- Execute customizing steps
- Navigate transactions
- Extract data and generate reports
By integrating AI-assisted scripting with SAP GUI automation, organizations can significantly accelerate operational tasks, reduce human error, and improve productivity.
This approach illustrates how AI agents can augment SAP consultants and administrators, enabling faster execution of routine activities and creating a foundation for more advanced intelligent automation in SAP landscapes.
Accelerate with AI Agents: Build, Adapt, and Report in SAP at Speed
1.What You Can Automate
With Codex + SAP GUI scripting, you can automate both daily operations and advanced SAP configuration work in a practical, scalable way:
- Perform SAP customizing tasks directly from scripts
- Execute mass customizing by providing Excel-based input files to Codex and applying changes in bulk
- Run operational and analytical reports automatically (for example via SE16N-driven workflows)
- Validate existing configurations and compare system state against expected setup rules
- Build guided simulations for training and decision support (for example, simulate and identify the best price scenario for a specific material)
In short, you can move from manual, screen-by-screen execution to controlled, repeatable, and auditable automation across reporting, configuration, validation, and simulation use cases.
Prerequisites (Before Any Connection)
2.1 SAP-side prerequisites
- SAP GUI for Windows must be installed
- SAP Logon entry must exist and be tested manually
- SAP GUI Scripting must be enabled:
- SAP GUI → Options → Accessibility & Scripting → Scripting
- Enable scripting (client-side)
- Server-side scripting parameter must allow scripting:
- sapgui/user_scripting = TRUE (Basis side)
- Your user needs authorization for:
- target transactions (SE16N, OVX4, OVX5, etc.)
- customizing save + transport assignment (if doing config)
2.2 Local environment prerequisites
- Python 3.10+ installed
- Install required packages:
python -m pip install pywin32 openpyxl pypdf - Keep SAP Logon open before script execution (recommended)
- Codex installed ( paid subscription required )
3. Project Structure (Recommended)
Use a simple folder structure:
automation/ sap_connect.py - sap_connect.py: connection/bootstrap layer
4. Connection Script Design (Core)
Your sap_connect.py should handle:
- Attach to running SAP GUI:
- GetObject("SAPGUI")
- Start SAP Logon if not running
- Open connection by exact SAP Logon entry name
- Wait until session window is ready
- Detect login screen vs already authenticated menu
- Handle post-login popups (information/multiple logon)
- Verify status bar for login errors
- Return a reusable session wrapper
Example Script:
"""Open or create an SAP GUI session and authenticate (SSO or password).
"""
import argparse
import getpass
import logging
import os
import subprocess
import sys
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
import win32com.client
try:
from sap_scripting import SapSession
except Exception:
class SapSession:
def __init__(self):
self.connection_index = 0
self.session_index = 0
self.application = None
self.connection = None
self.session = None
def get_session_info(self) -> Dict[str, Any]:
info = self.session.Info
return {
"system": getattr(info, "SystemName", ""),
"client": getattr(info, "Client", ""),
"user": getattr(info, "User", ""),
"transaction": getattr(info, "Transaction", ""),
"response_time": getattr(info, "ResponseTime", ""),
}
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger("sap_connect")
@dataclass(frozen=True)
class SapConnectConfig:
default_system: str = os.getenv("SAP_SYSTEM", "")
default_client: str = os.getenv("SAP_CLIENT", "")
default_user: str = os.getenv("SAP_USER", "")
default_language: str = os.getenv("SAP_LANGUAGE", "EN")
default_sso: bool = os.getenv("SAP_SSO", "true").lower() in ("1", "true", "yes")
login_wait: float = 2.0
popup_wait: float = 0.5
startup_timeout: int = 30
session_ready_timeout: float = 30.0
saplogon_candidates: tuple = (
r"C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe",
r"C:\Program Files\SAP\FrontEnd\SAPgui\saplogon.exe",
r"C:\Program Files (x86)\SAP\SAPLogon\saplogon.exe",
)
CONFIG = SapConnectConfig()
DEFAULT_SYSTEM = CONFIG.default_system
DEFAULT_CLIENT = CONFIG.default_client
DEFAULT_USER = CONFIG.default_user
DEFAULT_LANGUAGE = CONFIG.default_language
DEFAULT_SSO = CONFIG.default_sso
LOGIN_SCREEN_FIELDS = (
"wnd[0]/usr/txtRSYST-MANDT",
"wnd[0]/usr/txtRSYST-BNAME",
)
def _mask(text: str, enabled: bool) -> str:
if not enabled:
return text
if not text:
return text
if len(text) <= 2:
return "*" * len(text)
return text[0] + ("*" * (len(text) - 2)) + text[-1]
def _find_element(session: Any, element_id: str) -> Optional[Any]:
try:
return session.findById(element_id)
except Exception:
return None
def _attach_scripting_engine() -> Any:
rot_entry = win32com.client.GetObject("SAPGUI")
return rot_entry.GetScriptingEngine
def _ensure_saplogon_running() -> Any:
try:
app = _attach_scripting_engine()
log.info("SAP Logon already running. Active connections: %s", app.Children.Count)
return app
except Exception:
log.info("SAP Logon not detected - attempting to start it ...")
exe_path = next((p for p in CONFIG.saplogon_candidates if os.path.isfile(p)), None)
if exe_path is None:
raise ConnectionError(
"SAP Logon is not running and saplogon.exe was not found in:\n"
+ "\n".join(f" {p}" for p in CONFIG.saplogon_candidates)
)
log.info("Launching SAP Logon from: %s", exe_path)
subprocess.Popen([exe_path])
deadline = time.time() + CONFIG.startup_timeout
while time.time() < deadline:
try:
app = _attach_scripting_engine()
log.info("SAP Logon started successfully.")
return app
except Exception:
time.sleep(1.0)
raise ConnectionError(
f"SAP Logon did not become ready within {CONFIG.startup_timeout} seconds. "
"Check SAP GUI scripting settings."
)
def _wait_for_session_ready(connection: Any) -> Any:
deadline = time.time() + CONFIG.session_ready_timeout
while time.time() < deadline:
try:
session = connection.Children(0)
_ = session.findById("wnd[0]").Text
return session
except Exception:
time.sleep(0.5)
raise RuntimeError("SAP window did not become ready within timeout after OpenConnection().")
def _detect_screen_state(session: Any) -> str:
if _find_element(session, LOGIN_SCREEN_FIELDS[0]) is not None:
return "LOGIN"
try:
tcode = session.Info.Transaction.strip()
if tcode and tcode != "LOGIN":
return "MENU"
except Exception:
pass
try:
if session.findById("wnd[0]").Text.strip():
return "MENU"
except Exception:
pass
return "UNKNOWN"
def _do_login(
session: Any,
client: str,
user: str,
password: str,
language: str,
sso: bool,
) -> None:
mandt = _find_element(session, "wnd[0]/usr/txtRSYST-MANDT")
if mandt and mandt.Changeable and client:
mandt.text = client
log.info("Client set.")
if not sso:
bname = _find_element(session, "wnd[0]/usr/txtRSYST-BNAME")
bcode = _find_element(session, "wnd[0]/usr/pwdRSYST-BCODE")
langu = _find_element(session, "wnd[0]/usr/txtRSYST-LANGU")
if bname:
bname.text = user
if bcode:
bcode.text = password
if langu and langu.Changeable:
langu.text = language
log.info("Credentials filled (user/password mode).")
else:
log.info("SSO mode - skipping username/password fields")
session.findById("wnd[0]").sendVKey(0)
log.info("Login submitted (Enter)")
def _handle_multiple_logon_popup(session: Any) -> None:
log.info("Multiple logon detected - selecting OPT2 (keep existing sessions).")
opt2 = _find_element(session, "wnd[1]/usr/radMULTI_LOGON_OPT2")
if opt2:
opt2.select()
confirm = _find_element(session, "wnd[1]/tbar[0]/btn[0]")
if confirm:
confirm.press()
else:
session.findById("wnd[1]").sendVKey(0)
def _handle_post_login_popups(session: Any) -> None:
time.sleep(CONFIG.popup_wait)
for _ in range(5):
popup = _find_element(session, "wnd[1]")
if popup is None:
break
log.info("Popup detected: '%s'", popup.Text.strip())
try:
if _find_element(session, "wnd[1]/usr/radMULTI_LOGON_OPT1"):
_handle_multiple_logon_popup(session)
else:
popup.sendVKey(0)
except Exception as exc:
log.warning("Popup handling failed: %s", exc)
time.sleep(CONFIG.popup_wait)
def _verify_login(session: Any) -> None:
sbar = _find_element(session, "wnd[0]/sbar")
if not sbar:
return
msg_type = getattr(sbar, "MessageType", "")
msg_text = getattr(sbar, "Text", "")
if msg_type in ("E", "A"):
raise RuntimeError(f"Login failed [{msg_type}]: {msg_text}")
if msg_type == "W":
log.warning("Login warning [%s]: %s", msg_type, msg_text)
elif msg_type == "S" and msg_text:
log.info("Login status [%s]: %s", msg_type, msg_text)
def _wrap_existing_session(application: Any, conn_idx: int, session_index: int = 0) -> SapSession:
sap = object.__new__(SapSession)
sap.connection_index = conn_idx
sap.session_index = session_index
sap.application = application
sap.connection = application.Children(conn_idx)
sap.session = sap.connection.Children(session_index)
return sap
def connect_to_system(
system: str = DEFAULT_SYSTEM,
client: str = DEFAULT_CLIENT,
user: str = DEFAULT_USER,
password: str = "",
language: str = DEFAULT_LANGUAGE,
sso: bool = DEFAULT_SSO,
) -> SapSession:
if not sso:
if not user:
user = input(f"SAP user for {system}/{client}: ").strip()
if not password:
password = getpass.getpass(f"Password for {user}@{system}: ")
app = _ensure_saplogon_running()
log.info("SAP Logon ready. Active connections before: %s", app.Children.Count)
log.info("Opening connection to '%s' (SSO=%s) ...", system, sso)
try:
connection = app.OpenConnection(system, True)
except Exception as exc:
raise ConnectionError(
f"Cannot open connection to '{system}': {exc}\n"
"Check SAP Logon entry name (case-sensitive)."
) from exc
session = _wait_for_session_ready(connection)
log.info("Session opened.")
state = _detect_screen_state(session)
log.info("Screen state after OpenConnection: %s", state)
if state == "LOGIN":
_do_login(session, client, user, password, language, sso)
time.sleep(CONFIG.login_wait)
elif state == "MENU":
log.info("SSO authenticated automatically - skipped login screen.")
else:
log.warning("Unexpected screen state '%s' - attempting to continue.", state)
_handle_post_login_popups(session)
_verify_login(session)
conn_idx = app.Children.Count - 1
sap = _wrap_existing_session(app, conn_idx, session_index=0)
info = sap.get_session_info()
log.info(
"Connected - system=%s, client=%s, user=%s, transaction=%s",
info["system"],
info["client"],
_mask(info["user"], True),
info["transaction"],
)
return sap
def list_logon_entries(mask_sensitive: bool = True) -> List[Dict[str, Any]]:
try:
app = _attach_scripting_engine()
active: List[Dict[str, Any]] = []
for i in range(app.Children.Count):
conn = app.Children(i)
for j in range(conn.Children.Count):
sess = conn.Children(j)
active.append(
{
"conn_idx": i,
"sess_idx": j,
"system": sess.Info.SystemName,
"client": _mask(sess.Info.Client, mask_sensitive),
"user": _mask(sess.Info.User, mask_sensitive),
"tcode": sess.Info.Transaction,
}
)
return active
except Exception as exc:
log.warning("list_logon_entries failed: %s", exc)
return []
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Open a new SAP session from SAP Logon and log in.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python sap_connect.py\n"
" python sap_connect.py --system MYSYS --client 100\n"
" python sap_connect.py --no-sso --user YOUR_USER\n"
" python sap_connect.py --list\n"
" python sap_connect.py --list --no-mask"
),
)
parser.add_argument("--system", default=DEFAULT_SYSTEM, help="SAP Logon entry description")
parser.add_argument("--client", default=DEFAULT_CLIENT, help="SAP client number")
parser.add_argument("--user", default=DEFAULT_USER, help="SAP username (ignored for SSO)")
parser.add_argument("--language", default=DEFAULT_LANGUAGE, help="Logon language")
parser.add_argument("--no-sso", dest="sso", action="store_false", help="Use password login")
parser.set_defaults(sso=DEFAULT_SSO)
parser.add_argument("--list", action="store_true", help="List active SAP sessions and exit")
parser.add_argument("--no-mask", action="store_true", help="Do not mask user/client in output")
return parser
def main() -> None:
args = _build_parser().parse_args()
if args.list:
sessions = list_logon_entries(mask_sensitive=not args.no_mask)
if not sessions:
print("No active SAP sessions found.")
return
print(f"\nActive SAP sessions ({len(sessions)}):")
for s in sessions:
print(
f" [{s['conn_idx']}:{s['sess_idx']}] "
f"{s['system']}/{s['client']} user={s['user']} tcode={s['tcode']}"
)
return
mode = "SSO" if args.sso else f"password (user={args.user or 'prompt'})"
print(f"\nConnecting to {args.system} / client {_mask(args.client, not args.no_mask)} [{mode}] ...")
try:
sap = connect_to_system(
system=args.system,
client=args.client,
user=args.user,
language=args.language,
sso=args.sso,
)
info = sap.get_session_info()
print(f"\n[OK] Connected: {info['system']} / {_mask(info['client'], not args.no_mask)} / {_mask(info['user'], not args.no_mask)}")
print(f" Transaction : {info['transaction']}")
print(f" Server : {info['response_time']} ms response")
except (ConnectionError, RuntimeError) as exc:
log.error("%s", exc)
sys.exit(1)
if __name__ == "__main__":
main()
5. How to Connect (Execution)
5.1 List active sessions
python sap_connect.py --list 5.2 Connect with SSO
python sap_connect.py --system "Your SAP Logon Entry Name" --client 100 5.3 Connect with username/password
python sap_connect.py --system "Your SAP Logon Entry Name" --client 10
Important: --system must match SAP Logon entry description exactly (case-sensitive in many setups).
6. After Connection: Reporting Flow (SE16N)
A standard reporting script should do this:
- StartTransaction("SE16N")
- Set table (for example VBAK / VBRK)
- Set filters in selection fields (date/material/org/etc.)
- Execute (F8)
- Read ALV grid rows/columns
- Save output to Excel
Example use cases
- Last 6 months sales report
- Open request list (E070)
- Material-level pricing checks
7. Excel Output Best Practices
For business users, generate:
- Sheet 1: summary table (monthly totals/KPIs)
- Sheet 2: raw data extract
- Charts:
- pie chart for distribution
- bar chart for monthly trend
- Metadata row:
- generation timestamp
- source table
- filter range
8. After Connection: Customizing Flow
For customizing automation scripts:
- Start transaction (for example OVX4, OVXI, OVXB)
- Enter Change mode if needed
- Use New Entries / Copy As consistently
- Fill required fields only
- Save
- Handle transport popup:
- assign existing request or create new one
- Read status bar and validate success
9. Transport Handling Pattern
During save, scripts should:
- Detect “Prompt for Customizing Request”
- If request field is empty, set existing request ID
- Confirm popup
- Close follow-up information popup
- Verify status = success (MessageType = S)
10. Validation Strategy
Always validate after script execution:
- Re-open transaction and check entry exists
- Cross-check table data in SE16N
- Capture:
- status type
- status text
- key fields created/updated
- Export validation result if needed
Conclusion
By combining Codex for rapid script generation and SAP GUI Scripting for execution, teams can standardize repetitive SAP work: from reporting to customizing. The key to reliability is a strong connection layer, deterministic popup handling, and strict post-run validation.