Technology Blog Posts by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 

 

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

  1. SAP GUI for Windows must be installed
  2. SAP Logon entry must exist and be tested manually
  3. SAP GUI Scripting must be enabled:
    • SAP GUI → Options → Accessibility & Scripting → Scripting
    • Enable scripting (client-side)
  4. Server-side scripting parameter must allow scripting:
    • sapgui/user_scripting = TRUE (Basis side)
  5. Your user needs authorization for:
    • target transactions (SE16N, OVX4, OVX5, etc.)
    • customizing save + transport assignment (if doing config)

2.2 Local environment prerequisites

  1. Python 3.10+ installed
  2. Install required packages:
    python -m pip install pywin32 openpyxl pypdf
  3. Keep SAP Logon open before script execution (recommended)
  4. 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:

  1. Attach to running SAP GUI:
    • GetObject("SAPGUI")
  2. Start SAP Logon if not running
  3. Open connection by exact SAP Logon entry name
  4. Wait until session window is ready
  5. Detect login screen vs already authenticated menu
  6. Handle post-login popups (information/multiple logon)
  7. Verify status bar for login errors
  8. 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:

  1. StartTransaction("SE16N")
  2. Set table (for example VBAK / VBRK)
  3. Set filters in selection fields (date/material/org/etc.)
  4. Execute (F8)
  5. Read ALV grid rows/columns
  6. 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:

  1. Start transaction (for example OVX4, OVXI, OVXB)
  2. Enter Change mode if needed
  3. Use New Entries / Copy As consistently
  4. Fill required fields only
  5. Save
  6. Handle transport popup:
    • assign existing request or create new one
  7. Read status bar and validate success

9. Transport Handling Pattern

During save, scripts should:

  1. Detect “Prompt for Customizing Request”
  2. If request field is empty, set existing request ID
  3. Confirm popup
  4. Close follow-up information popup
  5. 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.

2 Comments
Labels in this area