import subprocess, json, psutil, time, os, win32security, sys, base64 #Get input argument from . import Server from . import Timer from . import Processor from . import BackwardCompatibility # Backward compatibility from v1.1.13 from . import Core # ATTENTION! HERE IS NO Relative import because it will be imported dynamically # All function check the flag SessionIsWindowResponsibleBool == True else no cammand is processed # All functions can return None, Bool or Dict { "IsSuccessful": True } from .RobotRDPActive import CMDStr # Create CMD Strings from .RobotRDPActive import Connector # RDP API #from .Settings import Settings import importlib from importlib import util import threading # Multi-threading for RobotRDPActive from .RobotRDPActive import RobotRDPActive #Start robot rdp active from .RobotScreenActive import Monitor #Start robot screen active from . import SettingsTemplate # Settings template import uuid # Generate uuid import datetime # datetime #Единый глобальный словарь (За основу взять из Settings.py) global gSettingsDict # AGENT DEFS # Add activity in AgentDict def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDict): # Check if item is created lAgentDictItemKeyTurple = (inHostNameStr.upper(),inUserStr.upper()) if lAgentDictItemKeyTurple not in inGSettings["AgentDict"]: inGSettings["AgentDict"][lAgentDictItemKeyTurple] = SettingsTemplate.__AgentDictItemCreate__() lThisAgentDict = inGSettings["AgentDict"][lAgentDictItemKeyTurple] lThisAgentDict["ActivityList"].append(inActivityItemDict) # Send to agent activity item to OSCMD def AgentOSCMD(inGSettings, inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True): # pyOpenRPA.Agent: Send CMD to OS. Result return to log + Orchestrator by the A2O connection # def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings=None): # Create Activity Item for the agent lActivityItemDict = { "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # Send binary file to Agent (Bytes) def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes): # pyOpenRPA.Agent: Create binary file by the base64 string (safe for JSON transmition) # def OSFileBinaryDataBase64StrCreate(inFilePathStr, inFileDataBase64Str,inGSettings = None): # Create Activity Item for the agent lFileDataBase64Str = base64.b64encode(inFileDataBytes).decode("utf-8") lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataBase64Str":lFileDataBase64Str}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # Send binary file to Agent (base64 string) def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str): # pyOpenRPA.Agent: Create binary file by the base64 string (safe for JSON transmission) # def OSFileBinaryDataBase64StrCreate(inFilePathStr, inFileDataBase64Str,inGSettings = None): # Create Activity Item for the agent lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataBase64Str":inFileDataBase64Str}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # Send text file to Agent (string) def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataStr, inEncodingStr = "utf-8"): # pyOpenRPA.Agent: Create text file by the string # def OSFileTextDataStrCreate(inFilePathStr, inFileDataStr, inEncodingStr = "utf-8",inGSettings = None): # Create Activity Item for the agent lActivityItemDict = { "Def":"OSFileTextDataStrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataStr":inFileDataStr, "inEncodingStr": inEncodingStr}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # OS DEFS # Defs to use in orchestrator def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## Verify credentials in windows try: hUser = win32security.LogonUser( inUserStr,inDomainStr, inPasswordStr, win32security.LOGON32_LOGON_NETWORK, win32security.LOGON32_PROVIDER_DEFAULT ) except win32security.error: return False else: return True def OSCMD(inCMDStr, inRunAsyncBool=True, inLogger = None): ## OS send command in shell locally lResultStr = "" # Subdef to listen OS result def _CMDRunAndListenLogs(inCMDStr, inLogger): lResultStr = "" lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if inLogger: lListenBool = True inLogger.info(f"{lOSCMDKeyStr}: # # # # CMD Process has been STARTED # # # # ") inLogger.info(f"{lOSCMDKeyStr}: {inCMDStr}") while lListenBool: lOutputLineBytes = lCMDProcess.stdout.readline() if lOutputLineBytes == b"": lListenBool = False lStr = lOutputLineBytes.decode('cp866') if lStr.endswith("\n"): lStr = lStr[:-1] inLogger.info(f"{lOSCMDKeyStr}: {lStr}") lResultStr+=lStr inLogger.info(f"{lOSCMDKeyStr}: # # # # CMD Process has been FINISHED # # # # ") return lResultStr # New call if inRunAsyncBool: lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inLogger":inLogger}) lThread.start() lResultStr="ActivityList has been started in async mode - no output is available here." else: lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inLogger=inLogger) #lCMDCode = "cmd /c " + inCMDStr #subprocess.Popen(lCMDCode) #lResultCMDRun = 1 # os.system(lCMDCode) return lResultStr def OrchestratorRestart(inGSettings=None): ## Orchestrator restart OrchestratorSessionSave(inGSettings=inGSettings) # Dump RDP List in file json if inGSettings is not None: lL = inGSettings["Logger"] if lL: lL.info(f"Do restart") # Restart session os.execl(sys.executable, os.path.abspath(__file__), *sys.argv) sys.exit(0) def OrchestratorSessionSave(inGSettings=None): ## Orchestrator session save # Dump RDP List in file json lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file lFile.close() # Close the file if inGSettings is not None: lL = inGSettings["Logger"] if lL: lL.info( f"Orchestrator has dump the RDP list before the restart. The RDP List is {inGSettings['RobotRDPActive']['RDPList']}") return True # Update user access def OrchestratorUACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=[], inCPAllowKeyList=[], inRoleHierarchyAllowedDict={}): lUserTurple = (inADStr.upper(),inADLoginStr.upper()) # Create turple key for inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"] if inURLList==[] and lUserTurple not in inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"]: # Backward compatibility if user is not exist inURLList=[ { "Method": "GET", "MatchType": "Beginwith", "URL": "/", # "FlagAccessDefRequestGlobalAuthenticate": TestDef "FlagAccess": True }, { "Method": "POST", "MatchType": "Beginwith", "URL": "/", # "FlagAccessDefRequestGlobalAuthenticate": TestDef "FlagAccess": True } ] # Check RoleHierarchyAllowedDict in gSettings for the old role hierarchy - include in result. if lUserTurple in inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"] and "RoleHierarchyAllowedDict" in inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"][lUserTurple]: lRoleHierarchyAllowedOLDDict = inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"][lUserTurple]["RoleHierarchyAllowedDict"] Server.__ComplexDictMerge2to1__(in1Dict=inRoleHierarchyAllowedDict, in2Dict=lRoleHierarchyAllowedOLDDict) # Merge dict 2 into dict 1 # Create Access item lRuleDomainUserDict = { "MethodMatchURLBeforeList": inURLList, "RoleHierarchyAllowedDict": inRoleHierarchyAllowedDict } # Case add domain + user inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"].update({(inADStr.upper(),inADLoginStr.upper()):lRuleDomainUserDict}) if inADIsDefaultBool: # Case add default domain + user inGSettings["Server"]["AccessUsers"]["RuleDomainUserDict"].update({("",inADLoginStr.upper()):lRuleDomainUserDict}) # Add supertoken for the all access (it is need for the robot communication without human) def OrchestratorUACSuperTokenUpdate(inGSettings, inSuperTokenStr): lLoginStr = "SUPERTOKEN" OrchestratorUACUpdate(inGSettings=inGSettings, inADLoginStr=lLoginStr) inGSettings["Server"]["AccessUsers"]["AuthTokensDict"].update( {inSuperTokenStr:{"User":lLoginStr, "Domain":"", "TokenDatetime": datetime.datetime.now(), "FlagDoNotExpire":True}} ) # OrchestratorWEB # Add control panel HTML, JSON generator or JS when page init def OrchestratorWebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None): # Create Struct if the re is current key if inCPKeyStr not in inGSettings["CPDict"]: inGSettings["CPDict"][inCPKeyStr] = {"HTMLRenderDef": None,"JSONGeneratorDef": None, "JSInitGeneratorDef": None} # CASE HTMLRender if inHTMLRenderDef is not None: inGSettings["CPDict"][inCPKeyStr]["HTMLRenderDef"]=inHTMLRenderDef # CASE JSONGenerator if inJSONGeneratorDef is not None: inGSettings["CPDict"][inCPKeyStr]["JSONGeneratorDef"] = inJSONGeneratorDef # CASE JSInitGeneratorDef if inJSInitGeneratorDef is not None: inGSettings["CPDict"][inCPKeyStr]["JSInitGeneratorDef"] = inJSInitGeneratorDef ## GSettings defs def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=[]): # Set value in GSettings by the key list lDict = inGSettings for lItem2 in inKeyList[:-1]: #Check if key - value exists if lItem2 in lDict: pass else: lDict[lItem2]={} lDict=lDict[lItem2] lDict[inKeyList[-1]] = inValue #Set value return True def GSettingsKeyListValueGet(inGSettings, inKeyList=[]): # Get the value from the GSettings by the key list lDict = inGSettings for lItem2 in inKeyList[:-1]: #Check if key - value exists if lItem2 in lDict: pass else: lDict[lItem2]={} lDict=lDict[lItem2] return lDict.get(inKeyList[-1],None) def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=[]): # Append value in GSettings by the key list lDict = inGSettings for lItem2 in inKeyList[:-1]: #Check if key - value exists if lItem2 in lDict: pass else: lDict[lItem2]={} lDict=lDict[lItem2] lDict[inKeyList[-1]].append(inValue) #Set value return True def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=[]): # Operator plus value in GSettings by the key list lDict = inGSettings for lItem2 in inKeyList[:-1]: #Check if key - value exists if lItem2 in lDict: pass else: lDict[lItem2]={} lDict=lDict[lItem2] lDict[inKeyList[-1]] += inValue #Set value return True ## Process defs def ProcessIsStarted(inProcessNameWOExeStr): # Check if process is started ''' 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 inProcessNameWOExeStr.lower() in proc.name().lower(): return True except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass return False; def ProcessStart(inPathStr, inArgList, inStopProcessNameWOExeStr=None): # Start process locally [optional: if task name is not started] lStartProcessBool = True if inStopProcessNameWOExeStr is not None: #Check if process running lCheckTaskName = inStopProcessNameWOExeStr if len(lCheckTaskName)>4: if lCheckTaskName[-4:].upper() != ".EXE": lCheckTaskName = lCheckTaskName+".exe" else: lCheckTaskName = lCheckTaskName+".exe" #Check if process exist if not ProcessIsStarted(inProcessNameWOExeStr=lCheckTaskName): lStartProcessBool=True if lStartProcessBool == True: # Start if flag is true lItemArgs=[inPathStr] lItemArgs.extend(inArgList) subprocess.Popen(lItemArgs,shell=True) def ProcessStop(inProcessNameWOExeStr, inCloseForceBool, inUserNameStr = "%username%"): # Stop process # Support input arg if with .exe lProcessNameWExeStr = inProcessNameWOExeStr if len(lProcessNameWExeStr) > 4: if lProcessNameWExeStr[-4:].upper() != ".EXE": lProcessNameWExeStr = lProcessNameWExeStr + ".exe" else: lProcessNameWExeStr = lProcessNameWExeStr + ".exe" # Flag Force lActivityCloseCommand = 'taskkill /im ' + lProcessNameWExeStr if inCloseForceBool == True: lActivityCloseCommand += " /F" # None - all users, %username% - current user, another str - another user if inUserNameStr is not None: lActivityCloseCommand += f' /fi "username eq {inUserNameStr}"' # Kill process os.system(lActivityCloseCommand) #Check activity of the list of processes def ProcessListGet(inProcessNameWOExeList=[]): '''Get list of running process sorted by Memory Usage and filtered by inProcessNameWOExeList''' lMapUPPERInput = {} # Mapping for processes WO exe lResult = {"ProcessWOExeList":[],"ProcessDetailList":[]} # Create updated list for quick check lProcessNameWOExeList = [] for lItem in inProcessNameWOExeList: if lItem is not None: lProcessNameWOExeList.append(f"{lItem.upper()}.EXE") lMapUPPERInput[f"{lItem.upper()}.EXE"]= lItem # # # Iterate over the list for proc in psutil.process_iter(): try: # Fetch process details as dict pinfo = proc.as_dict(attrs=['pid', 'name', 'username']) pinfo['vms'] = proc.memory_info().vms / (1024 * 1024) pinfo['NameWOExeUpperStr'] = pinfo['name'][:-4].upper() # Add if empty inProcessNameWOExeList or if process in inProcessNameWOExeList if len(lProcessNameWOExeList)==0 or pinfo['name'].upper() in lProcessNameWOExeList: pinfo['NameWOExeStr'] = lMapUPPERInput[pinfo['name'].upper()] lResult["ProcessDetailList"].append(pinfo) # Append dict to list lResult["ProcessWOExeList"].append(pinfo['NameWOExeStr']) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass return lResult # Python def - start module function def PythonStart(inModulePathStr, inDefNameStr, inArgList=[], inArgDict={}, inLogger = None): # Python import module and start def try: lModule=importlib.import_module(inModulePathStr) #Подключить модуль для вызова lFunction=getattr(lModule,inDefNameStr) #Найти функцию return lFunction(*inArgList,**inArgDict) except Exception as e: if inLogger: inLogger.exception("Loop activity error: module/function not founded") # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # RDPSession # # # # # # # # # # # # # # # # # # # # # # # # Create new RDPSession in RobotRDPActive def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionConnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inHostStr": inHostStr, "inPortStr": inPortStr, "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr, }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: # In processor - do execution # ATTENTION - dont connect if RDP session is exist if inRDPSessionKeyStr not in inGSettings["RobotRDPActive"]["RDPList"]: lRDPConfigurationItem = { # Init the configuration item "Host": inHostStr, # Host address, example "77.77.22.22" "Port": inPortStr, # RDP Port, example "3389" "Login": inLoginStr, # Login, example "test" "Password": inPasswordStr, # Password, example "test" "Screen": { "Width": 1680, # Width of the remote desktop in pixels, example 1680 "Height": 1050, # Height of the remote desktop in pixels, example 1050 # "640x480" or "1680x1050" or "FullScreen". If Resolution not exists set full screen, example "FlagUseAllMonitors": False, # True or False, example False "DepthBit": "32" # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": ["c"], # List of the Root sesion hard drives, example ["c"] ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, # Flag if the RDP window is exist, old name "FlagSessionIsActive". Check every n seconds , example False "SessionIsWindowResponsibleBool": False, # Flag if RDP window is responsible (recieve commands). Check every nn seconds. If window is Responsible - window is Exist too , example False "SessionIsIgnoredBool": False # Flag to ignore RDP window False - dont ignore, True - ignore, example False } inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Add item in RDPList Connector.Session(lRDPConfigurationItem) # Create the RDP session Connector.SystemRDPWarningClickOk() # Click all warning messages return True # Disconnect the RDP session def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = []): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionDisconnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inBreakTriggerProcessWOExeList": inBreakTriggerProcessWOExeList }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: # In processor - do execution lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) if lSessionHex: lProcessListResult = {"ProcessWOExeList":[],"ProcessDetailList":[]} if len(inBreakTriggerProcessWOExeList) > 0: lProcessListResult = ProcessListGet(inProcessNameWOExeList=inBreakTriggerProcessWOExeList) # Run the task manager monitor if len(lProcessListResult["ProcessWOExeList"]) == 0: # Start disconnect if no process exist inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) Connector.SessionClose(inSessionHexStr=lSessionHex) Connector.SystemRDPWarningClickOk() # Click all warning messages return True # RDP Session reconnect def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionReconnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lRDPConfigurationItem = inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] RDPSessionDisconnect(inRDPSessionKeyStr=inRDPSessionKeyStr) # Disconnect the RDP # Add item in RDPList inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Create the RDP session Connector.Session(lRDPConfigurationItem) return True # Stop track the RDP session. Current def dont kill RDP session - only stop to track it (it can give ) def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr): lResult = True inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult # Logoff the RDP session def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = []): lResult = True # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionLogoff, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inBreakTriggerProcessWOExeList": inBreakTriggerProcessWOExeList }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lCMDStr = "shutdown -L" # CMD logoff command # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) if lSessionHex: lProcessListResult = {"ProcessWOExeList":[],"ProcessDetailList":[]} if len(inBreakTriggerProcessWOExeList) > 0: lProcessListResult = ProcessListGet(inProcessNameWOExeList=inBreakTriggerProcessWOExeList) # Run the task manager monitor if len(lProcessListResult["ProcessWOExeList"]) == 0: # Start logoff if no process exist # Run CMD - dont crosscheck because CMD dont return value to the clipboard when logoff Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="RUN", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult # Check RDP Session responsibility TODO NEED DEV + TEST def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionResponsibilityCheck, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lRDPConfigurationItem = inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] # Get the alias # set the fullscreen # ATTENTION!!! Session hex can be updated!!! Connector.SessionScreenFull(inSessionHex=lRDPConfigurationItem["SessionHex"], inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) time.sleep(1) # Check RDP responsibility lDoCheckResponsibilityBool = True lDoCheckResponsibilityCountMax = 20 lDoCheckResponsibilityCountCurrent = 0 while lDoCheckResponsibilityBool: # Check if counter is exceed - raise exception if lDoCheckResponsibilityCountCurrent >= lDoCheckResponsibilityCountMax: pass #raise ConnectorExceptions.SessionWindowNotResponsibleError("Error when initialize the RDP session - RDP window is not responding!") # Check responding lDoCheckResponsibilityBool = not Connector.SystemRDPIsResponsible(inSessionHexStr = lRDPConfigurationItem["SessionHex"]) # Wait if is not responding if lDoCheckResponsibilityBool: time.sleep(3) # increase the couter lDoCheckResponsibilityCountCurrent+=1 return True # Start process if it is not running def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionProcessStartIfNotRunning, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inProcessNameWEXEStr": inProcessNameWEXEStr, "inFilePathStr": inFilePathStr, "inFlagGetAbsPathBool": inFlagGetAbsPathBool }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lResult = True lCMDStr = CMDStr.ProcessStartIfNotRunning(inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPath= inFlagGetAbsPathBool) # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) # Run CMD if lSessionHex: Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK"): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionCMDRun, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inCMDStr": inCMDStr, "inModeStr": inModeStr }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lResult = True # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) # Run CMD if lSessionHex: Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=inCMDStr, inModeStr=inModeStr, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult # Create CMD str to stop process def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionProcessStop, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inProcessNameWEXEStr": inProcessNameWEXEStr, "inFlagForceCloseBool": inFlagForceCloseBool }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lResult = True lCMDStr = f'taskkill /im "{inProcessNameWEXEStr}" /fi "username eq %USERNAME%"' if inFlagForceCloseBool: lCMDStr+= " /F" # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) # Run CMD if lSessionHex: Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult # Send file from Host to Session RDP using shared drive in RDP def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionFileStoredSend, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inHostFilePathStr": inHostFilePathStr, "inRDPFilePathStr": inRDPFilePathStr }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lResult = True lCMDStr = CMDStr.FileStoredSend(inHostFilePath = inHostFilePathStr, inRDPFilePath = inRDPFilePathStr) # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr, {}).get("SessionHex", None) #lSessionHex = inGlobalDict["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]["SessionHex"] # Run CMD if lSessionHex: Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="LISTEN", inClipboardTimeoutSec = 120, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult # Recieve file from Session RDP to Host using shared drive in RDP def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr): # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") lResult = { "Def": RDPSessionFileStoredRecieve, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPFilePathStr": inRDPFilePathStr, "inHostFilePathStr": inHostFilePathStr }, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } inGSettings["ProcessorDict"]["ActivityList"].append(lResult) else: lResult = True lCMDStr = CMDStr.FileStoredRecieve(inRDPFilePath = inRDPFilePathStr, inHostFilePath = inHostFilePathStr) # Calculate the session Hex lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None) # Run CMD if lSessionHex: Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="LISTEN", inClipboardTimeoutSec = 120, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult # # # # # # # # # # # # # # # # # # # # # # # # # # # # Start orchestrator # # # # # # # # # # # # # # # # # # # # # # # # Interval gsettings auto cleaner def GSettingsAutocleaner(inGSettings): while True: time.sleep(inGSettings["Autocleaner"]["IntervalSecFloat"]) # Wait for the next iteration lL = inGSettings["Logger"] if lL: lL.info(f"Autocleaner is running") # Info lNowDatetime = datetime.datetime.now() # Get now time # Clean old items in Client > Session > TechnicalSessionGUIDCache lTechnicalSessionGUIDCacheNew = {} for lItemKeyStr in inGSettings["Client"]["Session"]["TechnicalSessionGUIDCache"]: lItemValue = inGSettings["Client"]["Session"]["TechnicalSessionGUIDCache"][lItemKeyStr] if (lNowDatetime - lItemValue["InitDatetime"]).total_seconds() < inGSettings["Client"]["Session"]["LifetimeSecFloat"]: # Add if lifetime is ok lTechnicalSessionGUIDCacheNew[lItemKeyStr]=lItemValue # Lifetime is ok - set else: if lL: lL.debug(f"Client > Session > TechnicalSessionGUIDCache > lItemKeyStr: Lifetime is expired. Remove from gSettings") # Info inGSettings["Client"]["Session"]["TechnicalSessionGUIDCache"] = lTechnicalSessionGUIDCacheNew # Set updated Cache # # # # # # # # # # # # # # # # # # # # # # # # # # from .. import __version__ # Get version from the package # Main def for orchestrator def Orchestrator(inGSettings): lL = inGSettings["Logger"] # Append Orchestrator def to ProcessorDictAlias lModule = sys.modules[__name__] lModuleDefList = dir(lModule) for lItemDefNameStr in lModuleDefList: # Dont append alias for defs Orchestrator and ___deprecated_orchestrator_start__ if lItemDefNameStr not in ["Orchestrator", "___deprecated_orchestrator_start__"]: lItemDef = getattr(lModule,lItemDefNameStr) if callable(lItemDef): inGSettings["ProcessorDict"]["AliasDefDict"][lItemDefNameStr]=lItemDef #mGlobalDict = Settings.Settings(sys.argv[1]) gSettingsDict = inGSettings # Alias for old name in alg inGSettings["VersionStr"] = __version__ #Logger alias lL = gSettingsDict["Logger"] if lL: lL.info("Link the gSettings in submodules") #Logging Processor.gSettingsDict = gSettingsDict Timer.gSettingsDict = gSettingsDict Timer.Processor.gSettingsDict = gSettingsDict Server.gSettingsDict = gSettingsDict Server.ProcessorOld.gSettingsDict = gSettingsDict # Backward compatibility # Check _SessionLast_RDPList.json in working directory. if exist - load into gsettings # GSettings #"RobotRDPActive": { # "RDPList": { if os.path.exists("_SessionLast_RDPList.json"): lFile = open("_SessionLast_RDPList.json", "r", encoding="utf-8") lSessionLastRDPList = json.loads(lFile.read()) lFile.close() # Close the file os.remove("_SessionLast_RDPList.json") # remove the temp file gSettingsDict["RobotRDPActive"]["RDPList"]=lSessionLastRDPList # Set the last session dict if lL: lL.warning(f"RDP Session List was restored from previous Orchestrator session") #Инициализация настроечных параметров lDaemonLoopSeconds=gSettingsDict["Scheduler"]["ActivityTimeCheckLoopSeconds"] lDaemonActivityLogDict={} #Словарь отработанных активностей, ключ - кортеж (, , , ) lDaemonLastDateTime=datetime.datetime.now() gSettingsDict["Server"]["WorkingDirectoryPathStr"] = os.getcwd() # Set working directory in g settings # Init SettingsUpdate defs from file list (after RDP restore) lSettingsUpdateFilePathList = gSettingsDict.get("OrchestratorStart", {}).get("DefSettingsUpdatePathList",[]) lSubmoduleFunctionName = "SettingsUpdate" lSettingsPath = "\\".join(os.path.join(os.getcwd(), __file__).split("\\")[:-1]) for lModuleFilePathItem in lSettingsUpdateFilePathList: # Import defs with try catch try: # Try to init - go next if error and log in logger lModuleName = lModuleFilePathItem[0:-3] lFileFullPath = os.path.join(lSettingsPath, lModuleFilePathItem) lTechSpecification = importlib.util.spec_from_file_location(lModuleName, lFileFullPath) lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) if lSubmoduleFunctionName in dir(lTechModuleFromSpec): # Run SettingUpdate function in submodule getattr(lTechModuleFromSpec, lSubmoduleFunctionName)(gSettingsDict) except Exception as e: if lL: lL.exception(f"Error when init .py file in orchestrator '{lModuleFilePathItem}'. Exception is below:") # Turn on backward compatibility BackwardCompatibility.Update(inGSettings= gSettingsDict) #Инициализация сервера lThreadServer = Server.RobotDaemonServer("ServerThread", gSettingsDict) lThreadServer.start() if lL: lL.info("Web server has been started") #Logging # Init the RobotScreenActive in another thread lRobotScreenActiveThread = threading.Thread(target= Monitor.CheckScreen) lRobotScreenActiveThread.daemon = True # Run the thread in daemon mode. lRobotScreenActiveThread.start() # Start the thread execution. if lL: lL.info("Robot Screen active has been started") #Logging # Init the RobotRDPActive in another thread lRobotRDPActiveThread = threading.Thread(target= RobotRDPActive.RobotRDPActive, kwargs={"inGSettings":gSettingsDict}) lRobotRDPActiveThread.daemon = True # Run the thread in daemon mode. lRobotRDPActiveThread.start() # Start the thread execution. if lL: lL.info("Robot RDP active has been started") #Logging # Init autocleaner in another thread lAutocleanerThread = threading.Thread(target= GSettingsAutocleaner, kwargs={"inGSettings":gSettingsDict}) lAutocleanerThread.daemon = True # Run the thread in daemon mode. lAutocleanerThread.start() # Start the thread execution. if lL: lL.info("Autocleaner thread has been started") #Logging # Orchestrator start activity if lL: lL.info("Orchestrator start activity run") #Logging for lActivityItem in gSettingsDict["OrchestratorStart"]["ActivityList"]: Processor.ActivityListOrDict(lActivityItem) # Processor thread lProcessorThread = threading.Thread(target= Processor.ProcessorRunSync, kwargs={"inGSettings":gSettingsDict}) lProcessorThread.daemon = True # Run the thread in daemon mode. lProcessorThread.start() # Start the thread execution. if lL: lL.info("Processor has been started (ProcessorDict)") #Logging if lL: lL.info("Scheduler loop start") #Logging gDaemonActivityLogDictRefreshSecInt = 10 # The second period for clear lDaemonActivityLogDict from old items gDaemonActivityLogDictLastTime = time.time() # The second perioad for clean lDaemonActivityLogDict from old items while True: lCurrentDateTime = datetime.datetime.now() #Циклический обход правил lFlagSearchActivityType=True # Periodically clear the lDaemonActivityLogDict if time.time()-gDaemonActivityLogDictLastTime>=gDaemonActivityLogDictRefreshSecInt: gDaemonActivityLogDictLastTime = time.time() # Update the time for lIndex, lItem in enumerate(lDaemonActivityLogDict): if lItem["ActivityEndDateTime"] and lCurrentDateTime<=lItem["ActivityEndDateTime"]: pass # Activity is actual - do not delete now else: # remove the activity - not actual lDaemonActivityLogDict.pop(lIndex,None) lIterationLastDateTime = lDaemonLastDateTime # Get current datetime before iterator (need for iterate all activities in loop) # Iterate throught the activity list for lIndex, lItem in enumerate(gSettingsDict["Scheduler"]["ActivityTimeList"]): # Prepare GUID of the activity lGUID = None if "GUID" in lItem and lItem["GUID"]: lGUID = lItem["GUID"] else: lGUID = str(uuid.uuid4()) lItem["GUID"]=lGUID #Проверка дней недели, в рамках которых можно запускать активность lItemWeekdayList=lItem.get("WeekdayList", [0, 1, 2, 3, 4, 5, 6]) if lCurrentDateTime.weekday() in lItemWeekdayList: if lFlagSearchActivityType: ####################################################################### #Branch 1 - if has TimeHH:MM ####################################################################### if "TimeHH:MM" in lItem: #Вид активности - запуск процесса #Сформировать временной штамп, относительно которого надо будет проверять время #часовой пояс пока не учитываем lActivityDateTime=datetime.datetime.strptime(lItem["TimeHH:MM"],"%H:%M") lActivityDateTime=lActivityDateTime.replace(year=lCurrentDateTime.year,month=lCurrentDateTime.month,day=lCurrentDateTime.day) #Убедиться в том, что время наступило if ( lActivityDateTime>=lDaemonLastDateTime and lCurrentDateTime>=lActivityDateTime): # Log info about activity if lL: lL.info(f"Scheduler:: Activity is started. Scheduler item: {lItem}") #Logging # Do the activity Processor.ActivityListOrDict(lItem["Activity"]) lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity ####################################################################### #Branch 2 - if TimeHH:MMStart, TimeHH:MMStop, ActivityIntervalSeconds ####################################################################### if "TimeHH:MMStart" in lItem and "TimeHH:MMStop" in lItem and "ActivityIntervalSeconds" in lItem: #Сформировать временной штамп, относительно которого надо будет проверять время #часовой пояс пока не учитываем lActivityDateTime=datetime.datetime.strptime(lItem["TimeHH:MMStart"],"%H:%M") lActivityDateTime=lActivityDateTime.replace(year=lCurrentDateTime.year,month=lCurrentDateTime.month,day=lCurrentDateTime.day) lActivityTimeEndDateTime=datetime.datetime.strptime(lItem["TimeHH:MMStop"],"%H:%M") lActivityTimeEndDateTime=lActivityTimeEndDateTime.replace(year=lCurrentDateTime.year,month=lCurrentDateTime.month,day=lCurrentDateTime.day) #Убедиться в том, что время наступило if ( lCurrentDateTime=lActivityDateTime and (lGUID,lActivityDateTime) not in lDaemonActivityLogDict): #Запись в массив отработанных активностей lDaemonActivityLogDict[(lGUID,lActivityDateTime)]={"ActivityStartDateTime":lCurrentDateTime, "ActivityEndDateTime":lActivityTimeEndDateTime} #Запуск циклической процедуры Timer.activityLoopStart(lItem["ActivityIntervalSeconds"], lActivityTimeEndDateTime, lItem["Activity"]) lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity #Уснуть до следующего прогона time.sleep(lDaemonLoopSeconds) # Backward compatibility below to 1.2.0 def __deprecated_orchestrator_start__(): #Call Settings function from argv[1] file ################################################ lSubmoduleFunctionName = "Settings" lFileFullPath = sys.argv[1] lModuleName = (lFileFullPath.split("\\")[-1])[0:-3] lTechSpecification = importlib.util.spec_from_file_location(lModuleName, lFileFullPath) lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) gSettingsDict = None if lSubmoduleFunctionName in dir(lTechModuleFromSpec): # Run SettingUpdate function in submodule gSettingsDict = getattr(lTechModuleFromSpec, lSubmoduleFunctionName)() ################################################# Orchestrator(inGSettings=gSettingsDict) # Call the orchestrator