ブレーンアシストオンライン

デスクトップ上に増殖したRDPショートカットを整理してみた

Python RPAに触れてみて

先日Pythonを使ってのRPA開発に少しですが参加する機会がありました。
そのときの開発はRDA(デスクトップ操作のオートメーション化)だったので
やるべきことが具体的にイメージできて割と簡単に動くものを作成することができました。

簡単にとは書きましたがPythonでのRPA作成にもいろいろ考慮しないといけないことがあり、
例えば

など、現状の調査&操作検証&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接続を利用しているだろうと
どのように管理しているか確認したところ綺麗にフォルダ分けしたり、
人によっては視覚的に表示される便利なツールを使っていました。
まったく出る幕ございませんでした。