public void DoAddTasks(bool abStartup) { // Do this at most once per minute to avoid running the same task twice in rapid succession. if ( !abStartup && (!moProfile.ContainsKey("-AddTasks") || DateTime.Now < mdtPreviousAddTasksStarted.AddMinutes(1)) ) return; mdtPreviousAddTasksStarted = DateTime.Now; try { if ( null == moAddTasksProfile ) { moAddTasksProfile = moProfile.oProfile("-AddTasks").oOneKeyProfile("-Task"); moAddTasksProcessArray = new Process[moAddTasksProfile.Count]; } for (int i=0; i < moAddTasksProfile.Count; ++i) { // Convert the current task from a command-line string to a profile oject. tvProfile loAddTask = new tvProfile(moAddTasksProfile[i].ToString()); bool lbDoTask = false; if ( abStartup ) { lbDoTask = loAddTask.bValue("-OnStartup", false); // Reset pause timer to allow other tasks to run without delay after startup. mdtPreviousAddTasksStarted = DateTime.Now.AddMinutes(-1); } else { DateTime ldtTaskStartTime = loAddTask.dtValue("-StartTime", DateTime.MinValue); string lsTaskDaysOfWeek = loAddTask.sValue("-StartDays", ""); // If -StartTime is within the current minute, start the task. // If -StartDays is specified, run the task on those days only. lbDoTask = DateTime.MinValue != ldtTaskStartTime && (int)mdtPreviousAddTasksStarted.TimeOfDay.TotalMinutes == (int)ldtTaskStartTime.TimeOfDay.TotalMinutes && ("" == lsTaskDaysOfWeek || this.bListIncludesDay(lsTaskDaysOfWeek, mdtPreviousAddTasksStarted)); } if ( lbDoTask ) { string lsCommandEXE = loAddTask.sValue("-CommandEXE", "add task -CommandEXE missing"); Process loProcess = new Process(); loProcess.ErrorDataReceived += new DataReceivedEventHandler(this.BackupProcessOutputHandler); loProcess.OutputDataReceived += new DataReceivedEventHandler(this.BackupProcessOutputHandler); loProcess.StartInfo.FileName = lsCommandEXE; loProcess.StartInfo.Arguments = loAddTask.sValue("-CommandArgs", ""); loAddTask.bValue("-UnloadOnExit", false); // The following subset of parameters are overridden when -TimeoutMinutes is set. This is // necessary to guarantee IO redirection is handled properly (ie. output goes to the log). bool lbWaitForExitOverride = (loAddTask.iValue("-TimeoutMinutes", 0) > 0); loProcess.StartInfo.CreateNoWindow = lbWaitForExitOverride | loAddTask.bValue("-CreateNoWindow", false); loProcess.StartInfo.UseShellExecute = !lbWaitForExitOverride & loAddTask.bValue("-UseShellExecute", true); loProcess.StartInfo.RedirectStandardInput = lbWaitForExitOverride | loAddTask.bValue("-RedirectStandardInput", false); loProcess.StartInfo.RedirectStandardError = lbWaitForExitOverride | loAddTask.bValue("-RedirectStandardError", false); loProcess.StartInfo.RedirectStandardOutput = lbWaitForExitOverride | loAddTask.bValue("-RedirectStandardOutput", false); moAddTasksProcessArray[i] = loProcess; try { if ( !loAddTask.bValue("-OnStartup", false) ) { this.LogIt(String.Format("Starting Task: {0}", loAddTask.sCommandLine())); loProcess.Start(); // Start output to console also. if ( loProcess.StartInfo.RedirectStandardError ) loProcess.BeginErrorReadLine(); if ( loProcess.StartInfo.RedirectStandardOutput ) loProcess.BeginOutputReadLine(); if ( lbWaitForExitOverride ) { // Wait the timeout period, then call "WaitForExit()" to flush the output steams. if ( loProcess.WaitForExit(60000 * loAddTask.iValue("-TimeoutMinutes", 0)) ) loProcess.WaitForExit(); // Stop output to console. if ( loProcess.StartInfo.RedirectStandardError ) loProcess.CancelErrorRead(); if ( loProcess.StartInfo.RedirectStandardOutput ) loProcess.CancelOutputRead(); loProcess.Close(); } } else { bool lbFound = false; string lsWindowTitle = loAddTask.sValue("-CommandWindowTitle", ""); Process[] loProcessesArray = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(loProcess.StartInfo.FileName)); // If there's exactly one matching process and no given window title to compare, we're done. lbFound = (1 == loProcessesArray.Length && "" == lsWindowTitle ); // If no window title has been given to compare, there's nothing else to do. if ( !lbFound && "" != lsWindowTitle ) { // If no matching processes have been found so far, get them all to compare. if ( 0 == loProcessesArray.Length ) loProcessesArray = Process.GetProcesses(); // Since a window title has been provided, it must be compared to the process(es) found. // Wildcards are permitted, but only at the end of titles. We stop at the first match. foreach (Process loProcessEntry in loProcessesArray) if ( loProcessEntry.MainWindowTitle.StartsWith(lsWindowTitle.Replace("*", "")) ) { lbFound = true; break; } } // Don't start -OnStartup processes that have already been started. if ( lbFound ) { // The process has "already started" if there is only one with the // same EXE or multiple EXEs with one having the same window title. this.LogIt(String.Format("Already running, task not started: {0}", loAddTask.sCommandLine())); } else { this.LogIt(String.Format("Starting Task: {0}", loAddTask.sCommandLine())); loProcess.Start(); // Start output to console also. if ( loProcess.StartInfo.RedirectStandardError ) loProcess.BeginErrorReadLine(); if ( loProcess.StartInfo.RedirectStandardOutput ) loProcess.BeginOutputReadLine(); if ( lbWaitForExitOverride ) { // Wait the timeout period, then call "WaitForExit()" to flush the output steams. if ( loProcess.WaitForExit(60000 * loAddTask.iValue("-TimeoutMinutes", 0)) ) loProcess.WaitForExit(); // Stop output to console. if ( loProcess.StartInfo.RedirectStandardError ) loProcess.CancelErrorRead(); if ( loProcess.StartInfo.RedirectStandardOutput ) loProcess.CancelOutputRead(); loProcess.Close(); } } } } catch (Exception ex) { this.ShowError(ex.Message, String.Format("Failed starting task: {0}", lsCommandEXE)); } } } } catch (Exception ex) { this.ShowError(ex.Message, "Add Tasks Failed"); } }
/// <summary> /// Recursively deletes files of the given path\file /// specification older than the given age in days. /// </summary> /// <param name="asPathFiles"> /// The path\file specification of files to be deleted. /// </param> /// <param name="adtOlderThan"> /// Files with timestamps older than this will be deleted. /// </param> /// <param name="aeFileDateTimeType"> /// Each file has multiple timestamps. This specifies which one to use. /// </param> /// <param name="aoProfile"> /// This profile contains the various cleanup parameters. /// </param> public bool CleanupPathFileSpec( string asPathFiles , DateTime adtOlderThan , FileDateTimeTypes aeFileDateTimeType , tvProfile aoProfile ) { if ( this.bMainLoopStopped ) return true; bool lbCleanupPathFileSpec = true; string lsPath = Path.GetDirectoryName(asPathFiles); string lsFiles = Path.GetFileName(asPathFiles); bool lbCleanupHidden = aoProfile.bValue("-CleanupHidden", false); bool lbCleanupReadOnly = aoProfile.bValue("-CleanupReadOnly", false); bool lbRecurse = aoProfile.bValue("-Recurse", false); /* bool lbDisplayFileDeletionErrors = true; // Don't create a default value here. Let the user create the value via a prompt // below. This must be handled this way since the deletion error messages are // modeless and therefore the "skip this" checkbox will be presented only once. if ( moProfile.ContainsKey("-MsgBoxPromptFileDeletionErrors") ) lbDisplayFileDeletionErrors = moProfile.bValue("-MsgBoxPromptFileDeletionErrors", true); */ bool lbDisplayFileDeletionErrors = moProfile.bValue("-MsgBoxPromptFileDeletionErrors", false); string lsDirectorySeparatorChar = Path.DirectorySeparatorChar.ToString(); string lsRecurseFolder = aoProfile.sValue("-RecurseFolder", ""); // The recurse folder must be surrounded by path delimiters. Otherwise, // a matching path name substring may be found instead of a subfolder name. if ( !lsRecurseFolder.StartsWith(lsDirectorySeparatorChar) ) lsRecurseFolder = lsDirectorySeparatorChar + lsRecurseFolder; if ( !lsRecurseFolder.EndsWith(lsDirectorySeparatorChar) ) lsRecurseFolder += lsDirectorySeparatorChar; try { // Only check for file cleanup if either there is no recursion // or the base path contains the recursion subfolder. An empty // recursion subfolder matches everything from the base path up. if ( !lbRecurse || (lbRecurse && (lsPath + lsDirectorySeparatorChar).Contains(lsRecurseFolder)) ) { IOrderedEnumerable<FileSystemInfo> loFileSysInfoList = null; // If the given file path does not exist, do nothing. if ( Directory.Exists(lsPath) ) try { // Get a list of all files for potential deletion // sorted by file date (oldest files first). switch (aeFileDateTimeType) { case FileDateTimeTypes.CreationTime: loFileSysInfoList = new DirectoryInfo(lsPath).GetFileSystemInfos(lsFiles) .OrderBy(a => a.CreationTime); break; case FileDateTimeTypes.LastAccessTime: loFileSysInfoList = new DirectoryInfo(lsPath).GetFileSystemInfos(lsFiles) .OrderBy(a => a.LastAccessTime); break; default: loFileSysInfoList = new DirectoryInfo(lsPath).GetFileSystemInfos(lsFiles) .OrderBy(a => a.LastWriteTime); break; } } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("Folder: \"{0}\"\r\n", lsPath) + ex.Message); else this.ShowModelessError( string.Format("Folder: \"{0}\"\r\n", lsPath) + ex.Message , "Error Deleting Files" , "-FileDeletionErrors" ); } if ( null != loFileSysInfoList ) { // This boolean prevents wiping out many old files // that are not regularly replaced with newer files. bool lbApplyDeletionLimit = aoProfile.bValue("-ApplyDeletionLimit", true); int liFileDeletionLimit = this.iFileDeletionLimit( loFileSysInfoList.Count(), adtOlderThan); int liIndex = 0; foreach (FileSystemInfo loFileSysInfo in loFileSysInfoList) { System.Windows.Forms.Application.DoEvents(); System.Threading.Thread.Sleep(moProfile.iValue("-CleanupLoopSleepMS", 1)); if ( this.bMainLoopStopped ) break; // Show UI activity for each file evaluated. this.IncrementUIProgressBar(); // Since files are deleted in file date order, // the oldest files will always be deleted first. // Once the deletion limit is reached, stop deleting. if ( lbApplyDeletionLimit && ++liIndex > liFileDeletionLimit ) break; DateTime ldtFileDate; switch (aeFileDateTimeType) { case FileDateTimeTypes.CreationTime: ldtFileDate = loFileSysInfo.CreationTime; break; case FileDateTimeTypes.LastAccessTime: ldtFileDate = loFileSysInfo.LastAccessTime; break; default: ldtFileDate = loFileSysInfo.LastWriteTime; break; } // Delete the current file only if its file date is older // than the given date. If it's also a hidden file, the // -CleanupHidden switch must be specified (see above). bool lbDoDelete = ldtFileDate < adtOlderThan && ( lbCleanupHidden || FileAttributes.Hidden != (loFileSysInfo.Attributes & FileAttributes.Hidden)); if ( lbDoDelete ) { try { // Get the file size. long llFileSize = new FileInfo(loFileSysInfo.FullName).Length; // If the -CleanupReadOnly switch is used (see above), // set the current file's attributes to "Normal". if ( lbCleanupReadOnly && FileAttributes.ReadOnly == (loFileSysInfo.Attributes & FileAttributes.ReadOnly) ) loFileSysInfo.Attributes = FileAttributes.Normal; // Hidden files can be deleted without changing attributes. // Attempt to delete the file. If its attributes still // include "readonly", let it blow an error. loFileSysInfo.Delete(); this.LogDeletedFile(loFileSysInfo.FullName, ldtFileDate, llFileSize); } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("File: \"{0}\"\r\n", loFileSysInfo.FullName) + ex.Message); else this.ShowModelessError( string.Format("File: \"{0}\"\r\n", loFileSysInfo.FullName) + ex.Message , "Error Deleting File" , "-FileDeletionErrors" ); } } } } } // Recursion is determined by the -Recurse switch (see above). if ( lbRecurse ) { // Process the sub-folders in the base folder. // Use an empty array instead of null to // prevent the "foreach" from blowing up. string[] lsSubfoldersArray = new string[0]; if ( Directory.Exists(lsPath) ) { try { // Get subdirectories only at the next level. lsSubfoldersArray = Directory.GetDirectories(lsPath); } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("Folder: \"{0}\"\r\n", lsPath) + ex.Message); else this.ShowModelessError( string.Format("Folder: \"{0}\"\r\n", lsPath) + ex.Message , "Error Deleting Folders" , "-FileDeletionErrors" ); } } foreach (string lsSubfolder in lsSubfoldersArray) { System.Windows.Forms.Application.DoEvents(); if ( this.bMainLoopStopped ) break; // Get the current subfolder's attributes. Using "Hidden" by default prevents // an attempt at deleting the file if its attributes can't be read for whatever // reason (unless the -CleanupHidden switch is used). In the case of unreadable // attributes the file would not likely be deletable anyway. FileAttributes loFileAttributes = FileAttributes.Hidden; try { loFileAttributes = File.GetAttributes(lsSubfolder); } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message); else this.ShowModelessError( string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message , "Error Deleting Folder" , "-FileDeletionErrors" ); } if ( lbCleanupHidden || FileAttributes.Hidden != (loFileAttributes & FileAttributes.Hidden) ) { // Remove all applicable files in the current subfolder. this.CleanupPathFileSpec( Path.Combine(lsSubfolder, lsFiles) , adtOlderThan , aeFileDateTimeType , aoProfile ); // This is deliberate. Do not use "Path.Combine()" here. We need // the trailing directory separator character (eg. the backslash) // since the recurse folder will always have a trailing separator. string lsSubfolderPlus = lsSubfolder + lsDirectorySeparatorChar; // Remove empty subfolders in the recurse folder only (ie. // do not remove the recurse folder itself). In other words, // lsSubfolderPlus may contain lsRecurseFolder, but it can't // end with it (unless lsRecurseFolder is just a backslash). if ( lsSubfolderPlus.Contains(lsRecurseFolder) && ( !lsSubfolderPlus.EndsWith(lsRecurseFolder) || lsDirectorySeparatorChar == lsRecurseFolder) ) { // These are used to judge the subfolder emptiness. string[] lsPathFilesArray = new string[0]; string[] lsSubfoldersArray2 = new string[0]; if ( Directory.Exists(lsSubfolder) ) try { lsPathFilesArray = Directory.GetFiles(lsSubfolder); lsSubfoldersArray2 = Directory.GetDirectories(lsSubfolder); } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message); else this.ShowModelessError( string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message , "Error Deleting Files" , "-FileDeletionErrors" ); } // Remove the folder only if it's empty. if ( 0 == lsPathFilesArray.Length && 0 == lsSubfoldersArray2.Length ) { DirectoryInfo loDirInfo = new DirectoryInfo(lsSubfolder); DateTime ldtFileDate; switch (aeFileDateTimeType) { case FileDateTimeTypes.CreationTime: ldtFileDate = loDirInfo.CreationTime; break; case FileDateTimeTypes.LastAccessTime: ldtFileDate = loDirInfo.LastAccessTime; break; default: ldtFileDate = loDirInfo.LastWriteTime; break; } try { // If the -CleanupReadOnly switch is used, // set the subfolder to "Normal" attributes. if ( lbCleanupReadOnly && FileAttributes.ReadOnly == (loFileAttributes & FileAttributes.ReadOnly) ) File.SetAttributes(lsSubfolder, FileAttributes.Normal); // Hidden folders can be deleted without changing attributes. // Attempt to delete the subfolder. If its attributes still // include "readonly", let it blow an error. Directory.Delete(lsSubfolder); // Using "0" as the file size also indicates a folder deletion. this.LogDeletedFile(lsSubfolder + " (dir)", ldtFileDate, 0); } catch (Exception ex) { if ( !lbDisplayFileDeletionErrors ) this.LogIt(string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message); else this.ShowModelessError( string.Format("Folder: \"{0}\"\r\n", lsSubfolder) + ex.Message , "Error Deleting Folder" , "-FileDeletionErrors" ); } } } } } } } catch (Exception ex) { this.ShowError(ex.Message, "Unanticipated Error"); lbCleanupPathFileSpec = false; } return lbCleanupPathFileSpec; }
/// <summary> /// Deletes files greater than the given age /// days and of the given file specifications. /// </summary> public bool CleanupFiles() { // Return if cleanup is disabled. if ( !moProfile.bValue("-CleanupFiles", true) ) { this.LogIt(""); this.LogIt("Cleanup files is disabled."); return true; } // Return if backup is enabled and it was stopped. if ( moProfile.bValue("-BackupFiles", true) && this.bMainLoopStopped ) return true; else this.bMainLoopStopped = false; bool lbCleanupFiles = true; this.LogIt(""); this.LogIt("File cleanup started ..."); // Write the deleted file list header to disk. string lsDeletedFileListOutputPathFile = moProfile.sRelativeToProfilePathFile(this.sDeletedFileListOutputPathFile); if ( !File.Exists(lsDeletedFileListOutputPathFile) ) { // Get the column header array. string[] lsColumnHeaderArray = moProfile.sValue("-DeletedFileListOutputColumnHeaderArray", "Deleted File Time,File Size,Former File Location").Split(','); StreamWriter loStreamWriter = null; try { loStreamWriter = new StreamWriter(lsDeletedFileListOutputPathFile, false); // First, output the file header. loStreamWriter.WriteLine(string.Format(moProfile.sValue("-DeletedFileListOutputHeader" , "{0, -10:MM-dd-yyyy} File Cleanup List"), DateTime.Today)); loStreamWriter.WriteLine(); // Next output the column headers properly formatted to match the forthcoming data rows. loStreamWriter.WriteLine(string.Format( moProfile.sValue("-DeletedFileListOutputColumnFormat", "{0, -22:MM-dd-yyyy hh:mm:ss tt} {1, 13:#,#} {2}") , lsColumnHeaderArray[0], lsColumnHeaderArray[1], lsColumnHeaderArray[2])); loStreamWriter.WriteLine(); } catch (Exception ex) { this.ShowError(string.Format("File Write Failure: \"{0}\"\r\n" , lsDeletedFileListOutputPathFile) + ex.Message , "Failed Writing File" ); } finally { if ( null != loStreamWriter ) loStreamWriter.Close(); } } try { // This is used elsewhere to warn users if the software // has not been properly configured for file cleanups. this.mbHasNoDeletionGroups = true; if ( !moProfile.ContainsKey("-CleanupSet") ) { // Create the default file cleanup sets. // Get the primary backup set (ie. the 1st). tvProfile loBackupSet1Profile = new tvProfile(moProfile.sValue("-BackupSet", "(not set)")); string lsBackupOutputPathFileBase = this.sBackupOutputPathFileBase(loBackupSet1Profile); string lsBackupOutputPath = Path.GetDirectoryName(lsBackupOutputPathFileBase); string lsBackupOutputFilenameNoExt = Path.GetFileNameWithoutExtension(lsBackupOutputPathFileBase); // Initially set the cleanup of primary backups to "no cleanup" (ie. 1000 years). // The deletion limit prevents old file removal without new files to replace them. moProfile.Add("-CleanupSet", string.Format(@" -AgeDays=365000 -FilesToDelete={0}*{1} " , Path.Combine(lsBackupOutputPath, lsBackupOutputFilenameNoExt) , Path.GetExtension(lsBackupOutputPathFileBase) )); // Set the cleanup of temporary backup files to 0 days. // This is necessary to cleanup after killed processes. moProfile.Add("-CleanupSet", string.Format(@" -AgeDays=0 -FilesToDelete={0}.tmp* " , Path.Combine(this.sArchivePath(), "*" + Path.GetExtension(lsBackupOutputPathFileBase)) )); // Set the cleanup of file lists and backup / cleanup log files to 30 days. moProfile.Add("-CleanupSet", string.Format(@" -AgeDays=30 -FilesToDelete={0} -FilesToDelete={1} " , Path.Combine(Path.GetDirectoryName(this.sZipToolFileListPathFileBase) , "*" + Path.GetExtension(this.sZipToolFileListPathFileBase)) , Path.Combine(Path.GetDirectoryName(this.sLogPathFileBase) , "*" + Path.GetExtension(this.sLogPathFileBase)) )); } // Get all cleanup sets. tvProfile loCleanupSetsProfile = moProfile.oOneKeyProfile("-CleanupSet"); foreach (DictionaryEntry loEntry in loCleanupSetsProfile) { System.Windows.Forms.Application.DoEvents(); if ( this.bMainLoopStopped ) break; // Convert the current cleanup set from a command-line string to a profile oject. tvProfile loCurrentCleanupSet = new tvProfile(loEntry.Value.ToString()); // The default "LastWriteTime" is the last modified datetime. FileDateTimeTypes leFileDateTimeType; switch (loCurrentCleanupSet.sValue("-DeletedFileListDateTimeType", "LastWriteTime")) { case "CreationTime": leFileDateTimeType = FileDateTimeTypes.CreationTime; break; case "LastAccessTime": leFileDateTimeType = FileDateTimeTypes.LastAccessTime; break; default: leFileDateTimeType = FileDateTimeTypes.LastWriteTime; break; } // Use 1000 years as the default file age. int liAgeDays = loCurrentCleanupSet.iValue("-AgeDays", 365000); liAgeDays = 0 == liAgeDays ? -1 : liAgeDays; // "0" means "delete everything." -1 makes that happen. DateTime ldtOlderThan = DateTime.Now.AddDays(-liAgeDays); // Get the list of path\file specifications to delete. tvProfile loFilesToDeleteProfile = loCurrentCleanupSet.oOneKeyProfile("-FilesToDelete"); foreach (DictionaryEntry loPathFilesEntry in loFilesToDeleteProfile) { System.Windows.Forms.Application.DoEvents(); if ( this.bMainLoopStopped ) break; // Being here means there is at least one set of files to delete. this.mbHasNoDeletionGroups = false; if ( lbCleanupFiles ) lbCleanupFiles = this.CleanupPathFileSpec( moProfile.sRelativeToProfilePathFile(loPathFilesEntry.Value.ToString()) , ldtOlderThan , leFileDateTimeType , loCurrentCleanupSet ); } } if ( lbCleanupFiles ) this.DisplayDeletedFileList(); } catch (Exception ex) { this.ShowError(ex.Message, "Unanticipated Error"); lbCleanupFiles = false; } if ( this.bMainLoopStopped ) { this.LogIt("Cleanup process stopped."); this.bMainLoopStopped = false; lbCleanupFiles = false; } else { if ( lbCleanupFiles ) this.LogIt("File cleanup finished."); else this.LogIt("File cleanup failed."); } return lbCleanupFiles; }
public void BackupFailedScript() { // Before the "backup failed" script can be initialized, // -BackupFailedScriptPathFile and -BackupFailedScriptHelp // must be initialized first. if ( moProfile.bValue("-BackupFailedScriptInit", false) ) { moProfile.Remove("-BackupFailedScriptPathFile"); moProfile.Remove("-BackupFailedScriptHelp"); } string lsBackupFailedScriptPathFile = moProfile.sRelativeToProfilePathFile( moProfile.sValue("-BackupFailedScriptPathFile", msBackupFailedScriptPathFileDefault)); string lsBackupFailedScriptOutputPathFile = lsBackupFailedScriptPathFile + ".txt"; // If the "backup failed" script has not been redefined to point elsewhere, // prepare to create it from the current -BackupFailedScriptHelp content. // We do this even if the script file actually exists already. This way // the following default script will be written to the profile file if // it's not already there. if ( lsBackupFailedScriptPathFile == moProfile.sRelativeToProfilePathFile(msBackupFailedScriptPathFileDefault) ) { string lsBackupFailedScript = moProfile.sValue("-BackupFailedScriptHelp", @" @echo off if %1=="""" goto :EOF :: :: *** ""Backup Failed"" script goes here. *** :: :: This script is executed after each backup fails to complete. If you :: prompt for input within this DOS script (eg. ""pause""), the script :: will stay in memory. This is not recommended since such behavior would :: be similar to a memory leak. :: :: You can also create and edit another DOS script file and reference that :: instead (see ""-BackupFailedScriptPathFile"" in ""{ProfileFile}""). You :: can access several parameters from the completed backup via the DOS shell :: command-line: :: :: %1 = ""BackupOutputPathFile"" :: :: This is the full path\file specification of the backup file. :: It includes the output filename as well as the embedded date. :: :: %2 = ""BackupOutputFilename"" :: :: This is the backup filename only (ie. no path). It includes the :: embedded date as well as the filename extension. :: :: %3 = ""BackupBaseOutputFilename"" :: :: This is the backup filename with no path and no date. It's just :: the base output filename name with the filename extension. :: :: %4 = ""LocalArchivePath"" :: :: This is the local archive folder. :: :: %5 = ""VirtualMachineHostArchive"" :: :: This is the virtual machine host archive share name. :: :: :: Note: All arguments will be passed with double quotation marks included. :: So don't use quotes here unless you want ""double double"" quotes. :: Also, ERRORLEVEL is not reliable enough to be heavily used below. :: :: The following example copies the backup file to the root of drive C: :: (if it's ""AdministratorFiles.zip""). Then it outputs a directory listing :: of the archive folder. :: :: Example: :: :: if not %3.==""AdministratorFiles.zip"". goto :EOF :: :: echo copy %1 C:\ ] ""{BackupFailedScriptOutputPathFile}"" 2>&1 :: copy %1 C:\ ]] ""{BackupFailedScriptOutputPathFile}"" 2>&1 :: :: dir %4 ]] ""{BackupFailedScriptOutputPathFile}"" 2>&1 :: :: ^^ Replace brackets with darts. :: Initialize the ""backup failed"" script log file. It's for this run only. echo. > ""{BackupFailedScriptOutputPathFile}"" 2>&1 :: Any failed backup file less than this size will be removed. set MinFileBytes=1024 set Filesize=%~z1 :: This references the backup file by named variable (rather than positionally): set FileSpec=%1 echo If the failed backup file is smaller than %MinFileBytes% bytes, >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo it will be removed. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo %FileSpec% is %Filesize% bytes. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 if %Filesize% lss %MinFileBytes% goto RemoveIt echo. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo The file is not smaller than the minimmum (%MinFileBytes% bytes). Keep it. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 goto :EOF :RemoveIt echo. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo The file is smaller than the minimmum (%MinFileBytes% bytes). Remove it. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo This removes the failed backup file: >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 echo del %FileSpec% >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 del %FileSpec% >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 if exist %FileSpec% echo Error: %FileSpec% is still there. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 if not exist %FileSpec% echo %FileSpec% has been removed. >> ""{BackupFailedScriptOutputPathFile}"" 2>&1 " ) .Replace("{ProfileFile}", Path.GetFileName(moProfile.sLoadedPathFile)) .Replace("{BackupFailedScriptOutputPathFile}", Path.GetFileName(lsBackupFailedScriptOutputPathFile)) ; // Write the default "backup failed" script if it's // not there or if -BackupFailedScriptInit is set. if ( !File.Exists(lsBackupFailedScriptPathFile) || moProfile.bValue("-BackupFailedScriptInit", false) ) { StreamWriter loStreamWriter = null; try { loStreamWriter = new StreamWriter(lsBackupFailedScriptPathFile, false); loStreamWriter.Write(lsBackupFailedScript); // This is used only once then reset. moProfile["-BackupFailedScriptInit"] = false; moProfile.Save(); } catch (Exception ex) { this.ShowError(string.Format("File Write Failure: \"{0}\"\r\n" , lsBackupFailedScript) + ex.Message , "Failed Writing File" ); } finally { if ( null != loStreamWriter ) loStreamWriter.Close(); } } } try { this.LogIt(""); this.LogIt("Running \"backup failed\" script ..."); // Cache the arguments to be passed to the script. tvProfile loArgs = new tvProfile(); loArgs.Add("-BackupOutputPathFile" , msCurrentBackupOutputPathFile ); loArgs.Add("-BackupOutputFilename" , Path.GetFileName(msCurrentBackupOutputPathFile) ); loArgs.Add("-BackupBaseOutputFilename" , Path.GetFileName(this.sBackupOutputPathFileBase()) ); loArgs.Add("-LocalArchivePath" , this.sArchivePath() ); loArgs.Add("-VirtualMachineHostArchivePath" , moProfile.sValue("-VirtualMachineHostArchivePath", "") ); moProfile["-BackupFailedArgs"] = loArgs.sCommandBlock(); moProfile.Save(); // Run the "backup failed" script. Process loProcess = new Process(); loProcess.StartInfo.FileName = lsBackupFailedScriptPathFile; loProcess.StartInfo.Arguments = string.Format( " \"{0}\" \"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\" \"{7}\" \"{8}\" \"{9}\" " , loArgs.sValue("-BackupOutputPathFile" , "") , loArgs.sValue("-BackupOutputFilename" , "") , loArgs.sValue("-BackupBaseOutputFilename" , "") , loArgs.sValue("-LocalArchivePath" , "") , loArgs.sValue("-VirtualMachineHostArchivePath", "") , "" , "" , "" , "" , "" ); loProcess.StartInfo.UseShellExecute = true; loProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; loProcess.Start(); // Wait for the "backup failed" script to finish. while ( !this.bMainLoopStopped && !loProcess.HasExited ) { System.Windows.Forms.Application.DoEvents(); System.Threading.Thread.Sleep(moProfile.iValue("-MainLoopSleepMS", 100)); } // If a stop request came through, kill the "backup failed" script. if ( this.bMainLoopStopped && !this.bKillProcess(loProcess) ) this.ShowError("The \"backup failed\" script could not be stopped." , "Backup Failed"); if ( !this.bMainLoopStopped ) { if ( 0 == loProcess.ExitCode ) { this.LogIt("The \"backup failed\" script finished successfully."); } else { this.LogIt("The \"backup failed\" script did NOT finish successfully."); } // Get the output from the "backup failed" script. this.LogIt("\r\nHere's output from the \"backup failed\" script:\r\n\r\n" + this.sFileAsStream(lsBackupFailedScriptOutputPathFile)); } loProcess.Close(); } catch (Exception ex) { this.SetBackupFailed(); this.ShowError(ex.Message, "Failed Running \"Backup Failed\" Script"); } }
public int iBackupDoneScriptCopyFailuresWithBitField(bool abRerunLastArgs) { int liBackupDoneScriptCopyFailuresWithBitField = 0; // Before the "backup done" script can be initialized, // -BackupDoneScriptPathFile and -BackupDoneScriptHelp // must be initialized first. if ( moProfile.bValue("-BackupDoneScriptInit", false) ) { moProfile.Remove("-BackupDoneScriptPathFile"); moProfile.Remove("-BackupDoneScriptHelp"); } string lsBackupDoneScriptPathFile = moProfile.sRelativeToProfilePathFile( moProfile.sValue("-BackupDoneScriptPathFile", msBackupDoneScriptPathFileDefault)); string lsBackupDoneScriptOutputPathFile = lsBackupDoneScriptPathFile + ".txt"; // If the "backup done" script has not been redefined to point elsewhere, // prepare to create it from the current -BackupDoneScriptHelp content. // We do this even if the script file actually exists already. This way // the following default script will be written to the profile file if // it's not already there. if ( lsBackupDoneScriptPathFile == moProfile.sRelativeToProfilePathFile(msBackupDoneScriptPathFileDefault) ) { bool lbUseMainhostArchive = moProfile.bValue("-UseVirtualMachineHostArchive", false); bool lbUseConnectMainhost = moProfile.bValue("-UseConnectVirtualMachineHost", false); string lsBackupDoneScript = moProfile.sValue("-BackupDoneScriptHelp", @" @echo off if %1=="""" goto :EOF :: :: *** ""Backup Done"" script goes here. *** :: :: This script is executed after each successful backup completes. If you :: prompt for input within this DOS script (eg. ""pause""), the script :: will stay in memory. This is not recommended since such behavior would :: be similar to a memory leak. :: :: You can also create and edit another DOS script file and reference that :: instead (see ""-BackupDoneScriptPathFile"" in ""{ProfileFile}""). You :: can access several parameters from the completed backup via the DOS shell :: command-line: :: :: %1 = ""BackupOutputPathFile"" :: :: This is the full path\file specification of the backup file. :: It includes the output filename as well as the embedded date. :: :: %2 = ""BackupOutputFilename"" :: :: This is the backup filename only (ie. no path). It includes the :: embedded date as well as the filename extension. :: :: %3 = ""BackupBaseOutputFilename"" :: :: This is the backup filename with no path and no date. It's just :: the base output filename name with the filename extension. :: :: %4 = ""LocalArchivePath"" :: :: This is the local archive folder. :: :: %5 = ""VirtualMachineHostArchive"" :: :: This is the virtual machine host archive share name. :: :: %6 = ""LogPathFile"" :: :: This is the full path\file specification of the backup log file. :: :: :: Note: All arguments will be passed with double quotation marks included. :: So don't use quotes here unless you want ""double double"" quotes. :: Also, ERRORLEVEL is not reliable enough to be heavily used below. :: :: The following example copies the backup file to the root of drive C: :: (if it's ""AdministratorFiles.zip""). Then it outputs a directory listing :: of the archive folder. :: :: Example: :: :: if not %3.==""AdministratorFiles.zip"". goto :EOF :: :: echo copy %1 C:\ ] ""{BackupDoneScriptOutputPathFile}"" 2>&1 :: copy %1 C:\ ]] ""{BackupDoneScriptOutputPathFile}"" 2>&1 :: :: dir %4 ]] ""{BackupDoneScriptOutputPathFile}"" 2>&1 :: :: ^^ Replace brackets with darts. :: The ""CopyFailures"" environment variable is used to keep count of errors to be returned. set CopyFailures=0 :: Initialize the ""backup done"" script log file. It's for this run only. echo. > ""{BackupDoneScriptOutputPathFile}"" 2>&1 " + (!lbUseMainhostArchive ? "" : @" :: This references the backup destination copy on the VM host: set FileSpec=%5\%2 echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo This copies the backup to the virtual machine host archive: >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo copy %1 %5 >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 copy %1 %5 >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if not exist %FileSpec% echo Error: %FileSpec% is not there. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if not exist %FileSpec% set /A CopyFailures += 1 " ) + @" echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo The following copies the backup (base name) to each attached backup >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo device with the file ""{BackupDriveToken}"" at its root. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 set BackupOutputPathFile=%1 set BackupBaseOutputFilename=%3 set BackupDeviceDecimalBitField=0 set BackupDevicePositionExponent=23 :: There are 23 drive letters listed (ie. possible backup devices). A 32-bit integer :: can handle no more when a corresponding bit field is combined with copy failures. for %%d in (D: E: F: G: H: I: J: K: L: M: N: O: P: Q: R: S: T: U: V: W: X: Y: Z:) do call :DoCopy %%d :: Set bit 24 (ie. add 2^23 = 8,388,608) to preserve bit field's leading zeros. :: Combine the bit field and the copy failures into a single composite value. :: The factor of 100 means that there can be a maximum of 99 copy failures. set /A CompositeResult = 100 * (8388608 + %BackupDeviceDecimalBitField%) + %CopyFailures% echo CompositeResult=%CompositeResult% >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 exit %CompositeResult% :DoCopy set /A BackupDevicePositionExponent -= 1 dir %1 > nul 2> nul if ERRORLEVEL 1 goto :EOF if not exist %1\""{BackupDriveToken}"" goto :EOF :: Determine the bit position (and the corresponding decimal value) from the exponent. set BitFieldDevicePosition=1 for /L %%x in (1, 1, %BackupDevicePositionExponent%) do set /A BitFieldDevicePosition *= 2 :: Add the calculated positional value to the bit field for the current backup device. set /A BackupDeviceDecimalBitField += %BitFieldDevicePosition% :: This references the backup destination copy on the current backup device (%1): set FileSpec=%1\%BackupBaseOutputFilename% echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo This removes the previous backup (if any) from %1 >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo del %FileSpec% >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 del %FileSpec% >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if exist %FileSpec% echo Error: %FileSpec% is still there. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if exist %FileSpec% set /A CopyFailures += 1 echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo This copies the current backup to %1 >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 echo copy %BackupOutputPathFile% %FileSpec% >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 copy %BackupOutputPathFile% %FileSpec% >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if not exist %FileSpec% echo Error: %FileSpec% is not there. >> ""{BackupDoneScriptOutputPathFile}"" 2>&1 if not exist %FileSpec% set /A CopyFailures += 1 " ) .Replace("{ProfileFile}", Path.GetFileName(moProfile.sLoadedPathFile)) .Replace("{BackupDoneScriptOutputPathFile}", Path.GetFileName(lsBackupDoneScriptOutputPathFile)) .Replace("{BackupDriveToken}", this.sBackupDriveToken) ; // Write the default "backup done" script if it's // not there or if -BackupDoneScriptInit is set. if ( !File.Exists(lsBackupDoneScriptPathFile) || moProfile.bValue("-BackupDoneScriptInit", false) ) { StreamWriter loStreamWriter = null; try { loStreamWriter = new StreamWriter(lsBackupDoneScriptPathFile, false); loStreamWriter.Write(lsBackupDoneScript); // This is used only once then reset. moProfile["-BackupDoneScriptInit"] = false; moProfile.Save(); } catch (Exception ex) { this.ShowError(string.Format("File Write Failure: \"{0}\"\r\n" , lsBackupDoneScript) + ex.Message , "Failed Writing File" ); } finally { if ( null != loStreamWriter ) loStreamWriter.Close(); } } } try { this.LogIt(""); this.LogIt("Running \"backup done\" script ..."); // Cache the arguments to be passed to the script. tvProfile loArgs = new tvProfile(); if ( abRerunLastArgs ) { loArgs.LoadFromCommandLine(moProfile.sValue("-BackupDoneArgs", ""), tvProfileLoadActions.Append); } else { loArgs.Add("-BackupOutputPathFile" , msCurrentBackupOutputPathFile ); loArgs.Add("-BackupOutputFilename" , Path.GetFileName(msCurrentBackupOutputPathFile) ); loArgs.Add("-BackupBaseOutputFilename" , Path.GetFileName(this.sBackupOutputPathFileBase()) ); loArgs.Add("-LocalArchivePath" , this.sArchivePath() ); loArgs.Add("-VirtualMachineHostArchivePath" , moProfile.sValue("-VirtualMachineHostArchivePath", "") ); loArgs.Add("-LogPathFile" , moProfile.sRelativeToProfilePathFile(this.sLogPathFile) ); moProfile["-BackupDoneArgs"] = loArgs.sCommandBlock(); moProfile.Save(); } // Run the "backup done" script. Process loProcess = new Process(); loProcess.StartInfo.FileName = lsBackupDoneScriptPathFile; loProcess.StartInfo.Arguments = string.Format( " \"{0}\" \"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\" \"{7}\" \"{8}\" \"{9}\" " , loArgs.sValue("-BackupOutputPathFile" , "") , loArgs.sValue("-BackupOutputFilename" , "") , loArgs.sValue("-BackupBaseOutputFilename" , "") , loArgs.sValue("-LocalArchivePath" , "") , loArgs.sValue("-VirtualMachineHostArchivePath", "") , loArgs.sValue("-LogPathFile" , "") , "" , "" , "" , "" ); loProcess.StartInfo.UseShellExecute = true; loProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; loProcess.Start(); // Wait for the "backup done" script to finish. while ( !this.bMainLoopStopped && !loProcess.HasExited ) { System.Windows.Forms.Application.DoEvents(); System.Threading.Thread.Sleep(moProfile.iValue("-MainLoopSleepMS", 100)); } // If a stop request came through, kill the "backup done" script. if ( this.bMainLoopStopped && !this.bKillProcess(loProcess) ) this.ShowError("The \"backup done\" script could not be stopped." , "Backup Failed"); if ( !this.bMainLoopStopped ) { // The exit code is defined in the script as a combination of two integers: // a bit field of found backup devices and a count of copy failures (99 max). liBackupDoneScriptCopyFailuresWithBitField = loProcess.ExitCode; double ldCompositeResult = liBackupDoneScriptCopyFailuresWithBitField / 100.0; int liCurrentBackupDevicesBitField = (int)ldCompositeResult; // The integer part is the bit field. // The fractional part (x 100) is the number of copy failures. int liBackupDoneScriptCopyFailures = (int)Math.Round(100 * (ldCompositeResult - liCurrentBackupDevicesBitField)); // Compare the bit field of current backup devices to the bit field of devices selected by the user. List<char> loMissingBackupDevices = this.oMissingBackupDevices(liCurrentBackupDevicesBitField); if (0 == liBackupDoneScriptCopyFailures && 0 == loMissingBackupDevices.Count) { this.LogIt("The \"backup done\" script finished successfully."); } else { if ( 0 != liBackupDoneScriptCopyFailures ) { this.LogIt(string.Format("The \"backup done\" script had {0} copy failure{1}.\r\n" , liBackupDoneScriptCopyFailures , 1 == liBackupDoneScriptCopyFailures ? "" : "s") ); // Get the output from the "backup done" script. string lsFileAsStream = this.sFileAsStream(lsBackupDoneScriptOutputPathFile); this.LogIt("Here's output from the \"backup done\" script:\r\n\r\n" + lsFileAsStream); if ( moProfile.bValue("-ShowBackupDoneScriptErrors", true) ) this.DisplayFileAsErrors(lsFileAsStream, "Backup Done Script Errors"); } if ( 0 != loMissingBackupDevices.Count ) this.LogIt(string.Format("The \"backup done\" script noticed {0} backup device{1} missing.\r\n" , loMissingBackupDevices.Count , 1 == loMissingBackupDevices.Count ? "" : "s") ); } } loProcess.Close(); } catch (Exception ex) { ++liBackupDoneScriptCopyFailuresWithBitField; this.SetBackupFailed(); this.ShowError(ex.Message, "Failed Running \"Backup Done\" Script"); } return liBackupDoneScriptCopyFailuresWithBitField; }
/// <summary> /// Returns the "BackupOutputPathFile" base name from the given backup set profile. /// This is includes everything in the path\file specification except the embedded date. /// </summary> private string sBackupOutputPathFileBase(tvProfile aoBackupSetProfile) { string lsBackupOutputPathFileBase = ""; if ( null != aoBackupSetProfile ) lsBackupOutputPathFileBase = Path.Combine(this.sArchivePath(aoBackupSetProfile) , aoBackupSetProfile.sValue("-OutputFilename", "Files")) + moProfile.sValue("-BackupOutputExtension", ".zip"); return lsBackupOutputPathFileBase; }
private void GetSetConfigurationDefaults() { tvProfile loBackupSet1Profile = new tvProfile(moProfile.sValue("-BackupSet", "(not set)")); if ( !mbGetDefaultsDone ) { try { // General this.CleanupFiles.IsChecked = moProfile.bValue("-CleanupFiles", true); this.BackupFiles.IsChecked = moProfile.bValue("-BackupFiles", true); this.BackupBeginScriptEnabled.IsChecked = moProfile.bValue("-BackupBeginScriptEnabled", true); this.BackupDoneScriptEnabled.IsChecked = moProfile.bValue("-BackupDoneScriptEnabled", true); // Step 1 this.FolderToBackup.Text = loBackupSet1Profile.sValue("-FolderToBackup", Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)); // Step 2 this.BackupOutputFilename.Text = loBackupSet1Profile.sValue("-OutputFilename", string.Format("{0}Files", Environment.GetEnvironmentVariable("USERNAME"))); this.ArchivePath.Text = moDoGoPcBackup.sArchivePath(); this.UseVirtualMachineHostArchive.IsChecked = moProfile.bValue("-UseVirtualMachineHostArchive", false); this.VirtualMachineHostArchivePath.Text = moProfile.sValue("-VirtualMachineHostArchivePath", ""); this.UseConnectVirtualMachineHost.IsChecked = moProfile.bValue("-UseConnectVirtualMachineHost", false); this.VirtualMachineHostUsername.Text = moProfile.sValue("-VirtualMachineHostUsername", ""); this.VirtualMachineHostPassword.Text = moProfile.sValue("-VirtualMachineHostPassword", ""); // Step 3 // see below // Step 4 this.BackupTime.Text = moProfile.sValue("-BackupTime", "12:00 AM"); this.sldBackupTime_ValueFromString(this.BackupTime.Text); } catch (Exception ex) { msGetSetConfigurationDefaultsError = ex.Message; } mbGetDefaultsDone = true; } // Step 3 // If the user merely looks at the backup devices tab, update the profile. if ( this.ConfigWizardTabs.SelectedIndex == ItemsControl.ItemsControlFromItemContainer( this.tabStep3).ItemContainerGenerator.IndexFromContainer(this.tabStep3) ) mbUpdateSelectedBackupDevices = true; // The removal or insertion of external devices will be // detected whenever the "Setup Wizard" button is clicked. if ( 0 == gridBackupDevices.Children.Count ) { string lsFirstDriveLetter = moDoGoPcBackup.cPossibleDriveLetterBegin.ToString(); int liRow = 0; int liColumn = 0; // Add each drive (starting with lsFirstDriveLetter) to the list of checkboxes. foreach (DriveInfo loDrive in DriveInfo.GetDrives()) { try { if ( String.Compare(lsFirstDriveLetter, loDrive.Name) < 0 ) { CheckBox loCheckBox = new CheckBox(); loCheckBox.Width = 200; loCheckBox.Tag = loDrive; // If the drive has a valid volume label, display it alongside the drive name. try { loCheckBox.Content = "(" + loDrive.Name.Substring(0, 2) + ") " + loDrive.VolumeLabel; } // Otherwise, display the drive name by itself. catch { loCheckBox.Content = "(" + loDrive.Name.Substring(0, 2) + ") "; } // Add a CheckBox to the tab to represent the drive. gridBackupDevices.Children.Add(loCheckBox); Grid.SetRow(loCheckBox, liRow); Grid.SetColumn(loCheckBox, liColumn); // Arrange the CheckBoxes such that a new column is formed for every 8 CheckBoxes. if ( liRow < 7 ) { ++liRow; } else { liRow = 0; ++liColumn; } string lsTokenPathFile = Path.Combine((loCheckBox.Tag as DriveInfo).Name, moDoGoPcBackup.sBackupDriveToken); try { File.Create(lsTokenPathFile + ".test").Close(); File.Delete(lsTokenPathFile + ".test"); loCheckBox.Foreground = Brushes.DarkGreen; } catch { loCheckBox.Foreground = Brushes.Red; loCheckBox.IsEnabled = false; } // If the BackupDriveToken is already on a drive, set the drive's CheckBox to 'checked.' if ( File.Exists(lsTokenPathFile) ) loCheckBox.IsChecked = true; // Create or delete the BackupDriveToken from the drive whenever it is checked or unchecked. loCheckBox.Checked += new RoutedEventHandler(BackupDeviceCheckboxStateChanged); loCheckBox.Unchecked += new RoutedEventHandler(BackupDeviceCheckboxStateChanged); } } catch {} } } // Finish this.ReviewFolderToBackup.Text = this.FolderToBackup.Text; this.ReviewOutputFilename.Text = this.BackupOutputFilename.Text; this.ReviewArchivePath.Text = this.ArchivePath.Text; this.ReviewBackupTime.Text = this.BackupTime.Text; tvProfile loSelectedBackupDevices = new tvProfile(); string lsSelectedDrives = ""; // Generate a string with the content of each CheckBox that the user checked in Step 4. foreach (CheckBox loCheckBox in gridBackupDevices.Children) { if ((bool)loCheckBox.IsChecked) { loSelectedBackupDevices.Add("-Device", loSelectedBackupDevices.sSwapHyphens(loCheckBox.Content.ToString())); lsSelectedDrives += loCheckBox.Content.ToString().Substring(0, 5); } } this.ReviewAdditionalDevices.Text = lsSelectedDrives; loBackupSet1Profile["-FolderToBackup"] = this.ReviewFolderToBackup.Text; loBackupSet1Profile["-OutputFilename"] = this.ReviewOutputFilename.Text; if ( null == msGetSetConfigurationDefaultsError ) { moProfile["-BackupSet"] = loBackupSet1Profile.sCommandBlock(); moProfile["-ArchivePath"] = this.ReviewArchivePath.Text; moProfile["-BackupTime"] = this.ReviewBackupTime.Text; moProfile["-UseVirtualMachineHostArchive"] = this.UseVirtualMachineHostArchive.IsChecked; moProfile["-VirtualMachineHostArchivePath"] = this.VirtualMachineHostArchivePath.Text; moProfile["-UseConnectVirtualMachineHost"] = this.UseConnectVirtualMachineHost.IsChecked; moProfile["-VirtualMachineHostUsername"] = this.VirtualMachineHostUsername.Text; moProfile["-VirtualMachineHostPassword"] = this.VirtualMachineHostPassword.Text; // Only update the selected backup devices list (and bit field) if the backup devices // tab has been viewed or one of the backup device checkboxes has been clicked. if ( mbUpdateSelectedBackupDevices ) { // Make the list of selected backup devices a multi-line block by inserting newlines before hyphens. moProfile["-SelectedBackupDevices"] = loSelectedBackupDevices.sCommandBlock(); moProfile["-SelectedBackupDevicesBitField"] = Convert.ToString(this.iSelectedBackupDevicesBitField(), 2); mbUpdateSelectedBackupDevices = false; } moProfile["-CleanupFiles"] = this.CleanupFiles.IsChecked; moProfile["-BackupFiles"] = this.BackupFiles.IsChecked; moProfile["-BackupBeginScriptEnabled"] = this.BackupBeginScriptEnabled.IsChecked; moProfile["-BackupDoneScriptEnabled"] = this.BackupDoneScriptEnabled.IsChecked; moProfile.Save(); } // Reset the loop timer only if the backup time was changed in the configuration. if ( !this.bMainLoopStopped && this.BackupTime.Text != msPreviousBackupTime ) { msPreviousBackupTime = this.BackupTime.Text; mdtNextStart = DateTime.MinValue; this.bMainLoopRestart = true; } }