Python RPAに触れてみて
先日Pythonを使ってのRPA開発に少しですが参加する機会がありました。
そのときの開発はRDA(デスクトップ操作のオートメーション化)だったので
やるべきことが具体的にイメージできて割と簡単に動くものを作成することができました。
簡単にとは書きましたがPythonでのRPA作成にもいろいろ考慮しないといけないことがあり、
例えば
- 人間操作なら不意のアラート画面表示にも対応できますが
事前に想定した手順しかコーディングされていないRPAでは対応ができない。 - 「次に表示される画面を待つ。」場合、CPU負荷やネットワーク状況により
想定外に時間がかかった時の対応も備えておかないといけない
など、現状の調査&操作検証&RPA動作検証を何度も行い
ハプニングの洗い出し&対応処理追加という地味な作業の積み重ねが必要です。
せっかくなので何か作ってみることに
普段自分がRDP(リモートデスクトップ)接続するマシンの情報を
一か所にまとめて管理しその情報を元に指定したRDP接続を起動させる。
これによりデスクトップ上に置いていた複数のRDP定義ファイルが不要となり
デスクトップが広くなる。また選択インターフェースに備考を表示することにより
接続先が自分にとって何用なのか判別しやすくなる。
環境
windows11にMicrosoftStoreからpythonのインストール
コマンドプロンプトからpipコマンドで Pillow と opencv-python をインストール
※環境構築の詳細な手順は今回記事では触れておりません。
処理概要
1.起動時に当PGM用パスワード入力をチェック【コマンドライン入力と読み取り】
離席中に勝手に起動されないよう念のため。
2.複数のRDP接続情報(タイトル、接続先IP,アカウント、パスワード)をPGM内部に定義
3.選択方式インターフェースを作成【tkを利用してUIの生成】
前述RDP接続情報をプルダウン化
4.決定ボタンでRDP接続を開始
RDP用パラメータファイルを動的に生成【一時ファイルの生成】
警告画面が出る場合は 左キーを送り 「継続」を選択させ【画像認識とキー入力操作】
RDP情報にパスワード定義がある場合には資格情報画面にパスワードを自動入力させ接続【クリップボードから貼り付け】
稚拙なコードですが、最後に付けておきます。
バッチファイルを用意すればワンクリックで起動させることも可能です。
コードを見る
# -*- coding: utf-8 -*- # /************************ # メイン処理 ################################# # import ################################# import os import sys import time import tempfile import subprocess import pyautogui import pyperclip from PIL import Image import tkinter as tk from tkinter import * import tkinter.ttk as ttk #初期処理 rdpFile = '' rdpProc = None myAppImg = '' selectedIdx = None configArr = [] while True: print( "パスワードを入力してください。" ) password = input("パスワード:") if password == "password": break def getConfigs(): configs = [] configs.append( { 'title':'サンプル1番', 'ip':'192.168.254.11', 'user':'User1', 'passw':'Password1' } ) configs.append( { 'title':'サンプル2番', 'ip':'192.168.254.12', 'user':'User2', 'passw':'' } ) configs.append( { 'title':'サンプル3番', 'ip':'192.168.254.13', 'user':'User3', 'passw':'Password3' } ) configs.append( { 'title':'サンプル4番', 'ip':'192.168.254.14', 'user':'User4', 'passw':'password4' } ) return configs # 画像認識によるアプローチ def searchScreen( image_path, loop=1, confidenceValue=0.9 ): # 画面上で画像を検索(少し時間がかかる場合があります) while ( loop ): loop = loop - 1 try: location = pyautogui.locateCenterOnScreen( image_path, confidence=confidenceValue ) if location: return location else: time.sleep( 1 ) #return False except Exception as e: #print("Image not found on the screen." ) print( e ) time.sleep( 1 ) print("Image not found on the screen." ) print( image_path ) return False # 一時的なRDPファイルを作成 def createFileRDP( hostname, username, resolution=(1920, 1080) ): with tempfile.NamedTemporaryFile( delete=False, suffix='.rdp') as temp_rdp: # RDPファイルの内容 rdp_content = f""" screen mode id:i:1 use multimon:i:0 desktopwidth:i:{resolution[0]} desktopheight:i:{resolution[1]} session bpp:i:32 winposstr:s:0,3,633,191,1433,791 compression:i:1 keyboardhook:i:2 audiocapturemode:i:0 videoplaybackmode:i:1 connection type:i:7 networkautodetect:i:1 bandwidthautodetect:i:1 displayconnectionbar:i:1 enableworkspacereconnect:i:0 disable wallpaper:i:0 allow font smoothing:i:0 allow desktop composition:i:0 disable full window drag:i:1 disable menu anims:i:1 disable themes:i:0 disable cursor setting:i:0 bitmapcachepersistenable:i:1 full address:s:{hostname} audiomode:i:0 redirectprinters:i:1 redirectcomports:i:0 redirectsmartcards:i:1 redirectwebauthn:i:1 redirectclipboard:i:1 redirectposdevices:i:0 autoreconnection enabled:i:1 authentication level:i:2 prompt for credentials:i:1 negotiate security layer:i:1 remoteapplicationmode:i:0 alternate shell:s: shell working directory:s: gatewayhostname:s: gatewayusagemethod:i:4 gatewaycredentialssource:i:4 gatewayprofileusagemethod:i:0 promptcredentialonce:i:0 gatewaybrokeringtype:i:0 use redirection server name:i:0 rdgiskdcproxy:i:0 kdcproxyname:s: enablerdsaadauth:i:0 username:s:{username} """ # 一時ファイルに内容を書き込む temp_rdp.write(rdp_content.encode('utf-8')) temp_rdp.flush() tempName = temp_rdp.name temp_rdp.close() return tempName def disposeFileRDP( tempName ): # 一時ファイルを削除 if os.path.isfile( tempName ): os.unlink( tempName ) return True def connectRDP( rdpFile, rdp_pass ): imgPath = "\imgフォルダへのパス\" # RDP サブプロセスとして起動 proc = subprocess.Popen(['mstsc', rdpFile]) time.sleep( 2 ) loop = 10 while loop: loop -= 1 #RDPセキュリティ確認画面 あるときないときある location = searchScreen( imgPath + 'rdp00.png', 5 ) if ( location != False ): pyautogui.click(location) pyautogui.press( "left" ) time.sleep( 0.8 ) pyautogui.press( 'enter', presses=1 ) time.sleep( 1 ) if ( len( rdp_pass ) == 0 ): # パスワード情報無しならここで終了 return proc #資格情報画面マッチ location = searchScreen( imgPath + 'rdp01.png', 10 ) #資格情報画面 本チェック if ( location != False ): #パスワード入力 pyperclip.copy( rdp_pass ) pyautogui.hotkey( 'ctrl', 'v' ) time.sleep( 0.5 ) pyautogui.press( 'enter', presses=1 ) time.sleep( 1 ) location = searchScreen( imgPath + 'rdp02.png', 10 ) #RDPの警告確認画面 pyautogui.click(location) pyautogui.press( "left" ) time.sleep( 0.8 ) pyautogui.press( 'enter', presses=1 ) time.sleep( 1 ) return proc print( "RDP 失敗したみたい" ) return None def disconnectRDP( proc ): # RDP プロセス終了 if proc != None: proc.terminate() return None def doConnect( configs ): global rdpFile, rdpProc rdp_ip = configs[ 'ip' ] rdp_ps = configs[ 'passw' ] rdp_us = configs[ 'user' ] try: #RDP 接続 rdpFile = createFileRDP( rdp_ip, rdp_us ) print( 'rdpFile = '+ rdpFile ) rdpProc = connectRDP( rdpFile, rdp_ps ) disposeFileRDP( rdpFile ) if ( rdpProc == None ): print( "RDP接続に失敗しました。" ) return None return rdpProc except Exception as e: print( "RDP 接続 異常終了。" ) return None def combo_selected(event): global combo, selectedIdx selStr = combo.get() selArr = selStr.split( ' : ' ) selectedIdx = int( selArr[0] ) def button_click(): global selectedIdx, configArr configOne = configArr[ selectedIdx ] doConnect( configOne ) def makeOptions(): global configArr options = [] for sett in configArr: strSel = str( len( options ) ) + ' : ' + sett['title'] + ' (' + sett['ip'] + ')' options.append( strSel ) return options def main( option ): global combo label1=tk.Label(root,text="RDP選択") label1.pack() variable = tk.StringVar() combo=ttk.Combobox( root, values=option, textvariable=variable, width=30 ) combo.bind( "<<ComboboxSelected>>", combo_selected ) combo.pack() button1 = tk.Button(root, text="接続", command=button_click) button1.pack(side = tk.BOTTOM) if __name__ == "__main__": configArr = getConfigs() root = tk.Tk() root.geometry("300x100") root.title("RDPまとめ") optionLists = makeOptions() combo=str() main(optionLists) root.mainloop()
なお今回は簡易的紹介なので各パスワードがPGM内部に平文で定義されているなど
セキュリティ上問題もありますと言い訳させていただきます。
最後に
ネットワークの担当者なら非常に多くのRDP接続を利用しているだろうと
どのように管理しているか確認したところ綺麗にフォルダ分けしたり、
人によっては視覚的に表示される便利なツールを使っていました。
まったく出る幕ございませんでした。