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

1507 lines
71 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, logging, ctypes #Get input argument
from . import Server
from . import Timer
from . import Processor
from . import BackwardCompatibility # Backward compatibility from v1.1.13
from . import Core
from .Utils import LoggerHandlerDumpLogList
# 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
def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDict):
"""
Add activity in AgentDict. Check if item is created
.. code-block:: python
# USAGE
import this
:param inGSettings: Global settings dict (singleton)
:param inHostNameStr: Agent host name
:param inUserStr: User login, where agent is based
:param inActivityItemDict: ActivityItem
:return: sad
"""
lAgentDictItemKeyTurple = (inHostNameStr.upper(),inUserStr.upper())
if lAgentDictItemKeyTurple not in inGSettings["AgentDict"]:
inGSettings["AgentDict"][lAgentDictItemKeyTurple] = SettingsTemplate.__AgentDictItemCreate__()
lThisAgentDict = inGSettings["AgentDict"][lAgentDictItemKeyTurple]
lThisAgentDict["ActivityList"].append(inActivityItemDict)
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):
Send to agent activity item to OSCMD
:param inGSettings: Global settings dict (singleton)
:param inHostNameStr:
:param inUserStr:
:param inCMDStr:
:param inRunAsyncBool:
"""
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)
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):
Send binary file to Agent (Bytes)
:param inGSettings: Global settings dict (singleton)
:param inHostNameStr:
:param inUserStr:
:param inFilePathStr:
:param inFileDataBytes:
"""
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)
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):
Send binary file to Agent (base64 string)
:param inGSettings: Global settings dict (singleton)
:param inHostNameStr:
:param inUserStr:
:param inFilePathStr:
:param inFileDataBase64Str:
"""
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
:param inGSettings: Global settings dict (singleton)
:param inHostNameStr:
:param inUserStr:
:param inFilePathStr:
:param inFileDataStr:
:param inEncodingStr:
"""
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
def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ##
"""
Verify user credentials in windows. Return bool
:param inUserStr:
:param inPasswordStr:
:param inDomainStr:
:return: True - Credentials are actual; False - Credentials are not actual
"""
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
:param inCMDStr:
:param inRunAsyncBool:
:param inLogger:
:return: CMD result string
"""
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
:param inGSettings: Global settings dict (singleton)
"""
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 in file _SessionLast_RDPList.json (encoding = "utf-8")
:param inGSettings: Global settings dict (singleton)
:return: True every time
"""
# 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.")
return True
def UACKeyListCheck(inRequest, inRoleKeyList) -> bool:
"""
Check is client is has access for the key list
:param inRequest:
:param inRoleKeyList:
:return: bool
"""
return inRequest.UACClientCheck(inRoleKeyList=inRoleKeyList)
def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=None, inRoleHierarchyAllowedDict=None):
"""
Update user access (UAC)
:param inGSettings: Global settings dict (singleton)
:param inADLoginStr:
:param inADStr:
:param inADIsDefaultBool:
:param inURLList:
:param inRoleHierarchyAllowedDict:
"""
lUserTurple = (inADStr.upper(),inADLoginStr.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"]
if inURLList is None: inURLList = [] # Check if None
if inRoleHierarchyAllowedDict is None: inRoleHierarchyAllowedDict = {} # Check if None
# Get the old URLList
try:
inURLList += inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"][lUserTurple]["MethodMatchURLBeforeList"]
except:
pass
# Check RoleHierarchyAllowedDict in gSettings for the old role hierarchy - include in result.
if lUserTurple in inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] and "RoleHierarchyAllowedDict" in inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"][lUserTurple]:
lRoleHierarchyAllowedOLDDict = inGSettings["ServerDict"]["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["ServerDict"]["AccessUsers"]["RuleDomainUserDict"].update({(inADStr.upper(),inADLoginStr.upper()):lRuleDomainUserDict})
if inADIsDefaultBool:
# Case add default domain + user
inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"].update({("",inADLoginStr.upper()):lRuleDomainUserDict})
def UACSuperTokenUpdate(inGSettings, inSuperTokenStr):
"""
Add supertoken for the all access (it is need for the robot communication without human)
:param inGSettings: Global settings dict (singleton)
:param inSuperTokenStr:
"""
lLoginStr = "SUPERTOKEN"
UACUpdate(inGSettings=inGSettings, inADLoginStr=lLoginStr)
inGSettings["ServerDict"]["AccessUsers"]["AuthTokensDict"].update(
{inSuperTokenStr:{"User":lLoginStr, "Domain":"", "TokenDatetime": datetime.datetime.now(), "FlagDoNotExpire":True}}
)
# # # # # # # # # # # # # # # # # # # # # # #
# OrchestratorWeb defs
# # # # # # # # # # # # # # # # # # # # # # #
def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream"):
"""
Connect URL to DEF
"inMethodStr":"GET|POST",
"inURLStr": "/index", #URL of the request
"inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase",
"inContentTypeStr": "", #HTTP Content-type
"inDef": None #Function with str result
:param inGSettings: Global settings dict (singleton)
:param inMethodStr:
:param inURLStr:
:param inMatchTypeStr:
:param inDef:
:param inContentTypeStr:
"""
lURLItemDict = {
"Method": inMethodStr.upper(),
"URL": inURLStr, # URL of the request
"MatchType": inMatchTypeStr, # "BeginWith|Contains|Equal|EqualCase",
# "ResponseFilePath": "", #Absolute or relative path
#"ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings",
# Absolute or relative path
"ResponseContentType": inContentTypeStr, #HTTP Content-type
"ResponseDefRequestGlobal": inDef #Function with str result
}
inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
def WebURLConnectFolder(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr):
"""
Connect URL to Folder
"inMethodStr":"GET|POST",
"inURLStr": "/Folder/", #URL of the request
"inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase",
"inFolderPathStr": "", #Absolute or relative path
:param inGSettings: Global settings dict (singleton)
:param inMethodStr:
:param inURLStr:
:param inMatchTypeStr:
:param inFolderPathStr:
"""
# Check if last symbol is "/" - append if not exist
lFolderPathStr = os.path.abspath(inFolderPathStr)
if lFolderPathStr[-1]!="/":lFolderPathStr+="/"
# Prepare URLItem
lURLItemDict = {
"Method": inMethodStr.upper(),
"URL": inURLStr, # URL of the request
"MatchType": inMatchTypeStr, # "BeginWith|Contains|Equal|EqualCase",
# "ResponseFilePath": "", #Absolute or relative path
"ResponseFolderPath": lFolderPathStr, # Absolute or relative path
"ResponseContentType": "application/octet-stream", #HTTP Content-type
#"ResponseDefRequestGlobal": inDef #Function with str result
}
inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream"):
"""
Connect URL to File
"inMethodStr":"GET|POST",
"inURLStr": "/index", #URL of the request
"inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase",
"inFolderPathStr": "", #Absolute or relative path
:param inGSettings: Global settings dict (singleton)
:param inMethodStr:
:param inURLStr:
:param inMatchTypeStr:
:param inFilePathStr:
:param inContentTypeStr:
"""
lURLItemDict = {
"Method": inMethodStr.upper(),
"URL": inURLStr, # URL of the request
"MatchType": inMatchTypeStr, # "BeginWith|Contains|Equal|EqualCase",
"ResponseFilePath": os.path.abspath(inFilePathStr), #Absolute or relative path
#"ResponseFolderPath": os.path.abspath(inFilePathStr), # Absolute or relative path
"ResponseContentType": inContentTypeStr, #HTTP Content-type
#"ResponseDefRequestGlobal": inDef #Function with str result
}
inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None):
"""
Add control panel HTML, JSON generator or JS when page init
:param inGSettings: Global settings dict (singleton)
:param inCPKeyStr:
:param inHTMLRenderDef:
:param inJSONGeneratorDef:
:param inJSInitGeneratorDef:
"""
# 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
def WebUserInfoGet(inRequest):
"""
Return User info about request
:param inRequest:
:return: {"DomainUpperStr": "", "UserNameUpperStr": ""}
"""
lDomainUpperStr = inRequest.OpenRPA["Domain"].upper()
lUserUpperStr = inRequest.OpenRPA["User"].upper()
return {"DomainUpperStr": lDomainUpperStr, "UserNameUpperStr": lUserUpperStr}
def WebUserIsSuperToken(inRequest, inGSettings):
"""
Return bool if request is authentificated with supetoken (token which is never expires)
:param inRequest:
:param inGSettings: Global settings dict (singleton)
:return: bool True - is supertoken; False - is not supertoken
"""
lIsSuperTokenBool = False
# Get Flag is supertoken (True|False)
lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inRequest.OpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False)
return lIsSuperTokenBool
def WebUserUACHierarchyGet(inRequest):
"""
Return User UAC Hierarchy DICT Return {...}
:param inRequest:
:return: UAC Dict {}
"""
return inRequest.UserRoleHierarchyGet()
## GSettings defs
def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None):
"""
Set value in GSettings by the key list
:param inGSettings: Global settings dict (singleton)
:param inValue:
:param inKeyList:
:return: bool
"""
if inKeyList is None: inKeyList = []
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=None):
"""
Get the value from the GSettings by the key list
:param inGSettings: Global settings dict (singleton)
:param inKeyList:
:return: value any type
"""
if inKeyList is None: inKeyList = []
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=None):
"""
Append value in GSettings by the key list
.. code-block:: python
# USAGE
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueAppend(
inGSettings = gSettings,
inValue = "NewValue",
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue"
# ]
# }
#}
:param inGSettings: Global settings dict (singleton)
:param inValue: Any value to be appended in gSettings Dict by the key list
:param inKeyList: List of the nested keys (see example)
:return: True every time
"""
if inKeyList is None: inKeyList = []
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=None):
"""
Execute plus operation between 2 lists (1:inValue and 2:gSettings by the inKeyList)
.. code-block:: python
# USAGE
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueOperatorPlus(
inGSettings = gSettings,
inValue = [1,2,3],
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue",
# 1,
# 2,
# 3
# ]
# }
#}
:param inGSettings: Global settings dict (singleton)
:param inValue: List with values to be merged with list in gSettings
:param inKeyList: List of the nested keys (see example)
:return: True every time
"""
if inKeyList is None: inKeyList = []
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 ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None):
"""
Create alias for def (can be used in ActivityItem in field Def)
!WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment)
.. code-block:: python
# USAGE
from pyOpenRPA import Orchestrator
def TestDef():
pass
lAliasStr = Orchestrator.ProcessorAliasDefCreate(
inGSettings = gSettings,
inDef = TestDef,
inAliasStr="TestDefAlias")
# Now you can call TestDef by the alias from var lAliasStr with help of ActivityItem (key Def = lAliasStr)
:param inGSettings: Global settings dict (singleton)
:param inDef: Def
:param inAliasStr: String alias for associated def
:return: str Alias string (Alias can be regenerated if previous alias was occupied)
"""
#TODO Pay attention - New alias can be used too - need to create more complex algorythm to create new alias!
lL = inGSettings["Logger"]
if inAliasStr is None: inAliasStr = str(inDef)
# Check if key is not exists
if inAliasStr in inGSettings["ProcessorDict"]["AliasDefDict"]:
inAliasStr = str(inDef)
if lL: lL.warning(f"Orchestrator.ProcessorAliasDefCreate: Alias {inAliasStr} already exists in alias dictionary. Another alias will be generated and returned")
inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef
return inAliasStr
def ProcessorAliasDefUpdate(inGSettings, inDef, inAliasStr):
"""
Update alias for def (can be used in ActivityItem in field Def).
!WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment)
.. code-block:: python
# USAGE
from pyOpenRPA import Orchestrator
def TestDef():
pass
Orchestrator.ProcessorAliasDefUpdate(
inGSettings = gSettings,
inDef = TestDef,
inAliasStr="TestDefAlias")
# Now you can call TestDef by the alias "TestDefAlias" with help of ActivityItem (key Def = "TestDefAlias")
:param inGSettings: Global settings dict (singleton)
:param inDef: Def
:param inAliasStr: String alias for associated def
:return: str Alias string
"""
if callable(inDef): inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef
else: raise Exception(f"pyOpenRPA Exception: You can't use Orchestrator.ProcessorAliasDefUpdate with arg 'inDef' string value. inDef is '{inDef}', inAliasStr is '{inAliasStr}'")
return inAliasStr
def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None):
"""
Create ActivityItem
.. code-block:: python
# USAGE
from pyOpenRPA import Orchestrator
# EXAMPLE 1
def TestDef(inArg1Str, inGSettings, inLogger):
pass
lActivityItem = Orchestrator.ProcessorActivityItemCreate(
inDef = TestDef,
inArgList=[],
inArgDict={"inArg1Str": "ArgValueStr"},
inArgGSettingsStr = "inGSettings",
inArgLoggerStr = "inLogger")
# lActivityItem:
# {
# "Def":TestDef,
# "ArgList":inArgList,
# "ArgDict":inArgDict,
# "ArgGSettings": "inArgGSettings",
# "ArgLogger": "inLogger"
# }
# EXAMPLE 2
def TestDef(inArg1Str):
pass
Orchestrator.ProcessorAliasDefUpdate(
inGSettings = gSettings,
inDef = TestDef,
inAliasStr="TestDefAlias")
lActivityItem = Orchestrator.ProcessorActivityItemCreate(
inDef = "TestDefAlias",
inArgList=[],
inArgDict={"inArg1Str": "ArgValueStr"},
inArgGSettingsStr = None,
inArgLoggerStr = None)
# lActivityItem:
# {
# "Def":"TestDefAlias",
# "ArgList":inArgList,
# "ArgDict":inArgDict,
# "ArgGSettings": None,
# "ArgLogger": None
# }
:param inDef: def link or def alias (look gSettings["Processor"]["AliasDefDict"])
:param inArgList: Args list for the Def
:param inArgDict: Args dict for the def
:param inArgGSettingsStr: Name of def argument of the GSettings dict
:param inArgLoggerStr: Name of def argument of the logging object
:return: {}
"""
if inArgList is None: inArgList=[]
if inArgDict is None: inArgDict={}
lActivityItemDict= {
"Def":inDef, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
"ArgList":inArgList, # Args list
"ArgDict":inArgDict, # Args dictionary
"ArgGSettings": inArgGSettingsStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": inArgLoggerStr # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
return lActivityItemDict
def ProcessorActivityItemAppend(inGSettings, inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None):
"""
Add Activity item in Processor list
:param inGSettings: Global settings dict (singleton)
:param inDef:
:param inArgList:
:param inArgDict:
:param inArgGSettingsStr:
:param inArgLoggerStr:
"""
if inArgList is None: inArgList=[]
if inArgDict is None: inArgDict={}
lActivityList=[
{
"Def":inDef, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
"ArgList":inArgList, # Args list
"ArgDict":inArgDict, # Args dictionary
"ArgGSettings": inArgGSettingsStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": inArgLoggerStr # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
]
inGSettings["ProcessorDict"]["ActivityList"]+=lActivityList
## Process defs
def ProcessIsStarted(inProcessNameWOExeStr): # Check if process is started
"""
Check if there is any running process that contains the given name processName.
:param inProcessNameWOExeStr:
:return: True - process is running; False - process is not running
"""
#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]
:param inPathStr:
:param inArgList:
:param inStopProcessNameWOExeStr:
"""
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
:param inProcessNameWOExeStr:
:param inCloseForceBool:
:param inUserNameStr:
:return:
"""
# 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)
def ProcessListGet(inProcessNameWOExeList=None):
"""
Check activity of the list of processes
:param inProcessNameWOExeList:
:return: TODO FILL THE RESULT DICT
"""
if inProcessNameWOExeList is None: 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=None, inArgDict=None, inLogger = None):
"""
Python import module and start def
:param inModulePathStr:
:param inDefNameStr:
:param inArgList:
:param inArgDict:
:param inLogger:
:return:
"""
if inArgList is None: inArgList=[]
if inArgDict is None: inArgDict={}
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")
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# Scheduler
# # # # # # # # # # # # # # # # # # # # # # #
def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekdayList=None, inActivityList=None):
"""
Add activity in time weekly
:param inGSettings: Global settings dict (singleton)
:param inTimeHHMMStr:
:param inWeekdayList:
:param inActivityList:
:return:
"""
if inWeekdayList is None: inWeekdayList=[]
if inActivityList is None: inActivityList=[]
Processor.__ActivityListVerify__(inActivityList=inActivityList) # DO VERIFICATION FOR THE inActivityList
lActivityTimeItemDict = {
"TimeHH:MMStr": inTimeHHMMStr, # Time [HH:MM] to trigger activity
"WeekdayList": inWeekdayList, # List of the weekday index when activity is applicable, Default [1,2,3,4,5,6,7]
"ActivityList": inActivityList,
"GUID": None # # Will be filled in Orchestrator automatically - is needed for detect activity completion
}
inGSettings["SchedulerDict"]["ActivityTimeList"].append(lActivityTimeItemDict)
# # # # # # # # # # # # # # # # # # # # # # #
# RDPSession
# # # # # # # # # # # # # # # # # # # # # # #
def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortInt = 3389, inWidthPXInt = 1680, inHeightPXInt = 1050,
inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None):
"""
Create some RDP template dict to use it when connect/reconnect
:param inLoginStr:
:param inPasswordStr:
:param inHostStr:
:param inPortInt:
:param inWidthPXInt:
:param inHeightPXInt:
:param inUseBothMonitorBool:
:param inDepthBitInt:
:param inSharedDriveList:
:return:
"""
if inSharedDriveList is None: inSharedDriveList = ["c"]
lRDPTemplateDict= { # Init the configuration item
"Host": inHostStr, # Host address, example "77.77.22.22"
"Port": str(inPortInt), # RDP Port, example "3389"
"Login": inLoginStr, # Login, example "test"
"Password": inPasswordStr, # Password, example "test"
"Screen": {
"Width": inWidthPXInt, # Width of the remote desktop in pixels, example 1680
"Height": inHeightPXInt, # Height of the remote desktop in pixels, example 1050
# "640x480" or "1680x1050" or "FullScreen". If Resolution not exists set full screen, example
"FlagUseAllMonitors": inUseBothMonitorBool, # True or False, example False
"DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32"
},
"SharedDriveList": inSharedDriveList, # 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
}
return lRDPTemplateDict
# TODO Search dublicates in GSettings RDPlist !
# Return list if dublicates
def RDPSessionDublicatesResolve(inGSettings):
"""
Search dublicates in GSettings RDPlist
!def is developing!
:param inGSettings: Global settings dict (singleton)
:return:
"""
pass
#for lItemKeyStr in inGSettings["RobotRDPActive"]["RDPList"]:
# lItemDict = inGSettings["RobotRDPActive"]["RDPList"][lItemKeyStr]
def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None):
"""
Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if key is exists
2 way of the use
Var 1: inGSettings, inRDPSessionKeyStr, inRDPTemplateDict
Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inRDPTemplateDict:
:param inHostStr:
:param inPortStr:
:param inLoginStr:
:param inPasswordStr:
:return: True every time
"""
# 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, "inRDPTemplateDict":inRDPTemplateDict, "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
# Var 1 - if RDPTemplateDict is input
lRDPConfigurationItem=inRDPTemplateDict
# Var 2 - backward compatibility
if lRDPConfigurationItem is None:
lRDPConfigurationItem = RDPTemplateCreate(inLoginStr=inLoginStr, inPasswordStr=inPasswordStr,
inHostStr=inHostStr, inPortInt = int(inPortStr)) # ATTENTION - dont connect if RDP session is exist
# Start the connect
if inRDPSessionKeyStr not in inGSettings["RobotRDPActive"]["RDPList"]:
inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Add item in RDPList
Connector.Session(lRDPConfigurationItem) # Create the RDP session
Connector.SystemRDPWarningClickOk() # Click all warning messages
else:
if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP session was not created because it is alredy exists in the RDPList. Use RDPSessionReconnect if you want to update RDP configuration.")
return True
def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None):
"""
Disconnect the RDP session
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inBreakTriggerProcessWOExeList:
:return: True every time
"""
if inBreakTriggerProcessWOExeList is None: 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
def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None):
"""
RDP Session reconnect
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inRDPTemplateDict:
:return:
"""
# 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, "inRDPTemplateDict":inRDPTemplateDict }, # 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
# Replace Configuration item if inRDPTemplateDict exists
if inRDPTemplateDict is not None: lRDPConfigurationItem=inRDPTemplateDict
# Add item in RDPList
inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem
# Create the RDP session
Connector.Session(lRDPConfigurationItem)
return True
def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr):
"""
Stop track the RDP session. Current def dont kill RDP session - only stop to track it (it can give )
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:return:
"""
lResult = True
inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList
return lResult
def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None):
"""
Logoff the RDP session
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inBreakTriggerProcessWOExeList:
:return: True - logoff is successful
"""
if inBreakTriggerProcessWOExeList is None: 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
def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr):
"""
Check RDP Session responsibility TODO NEED DEV + TEST
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:return: True every time
"""
# 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
def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True):
"""
Start process if it is not running
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inProcessNameWEXEStr:
:param inFilePathStr:
:param inFlagGetAbsPathBool:
:return:
"""
# 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"):
"""
Send command in RDP session
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inCMDStr:
:param inModeStr: Variants:
"LISTEN" - Get result of the cmd command in result;
"CROSSCHECK" - Check if the command was successufully sent
"RUN" - Run without crosscheck and get clipboard
:return: # OLD > True - CMD was executed successfully
{
"OutStr": <> # Result string
"IsResponsibleBool": True|False # Flag is RDP is responsible - works only when inModeStr = CROSSCHECK
}
"""
lResult = {
"OutStr": None, # Result string
"IsResponsibleBool": False # Flag is RDP is responsible - works only when 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.")
lProcessorActivityDict = {
"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(lProcessorActivityDict)
else:
#lResult = True
# Calculate the session Hex
lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr,{}).get("SessionHex", None)
# Run CMD
if lSessionHex:
lResult = Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=inCMDStr, inModeStr=inModeStr, inLogger=inGSettings["Logger"],
inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr])
return lResult
def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool):
"""
Create CMD str to stop process
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inProcessNameWEXEStr:
:param inFlagForceCloseBool:
:return:
"""
# 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
def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr):
"""
Send file from Host to Session RDP using shared drive in RDP
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inHostFilePathStr:
:param inRDPFilePathStr:
:return:
"""
# 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
def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr):
"""
Receive file from Session RDP to Host using shared drive in RDP
:param inGSettings: Global settings dict (singleton)
:param inRDPSessionKeyStr:
:param inRDPFilePathStr:
:param inHostFilePathStr:
:return:
"""
# 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
# # # # # # # # # # # # # # # # # # # # # # #
def GSettingsAutocleaner(inGSettings):
"""
Interval gsettings auto cleaner
:param inGSettings: Global settings dict (singleton)
:return:
"""
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
def Orchestrator(inGSettings):
lL = inGSettings["Logger"]
# https://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if not is_admin():
# Re-run the program with admin rights
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
else:
# Code of your program here
#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")
# 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)
# 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
#Инициализация настроечных параметров
lDaemonLoopSeconds=gSettingsDict["SchedulerDict"]["CheckIntervalSecFloat"]
lDaemonActivityLogDict={} #Словарь отработанных активностей, ключ - кортеж (<activityType>, <datetime>, <processPath || processName>, <processArgs>)
lDaemonLastDateTime=datetime.datetime.now()
gSettingsDict["ServerDict"]["WorkingDirectoryPathStr"] = os.getcwd() # Set working directory in g settings
#Инициализация сервера
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
lRobotRDPThreadControlDict = {"ThreadExecuteBool":True} # inThreadControlDict = {"ThreadExecuteBool":True}
lRobotRDPActiveThread = threading.Thread(target= RobotRDPActive.RobotRDPActive, kwargs={"inGSettings":gSettingsDict, "inThreadControlDict":lRobotRDPThreadControlDict})
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.ActivityListExecute(inGSettings=gSettingsDict,inActivityList=BackwardCompatibility.v1_2_0_ProcessorOld2NewActivityDict(lActivityItem))
# Processor thread
lProcessorThread = threading.Thread(target= Processor.ProcessorRunSync, kwargs={"inGSettings":gSettingsDict, "inRobotRDPThreadControlDict":lRobotRDPThreadControlDict})
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:
try:
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["SchedulerDict"]["ActivityTimeList"]):
try:
# 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:MMStr" in lItem:
#Вид активности - запуск процесса
#Сформировать временной штамп, относительно которого надо будет проверять время
#часовой пояс пока не учитываем
lActivityDateTime=datetime.datetime.strptime(lItem["TimeHH:MMStr"],"%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 list is started in new thread. Parameters are not available to see.") #Logging
# Do the activity
lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs={"inGSettings": inGSettings, "inActivityList":lItem["ActivityList"]})
lThread.start()
lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity
except Exception as e:
if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module when activity time item was initialising. ActivityTimeItem is {lItem}")
lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity
#Уснуть до следующего прогона
time.sleep(lDaemonLoopSeconds)
except Exception as e:
if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module. Global error")
# Backward compatibility below to 1.2.0
def __deprecated_orchestrator_start__():
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