# $language = "Python" # $interface = "1.0" # RunCommandsOnMultipleHostsAndLogResults.py # # Last Modified: 08 Jun, 2021 # - Encourage best practice of prompting for credentials rather # than saving user/pass in plaintext in the script file. # # Last Modified: 21 May, 2018 # - If a commands file cannot be opened/read, complain "nicely" # rather than having the script bomb out with an exception. # - If parent folder specified in the log file template doesn't # exist, try to create it before attempting to write results # or errors. # - If unable to write to a results file or an error log file, # the script will continue and attempt to perform the work it # has been instructed to do -- regardless of ability to log # errors/results. Errors will still be displayed when the # script completes. # - Allow host to be specified as either existing sessions in the # session manager (in which case the existing session is used to # establish the connection), or a hostname/IP which which an ad # hoc connection is made. To force SecureCRT to make an ad hoc # connection even if an existing session is found to match the # host entry specified, set g_bUseExistingSessions = False below. # If a host matches an existing session, and the session has # a saved username and password (or Automate Logon option is # enabled, the script will not send any credentials in the Connect() # function defined below. Instead, it is expected that the session # will authenticate itself using credentials stored within the session. # # - Standardize logging to one function to avoid code duplication and # facilitate consistency with error reporting message format. # # - Ensure that errors are logged to the same file with "All(Errors)" # in the name, even if the template log file name doesn't have the # "IPADDRESS" substitution. # # - Only respond to Password: prompts if they appear as the left-most # item on the screen. # # Last Modified: 17 May, 2018 # - Make it so that the global commands file is not needed if the hosts # file has a host-specific command file specified for all hosts. # # Last Modified: 11 May, 2018 # - Add support for specifying a unique command file specific to each host. # To take advantage of this feature, your hosts.txt file should have this # format: # --------------------------------------------------------------------- # hostname1;commandfileA.txt # hostname2;commandfileA.txt # hostname3;commandfileA.txt # ipaddress1;commandFileB.txt # ipaddress2;commandFileB.txt # hostname4;commandFileB.txt # --------------------------------------------------------------------- # If a host-specific file is not specified for a host, the commands from # the file specified in the global g_strCommandsFile variable are used. # Host-specific command files can be specified as a fully-qualified (AKA # "Absolute") file path OR as a file name, in which case it is required # that the file exist in the same location as the hosts file. # # Last Modified: 08 May, 2018 # - Converted from VBScript to Python as an example. # # - Errors like connection/authentication are now logged to a single # "AllErrors" log file (same base name as the other log file(s)). # # - Default log file name template uses both IPADDRESS and COMMAND. # However, the g_bLogToIndividualFilesPerCommand is set to False, and # the g_bLogToIndividualFilesPerHost is set to True so that one log # file per host will be generated by default. If a single log file for # all hosts and all commands is desired, simply set both: # g_bLogToIndividualFilesPerCommand = False # g_bLogToIndividualFilesPerHost = False # # - Log file names will all share the same time stamp which correlates to # the time at which this script was intially launched, instead of the # time at which each command is run. This will help in collating log # files in terms of instantiations of the script. # # DESCRIPTION: # Demonstrates how to connect to hosts and send commands, logging results of # each command to separate, uniquely-named files based on the host IP and/or # the command that has been sent. Logging behavior can be controlled either # by changing the variable named "g_strLogFileTemplate" (examples shown # below) to add/remove components or by setting one or both of the following # variables to True/False to get the desired logging behavior regardless of # the template log file name: # g_bLogToIndividualFilesPerHost # g_bLogToIndividualFilesPerCommand # # By default, this script will log commands and their results to individual # files - one file per each host. Here are some examples of modifying the # log file template. # Default: Log all commands to separate files based on host: # g_strLogFileTemplate = "{0}/##IPADDRESS--%Y-%m-%d--%H'%M'%S.txt".format( # g_strMyDocs) # --> Note: This is effectively equivalent to setting the following # variables to the values indicated below, WITHOUT modifying the # default g_strLogFileTemplate value: # g_bLogToIndividualFilesPerHost = True # g_bLogToIndividualFilesPerCommand = False # # Log Option #1: Log everything (all hosts and commands) to A SINGLE FILE. # g_strLogFileTemplate = "(0}/##%Y-%m-%d--%H'%M'%S.txt".format( # g_strMyDocs) # --> Note: This is effectively equivalent to setting the following # variables to the values indicated below, WITHOUT modifying the # default g_strLogFileTemplate value: # g_bLogToIndividualFilesPerHost = False # g_bLogToIndividualFilesPerCommand = False # # Log Option #2: Log hosts + commands in SEPARATE FILES; ONE FILE FOR # EACH COMMAND. # g_strLogFileTemplate = "{0}/##IPADDRESS--COMMAND--%Y-%m-%d--%H'%M'%S.txt".format( # g_strMyDocs) # --> Note: This is effectively equivalent to setting the following # variables to the values indicated below, WITHOUT modifying the # default g_strLogFileTemplate value: # g_bLogToIndividualFilesPerHost = True # g_bLogToIndividualFilesPerCommand = True # # # Host information is read from a file named "##hosts.txt" (located in # "Documents" folder) # # Commands to be run on each remote host are read in from a file named # "##commands.txt" (also located in "Documents" folder) # # This script does not interfere with a session's logging settings; instead, # the Screen.ReadString() method is used to capture the output of a # command, and then native python code is used to write the captured data # to a file. # import os, platform, time, re, errno # ----------------------------------------------------------------------------- def GetDocumentsFolder(): objConfig = crt.OpenSessionConfiguration("Default") strOptionName = "Upload Directory V2" strOrigValue = objConfig.GetOption(strOptionName) objConfig.SetOption(strOptionName, "${VDS_USER_DATA_PATH}") objConfig.Save() objConfig = crt.OpenSessionConfiguration("Default") strMyDocs = objConfig.GetOption(strOptionName) objConfig.SetOption(strOptionName, strOrigValue) objConfig.Save() return strMyDocs.replace("\\", "/") g_strMyDocs = GetDocumentsFolder() # ##hosts.txt and ##commands.txt files are located in the current user's # ##MyDocuments folder. Hard-code to different paths as your needs dictate. g_strHostsFile = g_strMyDocs + "/##hosts.txt" g_strCommandsFile = g_strMyDocs + "/##commands.txt" # Template used for formulating the name of the results file in which the # command output will be saved. You can choose to arrange the various # components (i.e. "IPADDRESS", "COMMAND", "%Y", "%m", "%d", "%H", etc.) of # the log file name into whatever order you want it to be. g_strLogFileTemplate = (g_strMyDocs + "/Log/##IPADDRESS-COMMAND-%Y-%m-%d--%H'%M'%S.txt") g_bLogToIndividualFilesPerHost = True g_bLogToIndividualFilesPerCommand = False # Add Time stats to the log file name based on the Template # defined by the script author. We do this here at the top # of the script so that even when logging to multiple hosts # in separate files, the time stamps will all show up the # same for easy sorting/grouping in your file explorer/finder. g_strLogFileTemplate = time.strftime(g_strLogFileTemplate, time.localtime()) # Comment character allows for comments to exist in either the ##host.txt or # ##commands.txt files. Lines beginning with this character will be ignored. g_strComment = "#" g_vHosts = [] g_vCommands = [] g_nHostCount = 0 # If connecting through a proxy is required, comment out the second statement # below, and modify the first statement below to match the name of the firewall # through which you're connecting (as defined in global options within # SecureCRT) g_strFirewall = " /FIREWALL=myFireWallName " g_strFirewall = "" # Username for authenticating to the remote system g_strUsername = "" # Password for authenticating to the remote system g_strPassword = "" # Global variable for housing details of any errors that might be encountered global g_strError global g_objNewTab global g_strHost global g_vDefaultCommands, g_nDefaultCommandCount, g_bDefaultCommandsFileExists g_vDefaultCommands = [] g_nDefaultCommandCount = 0 g_bDefaultCommandsFileExists = False global g_bUseExistingSessions g_bUseExistingSessions = False # ----------------------------------------------------------------------------- def MainSub(): # Prompt for username and password to be used for authentication # to all devices. If you don't want to be prompted for this info, you # will need to define the values for g_strUsername and g_strPassword # earlier in this script. However, be advised that storing user and # password information in plaintext is not a security best practice. global g_strUsername, g_strPassword if g_strUsername == "": g_strUsername = crt.Dialog.Prompt( "Enter the user account name that will be " + "used for authentication to each device.", "Username?") if g_strUsername == "": return if g_strPassword == "": g_strPassword = crt.Dialog.Prompt( "Enter the password for the '{}' account.".format( g_strUsername), "Password?", "", True) if g_strPassword == "": return # Create arrays in which lines from the hosts and commands files will be # stored. global g_vHosts, g_vCommands, g_nDefaultCommandCount, g_vDefaultCommands # Create variables for storing information about the lines read from the # file g_nHostCount = 0 nCommandCount = 0 global g_strHostsFile, g_strComment, g_strHost, g_objNewTab, g_strError # Call the ReadDataFromFile() function defined in this script. It will # read in the hosts file and populate an array with non-comment lines that # will represent the hosts to which connections will be made later on. vReturnValsHostsFile = ReadDataFromFile(g_strHostsFile, g_strComment) if not vReturnValsHostsFile[0]: DisplayMessage("No hosts were found in file: \r\n " + g_strHostsFile) return g_vHosts = vReturnValsHostsFile[1] g_nHostCount = vReturnValsHostsFile[2] # crt.Dialog.MessageBox("Read in {0} hosts".format(g_nHostCount)) strErrors = "" strSuccesses = "" # If the g_strCommandsFile path exists, load those commands into a # global array and populate global variables for use when a host needs # to send commands from this file. If a line in the hosts file contains # a host-specific commands file, this script will load those as needed. # Call the ReadDataFromFile() function to populate the array of commands # that will be sent for this host. if os.path.isfile(g_strCommandsFile): g_bDefaultCommandsFileExists = True vReturnValsCmdsFile = ReadDataFromFile(g_strCommandsFile, g_strComment) if not vReturnValsCmdsFile[0]: strError = ( "WARNING: Default commands file does not contain any " + "commands: " + g_strCommandsFile + ".\r\n" + "Any lines in the hosts file which don't include a " + "commands file specification will fail.") strErrors = CaptureError(strErrors, strError) else: g_vDefaultCommands = vReturnValsCmdsFile[1] g_nDefaultCommandCount = vReturnValsCmdsFile[2] else: g_bDefaultCommandsFileExists = False # Before attempting any connections, ensure that the "Auth Prompts In # Window" option in the Default session is already enabled. If not, prompt # the user to have the script enable it automatically before continuing. # Before continuing on with the script, ensure that the session option # for handling authentication within the terminal window is enabled objConfig = crt.OpenSessionConfiguration("Default") bAuthInTerminal = objConfig.GetOption("Auth Prompts In Window") if not bAuthInTerminal: strMessage = ("" + "The 'Default' session (used for all ad hoc " + "connections) does not have the 'Display logon prompts in " + "terminal window' option enabled, which is required for this " + "script to operate successfully.\r\n\r\n") if not PromptYesNo( strMessage + "Would you like to have this script automatically enable this " + "option in the 'Default' session so that next time you run " + "this script, the option will already be enabled?"): return # User answered prompt with Yes, so let's set the option and save objConfig.SetOption("Auth Prompts In Window", True) objConfig.Save() # Iterate through each element of our g_vHosts array... for nIndex in range(0, g_nHostCount): # Arrays are indexed starting at 0 (zero) # Store the current host in a variable so we don't have to remember # what "g_vHosts(nIndex)" means. g_strHost = g_vHosts[nIndex] # Exit the loop if the host name is empty (this means we've # reached the end of our array if g_strHost == "": return strCommandsFilename = "" bContinue = True if ";" in g_strHost: vHostElems = g_strHost.split(";") g_strHost = vHostElems[0] strCommandsFilename = vHostElems[1] if strCommandsFilename == "": strCommandsFilename = g_strCommandsFile strCommandsFilenameOrig = strCommandsFilename if not os.path.isfile(strCommandsFilename): # Check if the file path is relative to where hosts.txt # lives strCommandsFilename = os.path.join( os.path.dirname(os.path.normpath(g_strHostsFile)), strCommandsFilename) if not os.path.isfile(strCommandsFilename): strError = ( "Host-specific command file not found for host '" + g_strHost + "': " + strCommandsFilenameOrig + " (" + strCommandsFilename + ")") strErrors = CaptureError(strErrors, strError) bContinue = False if bContinue: g_strError = "" # Now call the ReadDataFromFile() function for the commands file. vReturnValsCmdsFile = ReadDataFromFile(strCommandsFilename, g_strComment) g_vCommands = vReturnValsCmdsFile[1] nCommandCount = vReturnValsCmdsFile[2] if not vReturnValsCmdsFile[0]: strError = ( "Error attempting to read host-specific file for host '" + g_strHost + "': " + g_strError) strErrors = CaptureError(strErrors, strError) bContinue = False else: # If we've had any host-specific command file change, we'll need # to use the common commands file to avoid running the last-known # host-specific commands with this new host that doesn't have a # commands file specified; use the default commands file. # Call the ReadDataFromFile() function to populate the array of commands # that will be sent for this host. if not g_bDefaultCommandsFileExists: strError = ( "While working on host '" + g_strHost + "... could not send any commands; " + "default commands file was not found. Check the " + "g_strCommandsFile variable; it should point to " + "a valid commands file. Alternatively, edit " + "your hosts file and make sure it contains a " + "host-specific commands file.") strErrors = CaptureError(strErrors, strError) bContinue = False elif g_nDefaultCommandCount == 0: strError = ( "While working on host '" + g_strHost + "... could not send any commands; default " + "commands file is empty.") strErrors = CaptureError(strErrors, strError) bContinue = False else: # We have a valid default commands file and it has commands # in it. Let's use it. g_vCommands = g_vDefaultCommands nCommandCount = g_nDefaultCommandCount if bContinue: # Build up a string containing connection information and options. # /ACCEPTHOSTKEYS should only be used if you suspect that there might be # hosts in the hosts.txt file to which you haven't connected before, and # therefore SecureCRT hasn't saved out the SSH2 server's host key. If # you are confused about what a host key is, please read through the # white paper: # https://www.vandyke.com/solutions/host_keys/index.html # # A best practice would be to connect manually to each device, # verifying each server's hostkey individually before running this # script. # # If you want to authenticate with publickey authentication instead of # password, in the assignment of strConnectString below, replace: # " /AUTH password,keyboard-interactive /PASSWORD " + g_strPassword + _ # with: # " /AUTH publickey /I \"full_path_to_private_key_file\" " + _ if g_bUseExistingSessions and SessionExists(g_strHost): strConnectString = ("/S {0}".format(g_strHost)) else: strConnectString = ("" + g_strFirewall + " /SSH2 " + " /L " + g_strUsername + " /AUTH password,keyboard-interactive /PASSWORD " + g_strPassword + " " + g_strHost) #crt.Dialog.MessageBox(strConnectString) # Call the Connect() function defined below in this script. It handles # the connection process, returning success/fail. if not Connect(strConnectString): strError = "Failed to connect to " + g_strHost + ": " + g_strError strErrors = CaptureError(strErrors, strError) else: # If we get to this point in the script, we're connected (including # authentication) to a remote host successfully. g_objNewTab.Screen.Synchronous = True g_objNewTab.Screen.IgnoreEscape = True # Once the screen contents have stopped changing (polling every # 350 milliseconds), we'll assume it's safe to start interacting # with the remote system. if not WaitForScreenContentsToStopChanging(250): strError = ("Error: " + "Failed to detect remote ready status for host: " + "{0}. {1}".format(g_strHost, g_strError)) strErrors = CaptureError(strErrors, strError) else: # Get the shell prompt so that we can know what to look for when # determining if the command is completed. Won't work if the # prompt is dynamic (e.g. changes according to current working # folder, etc) nRow = g_objNewTab.Screen.CurrentRow strPrompt = g_objNewTab.Screen.Get( nRow, 0, nRow, g_objNewTab.Screen.CurrentColumn - 1) strPrompt = strPrompt.strip(" \r\n") # crt.Dialog.MessageBox("Here is the prompt: {0}".format(strPrompt)) # Send each command one-by-one to the remote system: for strCommand in g_vCommands: if strCommand == "": break # Send the command text to the remote # crt.Dialog.MessageBox("About to send cmd: {0}".format(str(strCommand))) g_objNewTab.Screen.Send("{0}\r".format(strCommand)) # Wait for the command to be echo'd back to us. g_objNewTab.Screen.WaitForString(strCommand) # Since we don't know if we're connecting to a cisco switch or a # linux box or whatever, let's look for either a Carriage Return # (CR) or a Line Feed (LF) character in any order. vWaitFors = ["\r", "\n"] bFoundEOLMarker = False while True: # Call WaitForStrings, passing in the array of possible # matches. g_objNewTab.Screen.WaitForStrings(vWaitFors, 1) # Determine what to do based on what was found) nMatchIndex = g_objNewTab.Screen.MatchIndex if nMatchIndex == 0: # Timed out break if nMatchIndex in [1,2]: # found either CR or LF # Check to see if we've already seen the other # EOL Marker if bFoundEOLMarker: break # If this is the first time we've been through # here, indicate as much, and then loop back up # to the top and try to find the other EOL # marker. bFoundEOLMarker = True # Now that we know the command has been sent to the remote # system, we'll begin the process of capturing the output of # the command. strResult = "" # Use the ReadString() method to get the text displayed # while the command was runnning. Note that the ReadString # usage shown below is not documented properly in SecureCRT # help files included in SecureCRT versions prior to 6.0 # Official. Note also that the ReadString() method captures # escape sequences sent from the remote machine as well as # displayed text. As mentioned earlier in comments above, # if you want to suppress escape sequences from being # captured, set the Screen.IgnoreEscape property = True. strResult = g_objNewTab.Screen.ReadString(strPrompt) # Set the log file name based on the remote host's IP # address and the command we're currently running. We also # add a date/timestamp to help make each filename unique # over time. strLogFile = g_strLogFileTemplate.replace( "IPADDRESS", g_objNewTab.Session.RemoteAddress) if g_bLogToIndividualFilesPerHost: strLogFile = g_strLogFileTemplate if "IPADDRESS" in g_strLogFileTemplate: strLogFile = g_strLogFileTemplate.replace( "IPADDRESS", g_objNewTab.Session.RemoteAddress) else: strLogFile = os.path.join( os.path.dirname(g_strLogFileTemplate), g_objNewTab.Session.RemoteAddress + os.path.basename(g_strLogFileTemplate)) else: if "IPADDRESS" in g_strLogFileTemplate: strLogFile = g_strLogFileTemplate.replace( "IPADDRESS", "ALLHOSTS") else: strLogFile = g_strLogFileTemplate if not "ALLHOSTS" in g_strLogFileTemplate: strLogFile = os.path.join( os.path.dirname(g_strLogFileTemplate), "ALLHOSTS_" + os.path.basename(g_strLogFileTemplate)) if "COMMAND" in strLogFile: if g_bLogToIndividualFilesPerCommand: # Replace any illegal characters that might have been # introduced by the command we're running (e.g. if the # command had a path or a pipe in it) strCleanCmd = strCommand.replace('/', "[SLASH]") strCleanCmd = strCleanCmd.replace('\\', "[BKSLASH]") strCleanCmd = strCleanCmd.replace(':', "[COLON]") strCleanCmd = strCleanCmd.replace( '*', "[STAR]") strCleanCmd = strCleanCmd.replace( '?', "[QUESTION]") strCleanCmd = strCleanCmd.replace( '"', "[QUOTE]") strCleanCmd = strCleanCmd.replace( '<', "[LT]") strCleanCmd = strCleanCmd.replace( '>', "[GT]") strCleanCmd = strCleanCmd.replace( '|', "[PIPE]") strLogFile = strLogFile.replace("COMMAND", strCleanCmd) else: strLogFile = strLogFile.replace("COMMAND", "ALLCMDS") if not os.path.isdir(os.path.dirname(strLogFile)): try: os.makedirs(os.path.dirname(strLogFile)) except Exception as objInst: if objInst.errno != errno.EEXIST: strErrors = CaptureError(strErrors, "Failed to create directory " + "structure for output files: " + str(objInst)) try: with open(strLogFile, "a") as objFile: # If you do not want the command logged along with the results, # command out the following script statement: objFile.write( "=" * 80 + "\n" + "Results of command " + "'{0}' sent to host '{1}':\n".format(strCommand, g_strHost) + "-" * 80) # Write out the results of the command and a separator objFile.write("\n{0}\n".format(strResult.replace("\r\n", "\n"))) except Exception as objInst: strErrors = CaptureError(strErrors, "Failed to open file for " + "writing results of command '" + strCommand + "': " + str(objInst)) # Now disconnect from the current machine before connecting to # the next machine while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) strSuccesses += "\n{0}".format(g_strHost) # End of WaitForScreenContentsToStopChanging() # End of Connect() # End of if bContinue # End of 'for nIndex in range(...)' strMsg = "Commands were sent to the following hosts:\n{0}".format( strSuccesses) if not strErrors == "": strMsg += ("\n\nErrors were encountered connecting to " + "these hosts:\n{0}".format(strErrors)) DisplayMessage(strMsg) crt.Session.SetStatusText("") if not strErrors == "": if crt.Dialog.MessageBox( "Copy these errors to the clipboard?", "Copy to clipboard?", 4) == 6: crt.Clipboard.Text = strErrors # End of def MainSub() # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def ReadDataFromFile(strFile, strComment): # strFile: IN parameter specifying full path to data file. # strComment: IN parameter specifying string that preceded # by 0 or more space characters will indicate # that the line should be ignored. # Return value: # Returns an array where the elements of the array have the following # meanings. # vParams[0]: OUT parameter indicating success/failure of this # function # vParams[1]: OUT parameter (destructive) containing array # of lines read in from file. # vParams[2]: OUT parameter integer indicating number of lines read # in from file. # vParams[3]: OUT parameter indicating number of comment/blank lines # found in the file global g_strError strFile = strFile.replace("\\", "/") # Set up return values # Start of with an empty list/array: vLines = [] nLineCount = 0 nCommentLines = 0 vOutParams = [False, vLines, nLineCount, nCommentLines] # Check to see if the file exists... if not, bail early. if not os.path.exists(strFile): DisplayMessage("File not found: {0}".format(strFile)) g_strError = "File not found: {0}".format(strFile) return vOutParams # Used for detecting comment lines, a regular expression object p = re.compile("(^[ \\t]*(?:{0})+.*$)|(^[ \\t]+$)|(^$)".format(strComment)) try: # Open a TextStream Object to the file... objFile = open(strFile, 'r') except Exception as objInst: g_strError = "Unable to open '{0}' for reading: {1}".format( strFile, str(objInst)) return vOutParams # Read in just the first byte so that we can tell if the encoding isn't right. # better to display a warning than to have things go awry and not really know # why... try: b = str(objFile.read(1)) except Exception as objInst: g_strError = "Unable to read data from file '{0}': {1}".format( strFile, strObjInst) if len(b) < 1: # if the file is empty... g_strError = "File is empty: {0}".format(strFile) return vOutParams if ord(b) == 239: objFile.close() strMsg = "UTF-8 format is not supported. File must be saved in ANSI format:\r\n{0}".format(strFile) DisplayMessage(strMsg) g_strError = strMsg return vOutParams elif ord(b) == 255 or ord(b) == 254: objFile.close() strMsg = "Unicode format is not supported. File must be saved in ANSI format:\r\n{0}".format(strFile) DisplayMessage(strMsg) g_strError = strMsg return vOutParams else: # We know we're likely an ANSI encoded file... close the file # and re-open so that we don't lose the first byte objFile.close() objFile = open(strFile, 'r') for strLine in objFile: strLine = strLine.strip(' \r\n') # Look for comment lines that match the pattern # [whitespace][strComment] if p.match(strLine): # Line matches our comment pattern... ignore it nCommentLines += 1 else: vLines.append(strLine) nLineCount += 1 # Check to make sure we actually have commands to run if nLineCount < 1: vOutParams = [False, vLines, nLineCount, nCommentLines] g_strError = 'No valid lines found in file: {0}'.format(strFile) return vOutParams # crt.Dialog.MessageBox("Read in {0} lines from file: {1}".format(nLineCount, strFile)) vOutParams = [True, vLines, nLineCount, nCommentLines] return vOutParams # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def Connect(strConnectInfo): # Connect in a new tab to the host specified global g_objNewTab, g_strError, g_strHost, g_strUsername bWaitForAuthToCompleteBeforeReturning = False bLetCallerDetectAndHandleConnectionErrors = True try: crt.Session.SetStatusText("Connecting to {}...".format( g_strHost)) g_objNewTab = crt.Session.ConnectInTab( strConnectInfo, bWaitForAuthToCompleteBeforeReturning, bLetCallerDetectAndHandleConnectionErrors) except Exception as objInst: crt.Session.SetStatusText("Failed to connect to {}".format( g_strHost)) g_strError = "Connection attempt raised an exception.\r\n{0}".format( str(objInst)) return False if not g_objNewTab.Session.Connected: if crt.GetLastErrorMessage() == "": g_strError = "Unknown error" #crt.Dialog.MessageBox(g_strError) else: g_strError = str(crt.GetLastErrorMessage()) #crt.Dialog.MessageBox(g_strError) # You're not allowed to close the script tab (the tab in which the # script was launched oringinally), so only try if the new tab really # was a new tab -- not just reusing a disconnected tab. if not g_objNewTab.Index == crt.GetScriptTab().Index: g_objNewTab.Close() return False # Make sure the new tab is "Synchronous" so we can properly wait/send/etc. g_objNewTab.Screen.Synchronous = True # Handle authentication in the new tab using the new tab's object reference # instead of 'crt' nAuthTimeout = 10 # seconds # Modify the "$", the "]#", and/or the "->" in the array below to reflect # the variety of legitimate shell prompts you would expect to see when # authentication is successful to one of your remote machines. vPossibleShellPrompts = [ "ogin:", "name:", "sword:", "Login incorrect", "authentication failed.", "$", "#", ">"] bSendPassword = True bSendUsername = True if SessionExists(g_strHost): objConfig = crt.OpenSessionConfiguration(g_strHost) if objConfig.GetOption("Use Login Script") == True: bSendUsername = False bSendPassword = False if objConfig.GetOption("Session Password Saved") == True: bSendPassword = False if not objConfig.GetOption("Username") == "": bSendUsername = False while True: try: g_objNewTab.Screen.WaitForStrings(vPossibleShellPrompts, nAuthTimeout) # This Select..Case statement represents somewhat of a "state machine" # in which the value of Screen.MatchIndex represents the index of the # array of strings we told WaitForStrings() to look for. Based on this # index, we know what action needs to be performed next. if g_objNewTab.Screen.MatchIndex == 0: # ...time out condition... g_strError = ("Authentication timed out!\r\n" + "(Or you forgot to add a case for a successful shell " + "prompt in the vPossibleShellPrompts array)") # Disconnect from the host so that we can reuse the disconnected # tab for the next connection in the loop while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) #crt.Dialog.MessageBox(g_strError) return False elif g_objNewTab.Screen.MatchIndex == 1: if not bSendUsername: continue #... "ogin: "... # Send the username only if it makes sense based on # the current position of the cursor: if g_objNewTab.Screen.CurrentColumn <= len("Login: "): g_objNewTab.Screen.Send(g_strUsername + "\r") elif g_objNewTab.Screen.MatchIndex == 2: if not bSendUsername: continue # ..."name:"... # Send the username, but only if it makes sense based on # the current position of the cursor: if g_objNewTab.Screen.CurrentColumn <= len("Username: "): g_objNewTab.Screen.Send(g_strUsername + "\r") elif g_objNewTab.Screen.MatchIndex == 3: if not bSendPassword: continue # ..."sword:"... # Send the password if g_objNewTab.Screen.CurrentColumn <= len("Password: "): g_objNewTab.Screen.Send(g_strPassword + "\r") elif (g_objNewTab.Screen.MatchIndex == 4 or g_objNewTab.Screen.MatchIndex == 5): # ..."Login incorrect", "authentication failed."... g_strError = ( "Password authentication to '" + g_strHost + "' as user '" + g_strUsername + "' failed.\r\n\r\n" + "Please specify the correct password for user " + "'" + g_strUsername + "'") # Disconnect from the host so that we can reuse the disconnected # tab for the next connection in the loop while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) #crt.Dialog.MessageBox(g_strError) return False elif (g_objNewTab.Screen.MatchIndex == 6 or g_objNewTab.Screen.MatchIndex == 7 or g_objNewTab.Screen.MatchIndex == 8): #...6,7,8 # "$", "#", or ">" <-- Shell prompt means auth success... g_objNewTab.Session.SetStatusText("Connected to " + g_strHost + " as " + g_strUsername) break else: g_strError = ("Ooops! Looks like you forgot to add code " + "to handle the '{0}' prompt (vPossibleShellPrompts[{1}]).".format( vPossibleShellPrompts[g_objNewTab.Screen.MatchIndex - 1], g_objNewTab.Screen.MatchIndex - 1) + "\r\n" + "\r\n" + "Modify your script code's 'Connect()' function " + "to have a statment like this for the corresponding " + "shell prompt:\n\n" + "elif g_objNewTab.Screen.MatchIndex == " + "{0}:".format(g_objNewTab.Screen.MatchIndex) + "\n # put your code to handle this case here" + "\n # ." + "\n # ." + "\n # .") #crt.Dialog.MessageBox(g_strError) while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) return False except Exception as objInst: # Most likely if there was a problem here, it would have been caused by # an unexpected disconnect occurring while WaitForStrings() was running # as called above. If error, set the global description variable and # then exit the function. g_strError = "{0}\n{1}".format(str(crt.GetLastErrorMessage()), str(objInst)) # Ensure that the session is disconnected before we exit this # function. If there are subsequent hosts to loop through, we don't # want a connected tab interfering with the next host's connection # attempts while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) return False # If the code gets here, then we must have been successful connecting and # authenticating to the remote machine; return the value of True # for the Connect() function. return True # ----------------------------------------------------------------------------- def CaptureError(strErrors, strError): global g_strLogFileTemplate if strErrors == "": strErrors = strError else: strErrors = "{0}\r\n{1}".format(strErrors, strError) strLogFile = g_strLogFileTemplate if "IPADDRESS" in strLogFile: strLogFile = strLogFile.replace("IPADDRESS", "All(Errors)") else: strLogFile = os.path.join(os.path.dirname(strLogFile), "All(Errors)" + os.path.basename(strLogFile)) strLogFile = strLogFile.replace("COMMAND", "") if not os.path.isdir(os.path.dirname(strLogFile)): try: os.makedirs(os.path.dirname(strLogFile)) except Exception as objInst: if objInst.errno != errno.EEXIST: if not "Failed to create" in strError: strErrors += ("\r\nFailed to create directory " + "structure for log file: " + str(objInst)) try: with open(strLogFile, "a") as objFile: objFile.write("=" * 80) objFile.write("\r\n{0}\r\n".format(strError)) except Exception as objInst: if not ( "Failed to create" in strError or "Failed to open" in strError): strErrors += "\r\nFailed to open file for writing: {1}".format( strLogFile, str(objInst)) return strErrors # ----------------------------------------------------------------------------- def WaitForScreenContentsToStopChanging(nMsDataReceiveWindow): # This function relies on new data received being different from the # data that was already received. It won't work if, as one example, you # have a screenful of 'A's and more 'A's arrive (because one screen # "capture" will look exactly like the previous screen "capture"). global g_objNewTab # Store Synch flag for later restoration bOrig = g_objNewTab.Screen.Synchronous # Turn Synch off since speed is of the essence; we'll turn it back on (if # it was already on) at the end of this function g_objNewTab.Screen.Synchronous = False # Be "safe" about trying to access Screen.Get(). If for any reason we # get disconnected, we don't want the script to error out on the problem # so we'll just return false and handle writing something to our log # file for this host. try: strLastScreen = g_objNewTab.Screen.Get(1,1,g_objNewTab.Screen.Rows,g_objNewTab.Screen.Columns) while True: crt.Sleep(nMsDataReceiveWindow) # Be "safe" about trying to access Screen.Get(). If for any reason we # get disconnected, we don't want the script to error out on the problem # so we'll just return false and handle writing something to our log # file for this host. strNewScreen = g_objNewTab.Screen.Get(1,1,g_objNewTab.Screen.Rows, g_objNewTab.Screen.Columns) if strNewScreen == strLastScreen: break strLastScreen = strNewScreen # Restore the Synch setting g_objNewTab.Screen.Synchronous = bOrig return True except Exception as objInst: # Most likely if there was a problem here, it would have been caused by # an unexpected disconnect occurring while WaitForStrings() was running # as called above. If error, set the global description variable and # then exit the function. g_strError = crt.GetLastErrorMessage() # Ensure that the session is disconnected before we exit this # function. If there are subsequent hosts to loop through, we don't # want a connected tab interfering with the next host's connection # attempts while g_objNewTab.Session.Connected: g_objNewTab.Session.Disconnect() crt.Sleep(100) g_objNewTab.Screen.Synchronous = bOrig return False # ----------------------------------------------------------------------------- def PromptYesNo(strText): vbYesNo = 4 return crt.Dialog.MessageBox(strText, "SecureCRT", vbYesNo) # ----------------------------------------------------------------------------- def DisplayMessage(strText): crt.Dialog.MessageBox(strText) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def SessionExists(strSessionPath): # Returns True if a session specified as value for strSessionPath already # exists within the SecureCRT configuration. # Returns False otherwise. try: objTosserConfig = crt.OpenSessionConfiguration(strSessionPath) return True except Exception as objInst: return False # Call the main subroutine named "MainSub" MainSub()