You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Sources/pyOpenRPA/Orchestrator/Orchestrator.py

834 lines
49 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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}}
)
## 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={} #Словарь отработанных активностей, ключ - кортеж (<activityType>, <datetime>, <processPath || processName>, <processArgs>)
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<lActivityTimeEndDateTime and
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