Opening SAP via python - python

I have sucessfully managed to open SAP, but I am struggling to make python click on the logon button that appears when the program is opened. How could I achieve that?
import subprocess
subprocess.run(r'C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe')
# I would like python to press the logon button in the window that has just been opened.

This is what I use in python
import subprocess
subprocess.check_call(['C:\\Program Files (x86)\\SAP\\FrontEnd\\SAPgui\\sapshcut.exe', '-system=PE1', '-client=500', '-user=user', '-pw=password', '-language=EN'])

In my opinion, python is not exactly the best language when it comes to automate windows in "makro-style" maybe you could use something lilke AutoHotkey (AHK) for that.
For completion's sake:
There seem to be python modules like PyAutoGUI which could help you achieve what you want.
Or you could import win32api and try to control windows that way.
If you really try to automate SAP with python you should try to get information about an official API of SAP which you could call.
Maybe this will help: https://blogs.sap.com/2020/06/09/connecting-python-with-sap-step-by-step-guide/

You can use win32com to connect to the SAPGUI.
Find the "Script Recording and Playback" in your SAP menu to record your SAP transactions in a script. Find that script you can see all your transaction in code.
Use the SAPGUI with Python example to create a session on an opened SAP. Replace the SAP script in that example with your recorded script

Try this:
import win32com.client
import subprocess
import sys
def saplogin():
try:
path = r"C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
sap = subprocess.Popen(path)
time.sleep(10)
SapGuiAuto = win32com.client.GetObject('SAPGUI')
if not type(SapGuiAuto) == win32com.client.CDispatch:
return
application = SapGuiAuto.GetScriptingEngine
if not type(application) == win32com.client.CDispatch:
SapGuiAuto = None
return
connection = application.OpenConnection("PCL", True)
if not type(connection) == win32com.client.CDispatch:
application = None
SapGuiAuto = None
return
session = connection.Children(0)
if not type(session) == win32com.client.CDispatch:
connection = None
application = None
SapGuiAuto = None
return
session.findById("wnd[0]/usr/txtRSYST-BNAME").text = "username"
session.findById("wnd[0]/usr/pwdRSYST-BCODE").text = "password"
session.findById("wnd[0]").sendVKey(0)
print(sys.exc_info())
finally:
session = None
connection = None
application = None
SapGuiAuto = None
saplogin()

Related

pythoncom.CoInitialize() is not called and the program terminates

I´m working on a Python program supposed to read incoming MS-Word documents in a client/server fashion, i.e. the client sends a request (one or multiple MS-Word documents) and the server reads specific content from those requests using pythoncom and win32com.
Because I want to minimize waiting time for the client (client needs a status message from server, I do not want to open an MS-Word instance for every request. Hence, I intend to have a pool of running MS-Word instances from which the server can pick and choose. This, in turn, means I have to reuse those instances from the pool in different threads and this is what causes trouble right now.
After I fixed the following error I asked previously on stack overflow, my code looks now like this:
import pythoncom, win32com.client, threading, psutil, os, queue, time, datetime
class WordInstance:
def __init__(self,app):
self.app = app
self.flag = True
appPool = {'WINWORD.EXE': queue.Queue()}
def initAppPool():
global appPool
wordApp = win32com.client.DispatchEx('Word.Application')
appPool["WINWORD.EXE"].put(wordApp) # For testing purpose I only use one MS-Word instance currently
def run_in_thread(instance,appid, path):
print(f"[{datetime.now()}] open doc ... {threading.current_thread().name}")
pythoncom.CoInitialize()
wordApp = win32com.client.Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(appid, pythoncom.IID_IDispatch))
doc = wordApp.Documents.Open(path)
doc.SaveAs(rf'{path}.FB.pdf', FileFormat=17)
doc.Close()
print(f"[{datetime.now()}] close doc ... {threading.current_thread().name}")
instance.flag = True
if __name__ == '__main__':
initAppPool()
pathOfFile2BeRead1 = r'C:\Temp\file4.docx'
pathOfFile2BeRead2 = r'C:\Temp\file5.docx'
#treat first request
wordApp = appPool["WINWORD.EXE"].get(True, 10)
wordApp.flag = False
pythoncom.CoInitialize()
wordApp_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, wordApp.app)
readDocjob1 = threading.Thread(target=run_in_thread,args=(wordApp,wordApp_id,pathOfFile2BeRead1), daemon=True)
readDocjob1.start()
appPool["WINWORD.EXE"].put(wordApp)
#wait here until readDocjob1 is done
wait = True
while wait:
try:
wordApp = appPool["WINWORD.EXE"].get(True, 1)
if wordApp.flag:
print(f"[{datetime.now()}] ok appPool extracted")
wait = False
else:
appPool["WINWORD.EXE"].put(wordApp)
except queue.Empty:
print(f"[{datetime.datetime.now()}] error: appPool empty")
except BaseException as err:
print(f"[{datetime.datetime.now()}] error: {err}")
wordApp.flag = False
openDocjob2 = threading.Thread(target=run_in_thread,args=(wordApp,wordApp_id,pathOfFile2BeRead2), daemon=True)
openDocjob2.start()
When I run the script I receive the following output printed on the terminal:
[2022-03-29 11:41:08.217678] open doc ... Thread-1
[2022-03-29 11:41:10.085999] close doc ... Thread-1
[2022-03-29 11:41:10.085999] ok appPool extracted
[2022-03-29 11:41:10.085999] open doc ... Thread-2
Process finished with exit code 0
And only the first word file is converted to a pdf. It seems like def run_in_thread terminates after the print statement and before/during pythoncom.CoInitialize(). Sadly I do not receive any error message which makes it quite hard to understand the cause of this behavior.
After reading into Microsofts documentation I tried using
pythoncom.CoInitializeEx(pythoncom.APARTMENTTHREADED) instead of pythoncom.CoInitialize(). Since my COM object needs to be called by multiple threads. However this changed nothing.

SAP GUI script sometimes randomly crashes (object not found by ID)

So, I have two SAP GUI scripts, both of them are running a simple report in SE16, exporting it to Excel and that's all. My Python script heavily relies on this data, yet, half of the time when I go back to the computer the VBS script crashed.
Nothing changes between the attempts, sometimes it works, sometimes it doesn't, with the same data...
The GUI script itself was done with the help of the record function, after looking at the resulting code I realized that I have absolutely no desire of ever learning this language (how do you even figure out the objectID of each button, vkeys are weird, etc).
Is there a simple way to make this (and the same with EKPO) crash-proof? I guess I'd need to show which object is not found, but ever since I started to write this post, no crashes happened, it only hanged without an error message...
This is the Python part of the code, it runs the VBS script. Based on Googling, it is possible to include the VBS code directly in Python, based on the VBS below, how bothersome would it be and would it (crystal ball time) solve my random crashes?
def create_ausp(local_raw):
user = os.getlogin()
file = rf"C:\Users\{user}\Desktop\Reporting\AUSP.XLSX"
local_raw["Vendor"].drop_duplicates().to_clipboard(index=False)
if os.path.exists(file):
os.remove(file)
if not os.path.exists(file):
os.startfile(rf"C:\Users\{user}\AppData\Roaming\SAP\SAP GUI\Scripts\AUSP.vbs")
while not os.path.exists(file):
time.sleep(1)
if os.path.isfile(file):
ausp_headers = ["Vendor", "SAP default material group"]
df = pd.read_excel("AUSP.xlsx")
# df.drop(columns=["Internal char no."], inplace=True) #sometimes comes up in SAP, uncomment if value error happens
df.columns = ausp_headers
os.startfile(rf"C:\Users\{user}\Desktop\Reporting\AUSP close.ahk")
return df
VBS script:
If Not IsObject(application) Then
Set SapGuiAuto = GetObject("SAPGUI")
Set application = SapGuiAuto.GetScriptingEngine
End If
If Not IsObject(connection) Then
Set connection = application.Children(0)
End If
If Not IsObject(session) Then
Set session = connection.Children(0)
End If
If IsObject(WScript) Then
WScript.ConnectObject session, "on"
WScript.ConnectObject application, "on"
End If
session.findById("wnd[0]").maximize
session.findById("wnd[0]/tbar[0]/okcd").text = "se16n"
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[1]").sendVKey 0
session.findById("wnd[0]/usr/ctxtGD-TAB").text = "ausp"
session.findById("wnd[0]/usr/ctxtGD-TAB").caretPosition = 4
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/btnPUSH[4,1]").setFocus
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/btnPUSH[4,1]").press
session.findById("wnd[1]").sendVKey 24
session.findById("wnd[1]").sendVKey 8
session.findById("wnd[0]/tbar[1]/btn[18]").press
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/chkGS_SELFIELDS-MARK[5,1]").selected = true
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/chkGS_SELFIELDS-MARK[5,7]").selected = true
session.findById("wnd[0]/usr/txtGD-MAX_LINES").text = ""
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/ctxtGS_SELFIELDS-LOW[2,2]").text = "DEFAULT_MATERIAL_GROUP"
session.findById("wnd[0]/usr/txtGD-MAX_LINES").setFocus
session.findById("wnd[0]/usr/txtGD-MAX_LINES").caretPosition = 0
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]").sendVKey 8
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").pressToolbarContextButton "&MB_VARIANT"
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").pressToolbarContextButton "&MB_EXPORT"
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").selectContextMenuItem "&XXL"
session.findById("wnd[1]/usr/ctxtDY_PATH").text = "C:\Users\...\Desktop\Reporting"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "AUSP.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 9
session.findById("wnd[1]").sendVKey 11
session.findById("wnd[0]").sendVKey 3
session.findById("wnd[0]").sendVKey 3
I've done something very similar to your code.
I had a python script that first opened SAP and logged in, and then ran a VBS script to download two excel files, and then run another python script to process the data.
Only recently have I managed to merge it all into one python script. Although it's still VBS code, only embedded in python through the win32com module.
I haven't found any better way to create the SAP code than to record it in the SAP gui, just as you did. I did find it quite easy to do and merge it into python though.
Perhaps it can help you with your needs.
import psutil
import subprocess
import time
import win32com.client
from win32com import client
from win32com.client import Dispatch
if "saplogon.exe" in (i.name() for i in psutil.process_iter()):
print("SAP is running")
else:
print("SAP is not running. Starting...")
subprocess.check_call(['C:\\Program Files (x86)\\SAP\\FrontEnd\\SAPgui\\sapshcut.exe', '-system=PE1', '-client=500', f'-user={sapuser}', f'-pw={password}', '-language=EN'])
time.sleep(20)
SapGuiAuto = win32com.client.GetObject("SAPGUI")
if not type(SapGuiAuto) == win32com.client.CDispatch:
exit
application = SapGuiAuto.GetScriptingEngine
if not type(application) == win32com.client.CDispatch:
SapGuiAuto = None
exit
connection = application.Children(0)
if not type(connection) == win32com.client.CDispatch:
application = None
SapGuiAuto = None
exit
session = connection.Children(0)
if not type(session) == win32com.client.CDispatch:
connection = None
application = None
SapGuiAuto = None
exit
print("Downloading excel files from SAP")
session.findById("wnd[0]").maximize
session.findById("wnd[0]/tbar[0]/okcd").text = "/ncoois"
session.findById("wnd[0]").sendVKey(0)
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_TOPBLOCK:PPIO_ENTRY:1100/cmbPPIO_ENTRY_SC1100-PPIO_LISTTYP").key = "PPIOO000"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E1").selected = True
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E2").selected = True
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtS_WERKS-LOW").text = "ika1"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtP_SYST1").text = "TECO"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtP_SYST2").text = "CNF"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E2").setFocus()
session.findById("wnd[0]").sendVKey(8)
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").setCurrentCell(-1,"")
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectAll()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").contextMenu()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectContextMenuItem("&XXL")
session.findById("wnd[1]/tbar[0]/btn[0]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").setFocus()
session.findById("wnd[1]/usr/ctxtDY_PATH").caretPosition = 0
session.findById("wnd[1]").sendVKey(4)
session.findById("wnd[2]/tbar[0]/btn[12]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").text = f"C:\\users\\{user}\\Documents"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "operations.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 15
session.findById("wnd[1]/tbar[0]/btn[11]").press()
session.findById("wnd[0]/tbar[0]/btn[15]").press()
session.findById("wnd[0]").resizeWorkingPane(267,40,False)
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_TOPBLOCK:PPIO_ENTRY:1100/cmbPPIO_ENTRY_SC1100-PPIO_LISTTYP").key = "PPIOH000"
session.findById("wnd[0]/tbar[1]/btn[8]").press()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").setCurrentCell(-1,"")
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectAll()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").contextMenu()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectContextMenuItem("&XXL")
session.findById("wnd[1]/tbar[0]/btn[0]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").text = f"C:\\users\\{user}\\Documents"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "header.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 12
session.findById("wnd[1]/tbar[0]/btn[11]").press()

COM Error while trying to perform action on SAP application

We are using Python pywin32 com library for scripting SAP GUI application running on Windows.
Things were working until yesterday.
Now, while trying to access the line of code below which performs the maximize(), we are getting
com_error: (-2147417851, 'The server threw an exception.', None, None)
And getting the following error while trying to access any object in the SAP window (the last line in code).
AttributeError: Property '.text' cannot be set.
Can someone help? Let me know if more information is needed.
Below is the code snippet which we use to get a new scripting session, launch SAP and perform actions:
from subprocess import call
import win32com.client
import time
GUIPath = 'C:/Program Files (x86)/SAP/FrontEnd/SAPgui/'
WinTitle = 'SAP'
SID = 'xxxxxx.sap.xxxxx.com'
InstanceNo = 'xx'
shell = win32com.client.Dispatch("WScript.Shell")
cmdString = os.path.join(GUIPath, 'SAPgui.exe') + " " + SID + " " + InstanceNo
call(cmdString)
while not shell.AppActivate(WinTitle):
time.sleep(1)
checkGUIExists = False
while not checkGUIExists:
try:
SAP = win32com.client.GetObject("SAPGUI").GetScriptingEngine
session = SAP.FindById("/app/con[0]/ses[0]") # session
checkGUIExists = True
except:
time.sleep(1)
continue
//The lines failing//
session.findById("wnd[0]").maximize()
session.findById("wnd[0]/tbar[0]/okcd).text = <transaction>

How can I listen to Windows 10 notifications in Python?

My Python test script causes our product to raise Windows notifications ("Toasts"). How can my python script verify that the notifications are indeed raised?
I see it's possible to make a notification listener in C# using Windows.UI.Notifications.Management.UserNotificationListener (ref), And I see I can make my own notifications in Python using win10toast - but how do I listen to othe apps' notifications?
You can use pywinrt to access the bindings in python.
A basic example would look something like this:
from winrt.windows.ui.notifications.management import UserNotificationListener, UserNotificationListenerAccessStatus
from winrt.windows.ui.notifications import NotificationKinds, KnownNotificationBindings
if not ApiInformation.is_type_present("Windows.UI.Notifications.Management.UserNotificationListener"):
print("UserNotificationListener is not supported on this device.")
exit()
listener = UserNotificationListener.get_current()
accessStatus = await listener.request_access_async()
if accessStatus != UserNotificationListenerAccessStatus.ALLOWED:
print("Access to UserNotificationListener is not allowed.")
exit()
def handler(listener, event):
notification = listener.get_notification(event.user_notification_id)
# get some app info if available
if hasattr(notification, "app_info"):
print("App Name: ", notification.app_info.display_info.display_name)
listener.add_notification_changed(handler)
Searching python windows notification listener on google brings up only this ok-ish result but it is not complete.
Since i couldn't find any self contained example on how to do it, here is a fully working code:
from winrt.windows.ui.notifications.management import UserNotificationListener
from winrt.windows.ui.notifications import KnownNotificationBindings
def handler(asd, aasd):
unotification = asd.get_notification(aasd.user_notification_id)
# print(dir(unotification))
if hasattr(unotification, "app_info"):
print("App Name: ", unotification.app_info.display_info.display_name)
text_sequence = unotification.notification.visual.get_binding(KnownNotificationBindings.get_toast_generic()).get_text_elements()
it = iter(text_sequence)
print("Notification title: ", it.current.text)
while True:
next(it, None)
if it.has_current:
print(it.current.text)
else:
break
else:
pass
listener = UserNotificationListener.get_current()
listener.add_notification_changed(handler)
while True: pass
tested on windows 10 and winrt v1.0.21033.1

How to perform a context menu action on a file using python 3

How can i do a context menu action on a particular file?
I managed to open the explorer and get the list of files through python using pywinauto.
On that file I need to perform a context menu action, is it possible through pywinauto?
import pywinauto
path = "C:\\Users\\Vishnu\\Desktop\\DM-test\\"
pywinauto.Application().Start(r'explorer.exe')
explorer = pywinauto.Application().Connect(path='explorer.exe')
NewWindow = explorer.Window_(top_level_only=True, active_only=True, class_name='CabinetWClass')
NewWindow.AddressBandRoot.ClickInput()
NewWindow.TypeKeys(path+'{ENTER}', with_spaces=True, set_foreground=False)
The code above will open the explorer and navigate to the dir. This is the Context menu action required on the file:
I managed to find the reg value and changed my code to pass that action to the file, It works perfect!!
pywinauto.Application().start(r'"C:\Program Files (x86)\Qualcomm\QCAT 6.x\Bin\QCAT.exe" -txt "{}"'.format(fileName))
Arrgh! Nobody reads the docs... The example is provided in the main Readme: MS UI Automation Example. For your case it should look like that:
# no need to type the path, explorer.exe has a cmd param for that
pywinauto.Application().start(r'explorer.exe "{}"'.format(path))
# backend is important!!!
app = Application(backend="uia").connect(path="explorer.exe")
NewWindow = explorer.Window_(top_level_only=True, active_only=True, class_name='CabinetWClass')
file_item = NewWindow.ItemsView.get_item('dmlog20180517-121505slot0.dlf')
file_item.right_click_input()
app.ContextMenu["Convert to QCAT Text"].invoke()
# further actions depend on a process / dialog started...
More details about backends: Getting Started Guide.

Categories

Resources