AgentActivityItemAdd() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

AgentOSCMD() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

AgentOSFileBinaryDataBase64StrCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

AgentOSFileBinaryDataBytesCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

AgentOSFileTextDataStrCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

GSettingsAutocleaner() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

GSettingsKeyListValueAppend() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

GSettingsKeyListValueGet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

GSettingsKeyListValueOperatorPlus() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

GSettingsKeyListValueSet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

OSCredentialsVerify() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

OrchestratorRestart() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

OrchestratorSessionSave() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessIsStarted() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessListGet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessStart() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessStop() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessorActivityItemAppend() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessorActivityItemCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessorAliasDefCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

ProcessorAliasDefUpdate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

PythonStart() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionCMDRun() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionConnect() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionDisconnect() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionDublicatesResolve() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionFileStoredRecieve() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionFileStoredSend() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionLogoff() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionMonitorStop() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionProcessStartIfNotRunning() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionProcessStop() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionReconnect() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPSessionResponsibilityCheck() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

RDPTemplateCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

SchedulerActivityTimeAddWeekly() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

UACKeyListCheck() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

UACSuperTokenUpdate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebCPUpdate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebURLConnectDef() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebURLConnectFile() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebURLConnectFolder() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebUserInfoGet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebUserIsSuperToken() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

WebUserUACHierarchyGet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)

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

    :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 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 inFilePathStr:
    :param inFileDataBytes:
    """

    lFileDataBase64Str = base64.b64encode(inFileDataBytes).decode("utf-8")
        "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"])
        "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataBase64Str":lFileDataBase64Str}, # Args dictionary
    }

def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str):
    """
    pyOpenRPA.Agent: Create binary file by the base64 string (safe for JSON transmission)
    Send binary file to Agent (base64 string)

    :param inFileDataBase64Str:
    """

        "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataBase64Str":inFileDataBase64Str}, # Args dictionary
    }

# 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 inFileDataStr:
    :param inEncodingStr:
    """

        "Def":"OSFileTextDataStrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"])
        "ArgDict":{"inFilePathStr":inFilePathStr,"inFileDataStr":inFileDataStr, "inEncodingStr": inEncodingStr}, # Args dictionary
    }

# OS DEFS
def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ##
    """
    Verify user credentials in windows. Return bool

    :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 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

    """
    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")

    :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 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 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 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 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
        "inURLStr": "/Folder/", #URL of the request
        "inFolderPathStr": "", #Absolute or relative path

    :param inFolderPathStr:
    """
    # Check if last symbol is "/" - append if not exist
    lFolderPathStr = os.path.abspath(inFolderPathStr)
    if lFolderPathStr[-1]!="/":lFolderPathStr+="/"
    # Prepare URLItem
        "ResponseFolderPath": lFolderPathStr, # Absolute or relative path
        "ResponseContentType": "application/octet-stream", #HTTP Content-type
        #"ResponseDefRequestGlobal": inDef #Function with str result
    }

def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream"):
    """
    Connect URL to File

    """
        "ResponseFilePath": os.path.abspath(inFilePathStr), #Absolute or relative path
        #"ResponseFolderPath": os.path.abspath(inFilePathStr), # Absolute or relative path
    }

def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None):
    """
    Add control panel HTML, JSON generator or JS when page init

    :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

    :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)

    :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 {...}

    :return: UAC Dict {}
    """
    return inRequest.UserRoleHierarchyGet()

## GSettings defs
def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None):
    """
    Set value in GSettings by the key list

    :param inValue:
    :param inKeyList:
    """
    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

def GSettingsKeyListValueGet(inGSettings, inKeyList=None):
    """
    Get the value from the GSettings by the key list

    :return: value any type
    """
            pass
        else:
    return lDict.get(inKeyList[-1],None)

def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None):
    """
    Append value in GSettings by the key list

    """
            pass
        else:
    lDict[inKeyList[-1]].append(inValue) #Set value

def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None):
    """
    Operator plus value in GSettings by the key list

    """
            pass
        else:
    lDict[inKeyList[-1]] += inValue #Set value

def ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None):
    """
    Create alias for def (can be used in ActivityItem in field Def)

    :param inAliasStr:
    :return: str 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)

    """

def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None):
    """
    Create ActivityItem

    :param inArgList:
    :param inArgDict:
    :param inArgGSettingsStr:
    :param inArgLoggerStr:
    :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

    """
    lActivityList=[
        {
        },
    ]
    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 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 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
        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'])
           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:
    :return:
    """
    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 inTimeHHMMStr:
    :param inWeekdayList:
    :param inActivityList:
    :return:
    """
    if inWeekdayList is None: inWeekdayList=[]
    if inActivityList is None: 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 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!

    :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 inRDPSessionKeyStr:
    :param inRDPTemplateDict:
    :param inPortStr:
    """
    # 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.")

def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None):
    """
    Disconnect the RDP session

    :param inBreakTriggerProcessWOExeList:
    """
    if inBreakTriggerProcessWOExeList is None: inBreakTriggerProcessWOExeList = []
        lResult = {
            "Def": RDPSessionDisconnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inBreakTriggerProcessWOExeList": inBreakTriggerProcessWOExeList },  # Args dictionary
        }
        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

def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None):
    """
    RDP Session reconnect

    :return:
    """
        lResult = {
            "Def": RDPSessionReconnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPTemplateDict":inRDPTemplateDict },  # Args dictionary
        }
    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)

def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr):
    """
    Stop track the RDP session. Current def dont kill RDP session - only stop to track it (it can give )

    :return:
    """
    lResult = True
    inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList

def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None):
    """
    Logoff the RDP session

    :return: True - logoff is successful
    """
        lResult = {
            "Def": RDPSessionLogoff, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
        }
    else:
        lCMDStr = "shutdown -L" # CMD logoff command
        # Calculate the session Hex
            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

def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr):
    """
    Check RDP Session responsibility TODO NEED DEV + TEST

    """
        lResult = {
            "Def": RDPSessionResponsibilityCheck, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr },  # Args dictionary
        }
    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

def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True):
    """
    Start process if it is not running

    :param inProcessNameWEXEStr:
    :param inFlagGetAbsPathBool:
    :return:
    """
        lResult = {
            "Def": RDPSessionProcessStartIfNotRunning, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr,  "inProcessNameWEXEStr": inProcessNameWEXEStr, "inFilePathStr": inFilePathStr, "inFlagGetAbsPathBool": inFlagGetAbsPathBool },  # Args dictionary
        }
    else:
        lResult = True
        lCMDStr = CMDStr.ProcessStartIfNotRunning(inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPath= inFlagGetAbsPathBool)
        # Run CMD
            Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"],
                                    inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr])

def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK"):
    """
    Send command in RDP session

    :param inModeStr:
    :return: True - CMD was executed successfully
    """
        lResult = {
            "Def": RDPSessionCMDRun, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr,  "inCMDStr": inCMDStr, "inModeStr": inModeStr },  # Args dictionary
        }
    else:
        # Run CMD
            Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=inCMDStr, inModeStr=inModeStr, inLogger=inGSettings["Logger"],

def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool):
    """
    Create CMD str to stop process

    :param inFlagForceCloseBool:
    :return:
    """
        lResult = {
            "Def": RDPSessionProcessStop, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr,  "inProcessNameWEXEStr": inProcessNameWEXEStr, "inFlagForceCloseBool": inFlagForceCloseBool },  # Args dictionary
        }
    else:
        lCMDStr = f'taskkill /im "{inProcessNameWEXEStr}" /fi "username eq %USERNAME%"'
        if inFlagForceCloseBool:
            lCMDStr+= " /F"
        # Run CMD
            Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr])

def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr):
    """
    Send file from Host to Session RDP using shared drive in RDP

    :param inHostFilePathStr:
    :param inRDPFilePathStr:
    :return:
    """
        lResult = {
            "Def": RDPSessionFileStoredSend, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr,  "inHostFilePathStr": inHostFilePathStr, "inRDPFilePathStr": inRDPFilePathStr },  # Args dictionary
        }
    else:
        lCMDStr = CMDStr.FileStoredSend(inHostFilePath = inHostFilePathStr, inRDPFilePath = inRDPFilePathStr)
        lSessionHex = inGSettings["RobotRDPActive"]["RDPList"].get(inRDPSessionKeyStr, {}).get("SessionHex", None)
        #lSessionHex = inGlobalDict["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]["SessionHex"]
        # Run CMD
            Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="LISTEN", inClipboardTimeoutSec = 120, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr])

def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr):
    """
    Receive file from Session RDP to Host using shared drive in RDP

    :return:
    """
        lResult = {
            "Def": RDPSessionFileStoredRecieve, # def link or def alias (look gSettings["Processor"]["AliasDefDict"])
            "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPFilePathStr": inRDPFilePathStr, "inHostFilePathStr": inHostFilePathStr },  # Args dictionary
        }
    else:
        lCMDStr = CMDStr.FileStoredRecieve(inRDPFilePath = inRDPFilePathStr, inHostFilePath = inHostFilePathStr)
        # Run CMD

# # # # # Start orchestrator

def GSettingsAutocleaner(inGSettings):
    """
    Interval gsettings auto cleaner

    :return:
    """
    while True:
        time.sleep(inGSettings["Autocleaner"]["IntervalSecFloat"])  # Wait for the next iteration
        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):
    # 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 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: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 list is started in new thread. Scheduler item: {lItem}")  #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)
                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