Pywinauto automation fails when there is no user session - python

I am working on program automation (program named SEO indexer). I wrote the automation using python's library name pywinauto.
Everything works just great when I am running the automation over RDP connection to the server. But when I am trying to leave the program and disconnect from RDP the "Save AS" windows window is not starting and the program crashes ...
someone knows how can I fix it?
the code that is responsible to save the file is -
def run(self, process_id, link):
controls = self._app[u'TForm1']
text_box = controls.Edit
text_box.set_text(link)
button = controls.ToolBar1
windows = Desktop(backend="uia")
button.click()
self.wait_for_finish(process_id)
result_box = controls.TVirtualStringTree
result_box.RightClick()
sleep(1)
windows_list = windows.windows()
context_menu = windows.window(best_match = "Context")
save_all_button = context_menu.children()[2]
save_all_button.select()
save_as = windows.window(best_match = "save_as")
properties_section = save_as.children()[0]
file_name = "C:\\Windows\\Temp\\indexer_" + str(randint(0, 10000))
file_name_label = properties_section.children()[4].children()[0]
file_name_label.set_text(file_name)
save_button = save_as.children()[2]
save_button.click()
sleep(2)
yes_no_dialog = windows.window(best_match = "GSA SEO Indexer v2.34")
yes_no_dialog.children()[0].click()
return file_name
it crashed on -
save_as = windows.window(best_match = "save_as")
there is a way to force it opening the save as dialog even if there are no screen ?
UPDATE:
I Just notices that the problem is not that the Save as panel is not created, the problem is that when I am without screen and trying to select from the context menu (which is created) - just the text is selected, without clicking on it

The Remote Execution Guide is what you need. This is common problem for any GUI automation tool. So the question is not exactly pywinauto related, but I wrote this guide a year ago to address it for many users.

Related

pywinauto - how to right-click in systemtray, select an item then subitem

I have an application in the hidden part of the systray in Windows 10. I am trying to connect to the application, right-click on it, and then select something like "About". I understand that once I have the target application, I need to connect to the application, which I do in line 25 of the code, but I cannot get further from there.
This is the code I have so far:
from pywinauto import Application
import time
app = Application(backend="uia").connect(path="explorer.exe")
systemTray = app.window(class_name="Shell_TrayWnd")
systemTray.child_window(title="Notification Chevron").click_input(button="left")
#systemTray.print_control_identifiers()
time.sleep(0.25)
list_box = Application(backend="uia").connect(class_name="NotifyIconOverflowWindow")
list_box_win = list_box.window(class_name="NotifyIconOverflowWindow")
list_box_win.wait('visible', timeout=30, retry_interval=3)
# List all the icons in the systray
for notification_area in list_box_win.children():
for app_in_tray in notification_area.children():
print(str(app_in_tray))
target_app = list_box_win.child_window(title="TrayStatus Pro Trial 4.6\r\nCaps Lock: Off")
target_app.wait('visible', timeout=30, retry_interval=3)
target_app.click_input(button="right")
target_app.target_app.print_control_identifiers()
target_app.dump_tree()
sysapp = Application().connect(path='TrayStatus.exe')
sysapp.menu_select('About') #This part fails
Application() class represents the application and sometime it fails to identify the window. Having said that, you have not mentioned the backend of the application at the beginning you have used UIA backend so figure out for the line sysapp = Application().connect(path='TrayStatus.exe') as well and add, Also adding timeout=10 parameter to the connect() works many times.
Still if the above option doesn't not work for you then try using Desktop class.
again you can mention backend of your choice and compatibility, there is no such way to identify backend of application.
window2 = Desktop(backend="win32").window(title='title of the rayStatus.exe window')
you will need to import -
from pywinauto import Desktop.
Finally you can print the titile of windows using
list_window = Desktop().windows()
for window in list_window:
window.window_texts() # this should print the all open window names on desktop

Excel button click event in Python

I was using pywin32 to make a connection between Python and Excel and I was starting to deal with events. It happens that in the project I'm working with, I would need to capture a button click event in Python. I've seen events from Workbooks and Worksheets, but I can't figure out the click ones.
class WorkbookEvents:
def OnSheetSelectionChange(self, *args):
#print(args)
print("You changed the selection")
#print(args[1].Address)
#args[0].Range("A1").Value = "Range :" + str(args[1].Address)
workbook_events= WIN32.WithEvents(wb, WorkbookEvents)
quite late for an answer but I got it working with this piece of code (and using the ActiveX "Command Button" control in Excel)
import win32com.client as win32
import pythoncom
import sys
import time
#The event handlers container
class wsEvents:
def OnClick(self,*args):
print('Button Clicked')
xlApp = win32.Dispatch("Excel.Application")
xlwb = xlApp.Workbooks("Book1.xlsm")
ws=xlwb.Sheets("Sheet1")
xl_events=win32.WithEvents(ws.OLEObjects("CommandButton1").Object,wsEvents)
# define initalizer
keepOpen = True
while keepOpen:
time.sleep(0.1)
# display the message
pythoncom.PumpWaitingMessages()
try:
# if the workbook count does not equal zero we can assume Excel is open
if xlApp.Workbooks.Count != 0:
keepOpen = True
# otherwise close the application and exit the script
else:
keepOpen = False
xlApp = None
sys.exit()
except:
# if there is an error close excel and exit the script
keepOpen = False
xl = None
The ActiveX command button is loaded in Excel as an "independent" COM(OLE) object that is referenced by the worksheet where it is displayed. The Excel API permit to get a reference to OLE objects as explained here and easily done in python code.
Basically the event handlers are not managed by the worksheet but by the Command Button object instead, and of course the events supported by a Command Button are OnClick etc...Turns out that the solution is elegant and that win32com has much respect for COM :)
Credits must be given to Alex Reed blog about his snippet to keep python script waiting for new events.

Trying to ensure that correct window is active right now

I'm trying to assert that the currently needed browser window is opened via selenium.
My approach is to compare titles of the windows to each other, and if title doesn't match - switch to the next window and repeat procedure. But right now check of the last window (which is correct) doesn't happen.
Method for collecting all opened browser windows:
def collect_windows(self):
windows = []
try:
for handle in self.driver.window_handles:
windows.append(handle)
return windows
except:
self.log.error(format_exc())
Method that runs through the list and checks titles of the windows:
def switch_window(self, window_title=''):
windows_list = self.collect_windows()
try:
for window in windows_list:
title = self.driver.title
if window_title not in title:
self.driver.switch_to.window(window)
self.log.info(f"Switched to window: {window_title}")
except:
self.log.error(format_exc())
Instead of comparing titles compare the window handles
def get_current_window_handle(self):
return self.driver.current_window_handle
def switch_window(self, current_handle):
for handle in self.driver.window_handles:
if handle != current_handle:
self.driver.switch_to.window(handle)
self.log.info(f"Switched to window: {self.driver.title}")
return
current_window_handle = get_current_window_handle()
# open new window
switch_window(current_window_handle)

A trouble with automation of Windows RDP through console

A brief description of my problem:
1.
My Jenkins job is required to establish an RDP connection to another machine to perform some activities.
2.
Until recently, the default password was maintained between sessions. But now some settings have changed, and the password needs to be reentered by hand each time I creating a new RDP session.
I prepared a short python script interacting with the Windows gui via the win32gui package.
I built a stand alone executable file from this script using the pyinstaller.
And finally I added a call to this executable file directly to the job.
Somethig like that:
while attempts:
security_window_title = "Windows Security"
try:
hwnd_credentials = win32gui.FindWindow(0, security_window_title)
window_controls = []
win32gui.EnumChildWindows(hwnd_credentials, collect_window_control, None)
focus_on_window(hwnd_credentials)
sleep(0.5)
prev_user_login = window_controls[2]["hwnd"]
x = int(window_controls[1]["x"] + 80)
y = int(window_controls[1]["y"] + 20)
click(x, y)
type_message(password)
ok_button = window_controls[6]["hwnd"]
push_button(ok_button)
except win32gui.error:
sleep(1)
attempts -= 1
if not attempts:
raise RuntimeError("Can't interact with window: {}.".format(security_window_title))
else:
break
while attempts:
sleep(timeout)
attempts -= 1
if check_connection_started():
break
if check_certificate_errors():
for control in window_controls[::-1]:
if control["text"] == "&Yes":
push_button(control["hwnd"])
if not attempts:
raise RuntimeError("Connection not established.")
3.
This would not be a problem when script running from the job working with the fully functional Windows ui. I can find a window in which my script is supposed to specify a password using the win32gui python package. I can generate all the appropriate keyboard events to enter a password.
Using RDP via console provides me a very strange set of windows-like objects which I can not interact with using the win32gui python package the same way as with ordinary windows. For example, I do locate a window with non zero hwnd and with text property equal to "Remote Desktop Connection". But I can't focus on such a window using the basic method win32gui.SetForegroundWindow(hwnd). This leads to an unnamed win32gui exception.
Is there any possibility to transfer the password to the desired control of the desired window-like structure, so that the job does not interrupt its execution?
Thank you so much for any help.
I can focus on both "Remote Desktop Connection" and "Windows Security" with win32gui.SetForegroundWindow(hwnd).
Sample code:
import win32api
import win32gui
import win32con
import time
from pynput.keyboard import Key, Controller
def main():
Remote = "Remote Desktop Connection"
Security = "Windows Security"
try:
hwnd_Remote = win32gui.FindWindow(0, Remote)
print(hwnd_Remote)
win32gui.ShowWindow(hwnd_Remote,win32con.SW_SHOWNORMAL)
win32gui.SetForegroundWindow(hwnd_Remote)
keyboard = Controller()
keyboard.type('ipaddress')
keyboard.press(Key.enter)
keyboard.release(Key.enter)
time.sleep(3)
hwnd_Security = win32gui.FindWindow(0, Security)
print(hwnd_Security)
win32gui.ShowWindow(hwnd_Security,win32con.SW_SHOWNORMAL)
win32gui.SetForegroundWindow(hwnd_Security)
keyboard.type('password')
keyboard.press(Key.enter)
keyboard.release(Key.enter)
except win32gui.error:
raise RuntimeError("Can't interact with window: {}.".format(Remote))
if __name__ == "__main__":
main()
Make sure that the foreground process did not disable calls to the SetForegroundWindow function. Add the LockSetForegroundWindow(LSFW_UNLOCK) or AllowSetForegroundWindow(ASFW_ANY) to enable the call of SetForegroundWindow.

Listen onclick event in PyObjC

I try to display the current windows for each click on the system.
I do this code :
from AppKit import NSWorkspace
def getwindows():
activeAppName = NSWorkspace.sharedWorkspace().activeApplication()['NSApplicationName']
print activeAppName
return
def main():
getwindows()
main()
But only the current windows when i setup the script is displayed.
How can i bind this script in a loop with a click event ?
I already try to use Turtle but some errors appended.
Note that the activeApplication method of NSWorkSpace is deprecated. The following can be used to actively probe the running applications for their active status:
import AppKit
import time
rl = AppKit.NSRunLoop.currentRunLoop()
ws = AppKit.NSWorkspace.sharedWorkspace()
for i in xrange(10):
for app in ws.runningApplications():
if app.isActive():
print "active app:", app.localizedName()
date = AppKit.NSDate.date()
time.sleep(1)
rl.acceptInputForMode_beforeDate_( AppKit.NSDefaultRunLoopMode, date )
Active means it is the receiving keyboard input. Clicking on an application will cause it to become active. Note that the acceptInputForMode method must be called so that property changes are reflected in the current app. Run this program then click on various other applications -- you should see the active app change.
A kind of binding can be done through observers:
import AppKit
ws = AppKit.NSWorkspace.sharedWorkspace()
appL = ws.runningApplications()
class MyClass( AppKit.NSObject ):
def observeValueForKeyPath_ofObject_change_context_(self,
kpath, objid, change, context ):
print "path change", kpath, change['new'], appL[context].localizedName()
obj = MyClass.new()
for i in xrange(len(appL)):
appL[i].addObserver_forKeyPath_options_context_( obj,
"isActive", AppKit.NSKeyValueObservingOptionNew, i )
date = AppKit.NSDate.date().dateByAddingTimeInterval_( 10 )
rl = AppKit.NSRunLoop.currentRunLoop()
rl.acceptInputForMode_beforeDate_( AppKit.NSDefaultRunLoopMode, date )
for app in appL:
app.removeObserver_forKeyPath_( obj, "isActive" )
Run this program same as the last.
There are a few other properties of NSRunningApplication that you could probe/observe (such as hidden) but the list is quite short.

Categories