From f93028125c78a77f7c7f173499cd541bc22f7c04 Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Thu, 9 Apr 2020 16:51:36 +0300 Subject: [PATCH] # RobotRDPActive refactoring - create ControlPanel template --- .../ControlPanel_RobotRDPActive_Template.py | 186 ++++++++++++++++++ Orchestrator/HowToConfigureOrchestrator.txt | 11 +- ...OpenRPA.Orchestrator_x64_administrator.lnk | Bin 0 -> 1694 bytes ...Orchestrator_x64_administrator_startup.cmd | 2 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 Orchestrator/ControlPanel_RobotRDPActive_Template.py create mode 100644 Orchestrator/pyOpenRPA.Orchestrator_x64_administrator.lnk create mode 100644 Orchestrator/pyOpenRPA.Orchestrator_x64_administrator_startup.cmd diff --git a/Orchestrator/ControlPanel_RobotRDPActive_Template.py b/Orchestrator/ControlPanel_RobotRDPActive_Template.py new file mode 100644 index 00000000..2aa4f2f9 --- /dev/null +++ b/Orchestrator/ControlPanel_RobotRDPActive_Template.py @@ -0,0 +1,186 @@ +import psutil, datetime, logging, os, json +# # # # # # # ORCHESTRATOR CONTROL PANEL # # # # # # # +# Init parameters +gProcessNameWOEXE = "OpenRPA_RobotRDPActive" +gRDPStartFilePath = os.path.abspath(os.path.join(os.getcwd(), "..\\Utils\\RobotRDPActive\\pyOpenRPA.Tools.RobotRDPActive_x64.cmd")) # cwd is orchestrator working directory +gOrchestratorToRobotKeyList = ["Storage","RobotRDPActive","OrchestratorToRobotStorage"] +gRobotToOrchestratorKeyList = ["Storage","RobotRDPActive","RobotToOrchestratorStorage"] +gRobotRDPActiveDefsFolderPath = "" + +# Function, which is generate Dict for front-endswith +def ControlPanelRenderDict(inGSettings): + """result={ + "HeaderLeftText":"", + "HeaderRightText":"
", + "DataStorageKey":"RobotRDPActive", #Use key for set current dict in mGlobal.DataStorage["DataStorageKey"] on client side + "SubheaderText": "State: Turned on", + "BodyKeyValueList":[ + {"Key":"Session list","Value":""}, + {"Key":"Session list","Value":""} + ], + "FooterText":"Last update: 9:38:00 09.10.2019", + "FooterButtonX2List":[ + {"Text":"Turn on", "Color":"green", "Link":"", "OnClick": lOnClickRunButton.replace("\\","\\\\")}, + ], + "FooterButtonX1List":[ + {"Text":"Kill", "Color":"red", "Link":"", "OnClick": lOnClickForceCloseButton.replace("\\","\\\\")} + ] + }""" + # START :: Create activities :: START # + ## RDP Start + lActivityRDPStartEscaped = (f"mGlobal.Controller.CMDRunText('start cmd /K {gRDPStartFilePath}');").replace("\\","\\\\") # Need escape because this is render on client side and after that it goes to server side :( + ## RDP Safe turn off + lActivityRDPSafeOffEscaped = (f"mGlobal.Processor.ServerValueSet({json.dumps(gOrchestratorToRobotKeyList+['SafeTurnOff'])},true);").replace("\\","\\\\") + ## RDP Kill + lActivityRDPKillEscaped = (f"mGlobal.Controller.CMDRunText('taskkill /F /im {gProcessNameWOEXE}.exe');").replace("\\","\\\\") + ## Recieve file from RDP session to Orchestrator session + lActivityFileRDP2OrchestratorDict = [ + { + "ModulePath": f"{os.path.join(gRobotRDPActiveDefsFolderPath,'Defs_SessionIndex.py')}", # "Session\\SessionDefs.py" + "DefName":"FileStoredRecieve", # Function name + "ArgList":[], # Args list + "ArgDict":{"inSessionIndex": 0, "inHostFilePath": "testRecieve.txt","inRDPFilePath": "C:\\Temp\\testRecieve.txt"} # Args dictionary + } + ] + lActivityFileRDP2Orchestrator = f"mGlobal.Processor.ServerValueSet(['Storage','RobotRDPActive','OrchestratorToRobotResetStorage','ActivityList'],{json.dumps(lActivityFileRDP2OrchestratorDict)});" + # END :: Create activities :: END # + # START :: Init result dict template :: START # + lResultDict={ + "HeaderLeftText":"Keep active RDP sessions", + "HeaderRightText":"Tech", + "DataStorageKey":"RobotRDPActive", #Use key for set current dict in mGlobal.DataStorage["DataStorageKey"] on client side + "SubheaderText":"", + "BodyKeyValueList":[ + {"Key":"Session list","Value":""} + ], + "FooterText":"Last update: 9:38:00 09.10.2019", + "FooterButtonX2List":[ + {"Text":"Turn on", "Color":"green", "Link":"", "OnClick": lActivityRDPStartEscaped}, + {"Text":"Safe turn off", "Color":"orange", "Link":"", "OnClick": lActivityRDPSafeOffEscaped} + ], + "FooterButtonX1List":[ + {"Text":"Kill", "Color":"red", "Link":"", "OnClick": lActivityRDPKillEscaped} + ]#, + #"GlobalStorage": inGSettings.get("Storage",{}) # UNCOMMENT FOR DEBUG PURPOSE TO WATCH inGSettings on client side + } + # END :: Init result dict template :: END # + # START :: Fill BodyKeyValueList :: START # + ## Read RDPList + lRDPList = TechDictKeyList_ItemGet(inDict=inGSettings, inKeyList=gRobotToOrchestratorKeyList+["RDPList"], inDefault=[]): + lFullScreenSessionIndex = TechDictKeyList_ItemGet(inDict=inGSettings, inKeyList=gRobotToOrchestratorKeyList+["FullScreenSessionIndex"], inDefault=None): + lRDPListIndex = 0 + for lItem in lRDPList: + ### Lable that session has fullscreen + lLabelSessionFullScreen = "" + lLabelIsIgnored = "" + ### Link set full screen + lOnClickSetFullScreen = f"mGlobal.Processor.ServerValueSet(['Storage','RobotRDPActive','OrchestratorToRobotStorage','FullScreenSessionIndex'],{lRDPListIndex});" + lSetFullScreenA = f'Set fullscreen' + if lRDPListIndex == lFullScreenSessionIndex: + lLabelSessionFullScreen = '[Fullscreen]' + lOnClickSetFullScreen = f"mGlobal.Processor.ServerValueSet(['Storage','RobotRDPActive','OrchestratorToRobotStorage','FullScreenSessionIndex'],null);" + lSetFullScreenA = f'Set minimized' + lIgnoreIndexListOnClick = "$.ajax({type: 'POST', url: 'RobotRDPActive/IgnoreIndexListAppend', data: '"+str(lRDPListIndex)+"', success: function(lData,l2,l3){}, dataType: 'text'});" + lIgnoreIndexListLink = f'Ignore' + ### Check if in ignore + if lRDPListIndex in inGSettings.get("Storage",{}).get("RobotRDPActive",{}).get("OrchestratorToRobotStorage",{}).get("IgnoreIndexList",[]): + lLabelIsIgnored = '[Ignored]' + lIgnoreIndexListOnClick = "$.ajax({type: 'POST', url: 'RobotRDPActive/IgnoreIndexListRemove', data: '"+str(lRDPListIndex)+"', success: function(lData,l2,l3){}, dataType: 'text'});" + lIgnoreIndexListLink = f'Unignore' + ### Session state + lItemSessionState='Disconnected' + if lItem.get("SessionIsWindowResponsibleBool",False): + lItemSessionState='Connected' + lResultDict["BodyKeyValueList"].append({"Key":f"[{str(lRDPListIndex)}]{lLabelSessionFullScreen}{lLabelIsIgnored}{lItem.get('Host','localhost')}:{lItem.get('Port','--')}","Value":f"{lItem.get('Login','--')}, {lItem.get('SessionHex','--')}, State {lItemSessionState}, {lSetFullScreenA}, {lIgnoreIndexListLink}"}) + lRDPListIndex = lRDPListIndex + 1 + # END :: Fill BodyKeyValueList :: END # + # START :: Fill SubheaderText :: START # + ## Variants + lSubheaderRunTrueText="State: Turned on" + lSubheaderRunFalseText="State: Turned off" + if CheckIfProcessRunning(gProcessNameWOEXE): + lResultDict["SubheaderText"]=lSubheaderRunTrueText + else: + lResultDict["SubheaderText"]=lSubheaderRunFalseText + # END :: Fill SubheaderText :: END # + # Fill FooterText + lResultDict["FooterText"]=f'Last update: {datetime.datetime.now().strftime("%H:%M:%S %d.%m.%Y")}' + return lResultDict +# Check in control panel, that process is runnning +def CheckIfProcessRunning(processName): + '''Check if there is any running process that contains the given name processName.''' + #Iterate over the all the running process + for proc in psutil.process_iter(): + try: + # Check if process name contains the given name string. + if processName.lower() in proc.name().lower(): + return True + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + pass + return False +#Add to ignore list - AJAX request from client side +def IgnoreIndexListAppend(inRequest,inConfiguration): + lIgnoreList = TechDictKeyList_ItemGet(inDict=inConfiguration, inKeyList=gOrchestratorToRobotKeyList+["IgnoreIndexList"], inDefault=[]): + lIgnoreIndex={} + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray=inRequest.rfile.read(lInputByteArrayLength) + #Превращение массива байт в объект + lIgnoreIndex=int(lInputByteArray.decode('utf8')) + #check if index not in list + if lIgnoreIndex not in lIgnoreList: + #append to list + lIgnoreList.append(lIgnoreIndex) +#remove from Ignore list - AJAX request from client side +def IgnoreIndexListRemove(inRequest,inConfiguration): + lIgnoreList = TechDictKeyList_ItemGet(inDict=inConfiguration, inKeyList=gOrchestratorToRobotKeyList+["IgnoreIndexList"], inDefault=[]): + lIgnoreIndex={} + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray=inRequest.rfile.read(lInputByteArrayLength) + #Превращение массива байт в объект + lIgnoreIndex=int(lInputByteArray.decode('utf8')) + #check if index not in list + if lIgnoreIndex in lIgnoreList: + #append to list + lIgnoreList.remove(lIgnoreIndex) #Remove delete the element lIgnoreIndex +# Technical def - Get item by the list of keys +def TechDictKeyList_ItemGet(inDict, inKeyList, inDefault={}): + lResult=inDict + for lItem in inKeyList: + if lResult: + lResult = lResult.get(lItem,None) + if not lResult: + lResult=inDefault + return lResult +# # # # # # # # # # # # # # # # # # # # # # # # +#Orchestrator settings +def SettingsUpdate(inGSettings): + #Add RobotRDPActive in control panel + inGSettings["ControlPanelDict"]["RobotList"].append({"RenderFunction": ControlPanelRenderDict}) + #Default structure + inGSettings["Storage"]["RobotRDPActive"]={ + "OrchestratorToRobotResetStorage":{"SafeTurnOff":False}, + "OrchestratorToRobotStorage":{ + "FullScreenSessionIndex":None, + "IgnoreIndexList": [] + } + } + #Add methods + inGSettings["Server"]["URLList"].append( + { + "Method":"POST", + "URL": "/RobotRDPActive/IgnoreIndexListAppend", #URL of the request + "MatchType": "Equal", #"BeginWith|Contains|Equal|EqualCase", + "ResponseDefRequestGlobal": IgnoreIndexListAppend #Function with str result + } + ) + inGSettings["Server"]["URLList"].append( + { + "Method":"POST", + "URL": "/RobotRDPActive/IgnoreIndexListRemove", #URL of the request + "MatchType": "Equal", #"BeginWith|Contains|Equal|EqualCase", + "ResponseDefRequestGlobal": IgnoreIndexListRemove #Function with str result + } + ) + return inGSettings \ No newline at end of file diff --git a/Orchestrator/HowToConfigureOrchestrator.txt b/Orchestrator/HowToConfigureOrchestrator.txt index 93c7fd9c..7cb7acfb 100644 --- a/Orchestrator/HowToConfigureOrchestrator.txt +++ b/Orchestrator/HowToConfigureOrchestrator.txt @@ -1,3 +1,12 @@ 1. Create user for Orchestrator console session (pay attention for administrator rights - is needed for RobotRDPActive) - how to create user look windows instructions 2. Create AutoAdminLogon in windows for Orchestrator user - go in console session when restart (Utils/AutoLogon(ProtectedPassword).zip) - https://docs.microsoft.com/en-us/sysinternals/downloads/autologon -3. Add Orchestrator in startup - win+r > shell:startup > add shortcut of orchestrator \ No newline at end of file +3. Add Orchestrator in startup - win+r > shell:startup > add shortcut of orchestrator. Add shortcut to pyOpenRPA.Orchestrator_x64_administrator_startup.cmd if you want to auto start orchestrator as administrator + +Screen active is require administrator rights because of tscon cmd usage (to switch to console desktop session after the RDP). +! ATTENTION ! Screen active can be used only with local administrator rights on the machine + +OpenRPA suggest 2 ways to automate run +Way 1 (create task in windows task scheduler to run with extended rights ScreenActive or Orchestrator) + +Way 2 (use shortcut ScreenActive| Orchestrator to run with administrator rights in shortcut options) +To turn off UAC window got to Menu > Change User Account Control Settings > Select Never notify \ No newline at end of file diff --git a/Orchestrator/pyOpenRPA.Orchestrator_x64_administrator.lnk b/Orchestrator/pyOpenRPA.Orchestrator_x64_administrator.lnk new file mode 100644 index 0000000000000000000000000000000000000000..65d446af8ff957b9404f7da3e59bf3faf39b5645 GIT binary patch literal 1694 zcmcIkTS(JU82`yYl2woy`WA#_pZLfEDyzUyW z(`^kDLDtBdqSdF{ib`udP0mwRm(S#Kv~WRZT5!PDmYJcmH!}te!?7+ajWigMMH>O~ ztxH}Z>o3I*o%?&ys+l{9xOP-XU~{`_v<|O}i3a9f6_+>sI8#^hX!6qS^WV(zsqgM4 z>1wB9`W{26ym@bCsr8F|50SLrLvv{s#qvo%{MLxG2M528t?T#HZSzqt1w$f{LHx^2 z(;#VC)Fg{TwoilvrT@J1;q<|c>VeN;)!kP=-YKI#N;XJPg5_OCAdWWH5HLuWbJ_p# ziE_2fMF{fQ zh|r%Z^B069d*g&XDZA%o%+M>0nBF9e4N<>UHi$>b{QqCN)t|ry=*8X+NyU)RZJL-!`