# $language = "Python" # $interface = "1.0" # BackupSCRTConfig.py # # Last Modified: # # 29 Aug, 2025 # - The summary output now includes the number of seconds it took to # complete the export operation. # # - Add support for a /auto-close script argument which, if present, # results in the progress/output tab being closed automatically after # five seconds. # # - Detect if the script was started from a login script or the command # line when SecureCRT is launched - in which case, don't create another # Local Shell tab if the existing tab is already a Local Shell tab; just # re-use that tab. In conjunction with enabling the Default Local Shell # session's "Close on disconnect" option, this would allow for a better # behavior/outsome if a scheduled task were launching SecureCRT to run # the script unattended. # # 14 Apr, 2023 # - Add highlights to the "Press any key to close this window" # instructions for increased visibility. # # 16 Mar, 2023 # - Eliminate awkward "silence" while this script operates by refactoring # slightly the way it waits for the export process to finish. Instead of # calling wait() on the process, the script polls the process at regular # intervals, allowing for "..." to be displayed as an indicator of work # beiong accomplished. # # - Display stats about how much data was written. # # - Display everything to the terminal window of a brand new Local Shell # connection ("Status Tab") so that there's no popup, and so that the # path to the resulting XML file can be easily selected/copied. # # - Lock the Status Tab, so that the individual cannot press Ctrl+C, ESC, # Ctrl+D, etc. to interfere with status reporting that's going on inside # the Status Tab. # # - Use a technique of sending "READY?" to the screen and waiting for it to # appear echo'd on the screen as an indication that the screen is now # responsive to our ability to interact with it from the script. # # - Clear the screen and set the cursor to row 2, col 1 before displaying # progress. # # - After sending the final Ctrl+C in preparation for sending the exit # command, wait for a brief moment so that the 'e' from the exit command # doesn't get swallowed/lost before the local shell is ready to receive # a command. # # 22 Sep, 2022 # - Initial Revision import sys, os, datetime, subprocess, time strSecureCRTExePath = sys.executable strDocumentsPath = os.path.join(os.path.expanduser("~"), "Documents") objStatsTab = None ESC = chr(27) CSI = ESC + "[" clrscn = CSI + "2J" highlight = CSI + "7;32m" highboldy = CSI + "7;1;32m" att_off = CSI + "0m" #--------------------------------------------------------------------- def DisplayOnScreen(strText): global objStatsTab bIsLocalShell = crt.Session.Config.GetOption("Protocol Name") == "Local Shell" bSyncOn = crt.Screen.Synchronous == True if (objStatsTab is None): nCurIndex = 0 bStatsTabAlreadyExists = False while nCurIndex < (crt.GetTabCount()): nCurIndex += 1 objCurTab = crt.GetTab(nCurIndex) if objCurTab.Caption == "Backup Status": objStatsTab = objCurTab bStatsTabAlreadyExists = True break if not bStatsTabAlreadyExists: # Check to see if we already have a Local Shell script tab, or if # we need to create one. bIsSyncTrue = crt.Screen.Synchronous == True bIsLocalShell = crt.Session.Config.GetOption("Protocol Name") == "Local Shell" if bIsSyncTrue and bIsLocalShell: objStatsTab = crt.GetScriptTab() else: objStatsTab = crt.Session.ConnectInTab("/LocalShell") objStatsTab.Session.Config.SetOption("Rows", 30) objStatsTab.Session.Config.SetOption("Cols", 100) objStatsTab.Caption = "Backup Status" objStatsTab.Screen.Synchronous = True objStatsTab.Session.Config.SetOption("Emulation", "XTerm") objStatsTab.Screen.IgnoreEscape = True while not objStatsTab.Session.Connected: crt.Sleep(25) objStatsTab.Screen.Send("READY?", True) objStatsTab.Screen.WaitForString("READY?") objStatsTab.Screen.Send(clrscn, True) objStatsTab.Screen.Send(CSI + "2;1H", True) objStatsTab.Screen.Synchronous = False objStatsTab.Session.Lock() while True: if not objStatsTab.Screen.WaitForCursor(100, True): break objStatsTab.Screen.SendSpecial("MENU_CLEAR_SCREEN_AND_SCROLLBACK") objStatsTab.Screen.Send(strText, True) #--------------------------------------------------------------------- def ConvertToHumanReadable(strBytes): bNegative = False fBytes = float(strBytes) if fBytes < 0: # We don't expect any negative numbers here, but just in case... return "0 bytes" fBytes = abs(fBytes) fKBytes = float(1024) fMBytes = float(1024 * 1024) fGBytes = float(1024 * 1024 * 1024) fTBytes = float(1024 * 1024 * 1024 * 1024) if fBytes >= fTBytes: strReturn = '{0:.2f} TB'.format(fBytes / fTBytes) elif fBytes >= fGBytes: strReturn = '{0:.2f} GB'.format(fBytes / fGBytes) elif fBytes >= fMBytes: strReturn = '{0:.2f} MB'.format(fBytes / fMBytes) elif fBytes >= fKBytes: strReturn = '{0:.2f} KB'.format(fBytes / fKBytes) else: strLabel = "bytes" if fBytes == 1: strLabel = "byte" strReturn = '{0:.0f} {1}'.format(fBytes, strLabel) strReturn = "{:,.0f} bytes ({})".format(fBytes, strReturn) return strReturn #--------------------------------------------------------------------- # Include the current SecureCRT version in the name of the XML file strVersion = ((crt.Version).replace("(", "")).replace(")", "") vVersionToks = strVersion.split(" ") if "x64" in strVersion: strCurVer = vVersionToks[0] + "." + vVersionToks[3] strCurVer += "(x64)" else: strCurVer = vVersionToks[0] + "." + vVersionToks[2] # Include the current time/date in the name of the XML file strCurDateTime = datetime.datetime.now().strftime("%Y%m%d-%H%M%S.%f")[:19] strFilename = "SCRTBackup_v{}__{}.xml".format(strCurVer, strCurDateTime) strBackupFilename = os.path.join(strDocumentsPath, strFilename) strBkOpLogFilename = strBackupFilename.replace(".xml", ".log") DisplayOnScreen("Backing up to {}\r\n".format(strBackupFilename)) nStartTime = time.perf_counter() p = subprocess.Popen([strSecureCRTExePath, "/EXPORT", strBackupFilename]) while True: crt.Sleep(250) p.poll() if p.returncode is not None: break DisplayOnScreen(f".") nSecondsElapsed = round(time.perf_counter() - nStartTime, 2) # Remove the export log file; for this automated mechanism, the goal # is simplicity if p.returncode == 0: if not os.path.isfile(strBackupFilename): strData = ( "\r\n" + "The backup operation completed, but the resulting XML file " + f"was not found to exist where it was expected ({strBackupFilename})." + "\r\n\r\n" + "You will likely need to use a tool like procmon to troubleshoot why your system is removing, renaming, or otherwise disallowing this XML file to be/remain on your filesystem at the above location." ) else: if not os.path.isfile(strBkOpLogFilename): strBkOpLogFilename = strBkOpLogFilename.replace(".log", ".xml.log") if os.path.isfile(strBkOpLogFilename): os.remove(strBkOpLogFilename) nBytes = 0 try: nBytes = os.stat(strBackupFilename).st_size strData = ( "\r\nBackup operation completed." + "\r\n{} written to XML file.".format( ConvertToHumanReadable(nBytes))) except Exception as objException: strData = ( "\r\n" + "The backup operation completed, but there was a failure attempting to read the size of the resulting XML file." + f"\r\n{strBackupFilename}" + "\r\n" + f"\r\nException: {str(objException)}" + "\r\nYou will likely need to use a tool like procmon to troubleshoot why your system is removing, renaming, or otherwise disallowing this XML file to be/remain on your filesystem at the above location." ) DisplayOnScreen(strData) else: DisplayOnScreen("\r\nBackup failed. Exit code: {}".format(nExitCode)) if objStatsTab is not None: objStatsTab.Activate() DisplayOnScreen(f"\r\n\r\nOperation took {nSecondsElapsed} seconds.") if crt.Arguments.Count > 0 and crt.Arguments.GetArg(0).lower() == "/auto-close": DisplayOnScreen("\r\n\r\n") strSecondsLabel = "seconds" nSecond = 5 while nSecond > 0: if nSecond == 1: strSecondsLabel = "second " DisplayOnScreen(f"\r{highlight}Closing automatically in {highboldy}{nSecond} {strSecondsLabel}{att_off}{highlight}...{att_off}") crt.Sleep(1000) nSecond -= 1 else: DisplayOnScreen("\r\n\r\n{}Press any key to close this tab.{}".format(highlight, att_off)) objStatsTab.Screen.WaitForKey() objStatsTab.Screen.Synchronous = True objStatsTab.Session.Unlock() objStatsTab.Screen.Send(chr(3)) objStatsTab.Screen.WaitForCursor(250, True) objStatsTab.Screen.Send("exit\r") if not objStatsTab.Session.Config.GetOption("Close On Disconnect"): objStatsTab.Activate() crt.Sleep(100) objStatsTab.Screen.SendSpecial("MENU_TAB_CLOSE")