' Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. ' ' This program is free software; you can redistribute it and/or ' modify it under the terms of the GNU General Public License as ' published by the Free Software Foundation; version 2 of the ' License. ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY; without even the implied warranty of ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' GNU General Public License for more details. ' ' You should have received a copy of the GNU General Public License ' along with this program; if not, write to the Free Software ' Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA ' 02110-1301 USA ' VBS doesn't provide sorting out of the box function qsort(arr, first, last) if first < last then p = qsort_partition(arr, first, last) call qsort(arr, first, p - 1) call qsort(arr, p + 1, last) end if end function function qsort_partition(arr, first, last) pivot = arr(last) i = first for j = first to last - 1 if arr(j) <= pivot then ' swap tmp = arr(i) arr(i) = arr(j) arr(j) = tmp i = i + 1 end if next ' swap tmp = arr(i) arr(i) = arr(last) arr(last) = tmp qsort_partition = i end function ' These variables will be retrieved from the backup profile class JSONDumper public function json_object(element) element_type = TypeName(element) select case element_type case "Dictionary" fs = "" for each item in element.Keys att = json_object(item) val = json_object(element(item)) att_val = att & ":" & val if len(fs) > 0 then fs = fs & "," fs = fs & att_val next json_object = "{" & fs & "}" case "String" ' Holds the final string fs = "" for index = 1 to len (element) next_char = mid(element, index, 1) select case next_char case """" fs = fs & "\""" case "\" fs = fs & "\\" case else fs = fs & next_char end select next json_object = """" & fs & """" case "Boolean" if element then json_object = "True" else json_object = "False" end if end select end function end class class ConfigReader private my_file private my_file_handler public configuration private sub Class_Initialize() Set configuration = CreateObject("Scripting.Dictionary") end sub public sub load(file_name) set fso = CreateObject("Scripting.FileSystemObject") set my_file_handler = fso.OpenTextFile(file_name) set current_section = Nothing while not my_file_handler.AtEndOfStream strLine = my_file_handler.ReadLine() strLine = TRIM(strLine) lineLen = LEN(strLine) ' Handles a found section if Left(strLine, 1) = "[" and Right(strLine, 1) = "]" then section_name = Mid(strLine, 2, lineLen - 2) if configuration.exists(section_name) then set current_section = configuration(section_name) else set current_section = CreateObject("Scripting.Dictionary") configuration.add section_name, current_section end if ' Ignores comment lines and processes the rest as items of the current section else if Left(strLine,1) <> "#" then index = INSTR(1, strLine, "=", 1) if index > 1 then attName = LCase(Trim(Mid(strLine, 1, index - 1))) attValue = Trim(Mid(strLine, index+1, lineLen - index)) if not current_section is Nothing then current_section.add attName, attValue end if end if end if end if wend my_file_handler.close() end sub public function get_value(section, item, default) found = false value = "" if configuration.exists(section) then set section_data = configuration(section) if section_data.exists(item) then value = section_data(item) found = true end if end if if found then get_value = value else get_value = default end if end function end class class ConfigUpdater public config private sub Class_Initialize() Set config = new ConfigReader end sub public sub load_config(file_name) config.load(file_name) end sub public sub update(file_name) set fso = CreateObject("Scripting.FileSystemObject") set my_source_handler = fso.OpenTextFile(file_name) set my_target_handler = fso.CreateTextFile(file_name & ".tmp", true) set current_section = Nothing while not my_source_handler.AtEndOfStream strLine = my_source_handler.ReadLine() strLine = TRIM(strLine) lineLen = LEN(strLine) ' Handles a found section if Left(strLine, 1) = "[" and Right(strLine, 1) = "]" then section_name = Mid(strLine, 2, lineLen - 2) if config.configuration.exists(section_name) then set current_section = config.configuration(section_name) else set current_section = CreateObject("Scripting.Dictionary") end if else if Left(strLine,1) <> "#" then index = INSTR(1, strLine, "=", 1) if index > 1 then attName = LCase(Trim(Mid(strLine, 1, index - 1))) attValue = Trim(Mid(strLine, index+1, lineLen - index)) if current_section.exists(attName) then strLine = attName & " = " & current_section(attName) end if end if end if end if my_target_handler.WriteLine(strLine) wend my_source_handler.close() my_target_handler.close() fso.DeleteFile file_name fso.MoveFile file_name & ".tmp", file_name end sub end class class Utilities private shell private sub Class_Initialize() set shell = WScript.CreateObject("WScript.Shell") end sub public function check_version_at_least(command, major, minor, revno) set exec = shell.Exec(command & " --version") output = "" do while exec.status = 0 output = output & exec.StdErr.ReadAll() loop tokens = split(output, " ") version = "" found_major = 0 found_minor = 0 found_revno = 0 for each token in tokens if token = "version" then version = "found" else if version = "found" then version = token version_tokens = split(version, ".") found_major = Int(version_tokens(0)) found_minor = Int(version_tokens(1)) found_revno = Int(version_tokens(2)) end if end if next if found_major > major or _ (found_major = major and found_minor > minor) or _ (found_major = major and found_minor = minor and found_revno >= revno) then check_version_at_least = True else check_version_at_least = False end if end function end class class MEBCommandProcessor private help_needed private command_name private sub Class_Initialize() help_needed = false command_name = "" end sub private function read_params() ret_val = True if Wscript.Arguments.Count >= 1 then command_name = UCase(Wscript.Arguments.Item(0)) if command_name = "HELP" then help_needed = true if Wscript.Arguments.Count >= 2 then command_name = UCase(Wscript.Arguments.Item(1)) end if end if end if if command_name = "" and not help_needed then Wscript.Echo "Error executing helper, use it as follows:" ret_val = false end if read_params = ret_val end function private sub print_usage() Wscript.Echo "mysqlwbmeb " Wscript.Echo Wscript.Echo "WHERE : : is one of HELP, VERSION, BACKUP, GET_PROFILES, UPDATE_SCHEDULING, PROPAGATE_DATA" Wscript.Echo " : are the parameters needed for each command." Wscript.Echo Wscript.Echo "You can also use as follows to get each command parameters:" Wscript.Echo Wscript.Echo "mysqlwbmeb help " Wscript.Echo "WHERE : : is one of VERSION, BACKUP, GET_PROFILES, UPDATE_SCHEDULING, PROPAGATE_DATA" end sub public function execute() ret_val = 1 if read_params() then set command = Nothing select case command_name case "VERSION" set command = new MEBHelperVersion case "BACKUP" set command = new MEBBackup case "GET_PROFILES" set command = new MEBGetProfiles case "UPDATE_SCHEDULING" set command = new MEBUpdateScheduling case "PROPAGATE_DATA" set command = new MEBPropagateSettings end select if command is Nothing then if not help_needed then Wscript.Echo "ERROR Executing mysqlwbmeb" print_usage() else if help_needed then command.print_usage() ret_val = 0 else ret_val = command.execute() end if end if else print_usage() end if execute = ret_val end function end class class MEBHelperVersion private current private sub Class_Initialize() current = "10" end sub public function execute() Wscript.Echo current execute = 0 end function end class class MEBBackup ' These will be received as parameters private profile_file private compress private compress_method private compress_level private incremental private to_single_file private report_progress private bkcommand ' These will be read from the profile file private command private backup_dir private inc_backup_dir private log_path private backups_home private target_folder private command_call private use_tts private skip_unused_pages private incremental_with_redo_log_only private use_encryption_password private encryption_password ' These are for internal use private shell private file_name private fso private sub Class_Initialize() file_name = "" set fso = CreateObject("Scripting.FileSystemObject") ' Sets the backups home to the parent folder of this script backups_home = fso.GetParentFolderName(Wscript.ScriptFullName) skip_unused_pages = false end sub private function read_params() if Wscript.Arguments.Count >= 7 then profile_file = Wscript.Arguments.Item(1) compress = Wscript.Arguments.Item(2) incremental = Wscript.Arguments.Item(3) to_single_file = Wscript.Arguments.Item(4) report_progress = Wscript.Arguments.Item(5) bkcommand = Wscript.Arguments.Item(6) if Wscript.Arguments.Count > 7 then file_name = Wscript.Arguments.Item(7) end if read_params = true else read_params = false end if end function sub read_profile_data() set profile = new ConfigReader profile.load(backups_home & "\" & profile_file) command = profile.get_value("meb_manager", "command", "") backup_dir = profile.get_value("mysqlbackup", "backup_dir", "") inc_backup_dir = profile.get_value("mysqlbackup", "incremental_backup_dir", "") use_tts = profile.get_value("meb_manager", "using_tts", "0") if profile.get_value("meb_manager", "compress", "False") = "True" then compress = true end if compress_method = profile.get_value("meb_manager", "compress_method", "lz4") compress_level = profile.get_value("meb_manager", "compress_level", "1") if profile.get_value("meb_manager", "skip_unused_pages", "False") = "True" then skip_unused_pages = true end if if profile.get_value("meb_manager", "incremental_with_redo_log_only", "False") = "True" then incremental_with_redo_log_only = true end if if profile.get_value("meb_manager", "use_encryption_password", "False") = "True" then use_encryption_password = true end if encryption_password = profile.get_value("meb_manager", "encryption_password", "") end sub ' Function used to create the target name in case it is a timestamp function get_tstamp_folder_name(ts) ts_short = FormatDateTime(ts, vbShortTime) ' Formats the elements to be 2 chars length with leading 0's ' if needed the_month = Right("0" & Month(ts), 2) the_day = Right("0" & Day(ts), 2) the_hour = Right("0" & MID(ts_short, 1, InStr(ts_short, ":") -1), 2) the_minute = Right("0" & Minute(ts), 2) the_second = Right("0" & Second(ts), 2) get_tstamp_folder_name = Year(ts) & "-" & the_month & "-" & the_day & "_" & the_hour & "-" & the_minute & "-" & the_second end function private sub set_backup_paths target_folder = "" ' Defines the target file/folder name for the backup if file_name <> "" then if to_single_file then if LCase(Right(file_name, 4)) <> ".mbi" then file_name = file_name & ".mbi" end if ' On an image backup the backup dir will be the one ' Received as a parameter backup_dir = Left(file_name, Len(file_name) - 4 ) log_path = replace(file_name, ".mbi", ".log") else ' If a file name is passed it is used as the backup folder target_folder = file_name end if else ' If no file name is passed, uses the timestamp to create one target_folder = get_tstamp_folder_name(Now()) end if ' The full path is the target folder under the backups home for ' the profile if target_folder <> "" then backup_dir = backup_dir & "\" & target_folder log_path = backup_dir & ".log" end if end sub private function get_incremental_base_folder base_folder = "" base_tstamp = CDate("1900-01-01 00:00:00") lastest_full = find_lastest_backup(backup_dir, base_tstamp) if lastest_full > base_tstamp then base_folder = backup_dir base_tstamp = lastest_full lastest_inc = find_lastest_backup(inc_backup_dir, lastest_full) if lastest_inc > lastest_full then base_folder = inc_backup_dir base_tstamp = lastest_inc end if end if if base_folder <> "" then folder = get_tstamp_folder_name(base_tstamp) base_folder = base_folder & "\" & folder end if get_incremental_base_folder = base_folder end function private function get_folders_in_newest_to_oldest_order(path) ' Gets the folders on the target path set tgt_folder = fso.GetFolder(path) set folder_list = tgt_folder.SubFolders ' copy folder_list to a native array arr = Array() for each folder in folder_list last = UBound(arr) redim preserve arr(last + 1) arr(last + 1) = folder.path next ' sort folder names in reverse order arr_ub = UBound(arr) call qsort(arr, 0, arr_ub) redim sorted_folder_list(arr_ub + 1) for i = 0 to arr_ub sorted_folder_list(arr_ub - i) = arr(i) next get_folders_in_newest_to_oldest_order = sorted_folder_list end function private function find_lastest_backup(path, basetstamp) lastest = basetstamp ' Creates the regular expression to match the timestamo ' folder names used un meb set regExp = CreateObject("VBScript.RegExp") regExp.Global = true regExp.Pattern = "(\b[1-9]\d\d\d\b)-([0-1]\d)-([0-3]\d)_([0-2]\d)-([0-5]\d)-([0-5]\d)" folder_list = get_folders_in_newest_to_oldest_order(path) for each folder in folder_list set matches = regExp.Execute(folder) if matches.Count = 1 then if is_backup_dir_valid(folder) then dim match, year, month, day, hour, minute, second set match = matches(0).SubMatches ' Gets the data for the matched folder year = match(0) month = match(1) day = match(2) hour = match(3) minute = match(4) second = match(5) ' Compares the new tstamp with the base and if greather (more recent) ' then replaces it lastest = CDate(year & "-" & month & "-" & day & " " & hour & ":" & minute & ":" & second) exit for end if end if next find_lastest_backup = lastest end function private function is_backup_dir_valid(folder) ' create regex for detecting backup success set backup_ok_flag_re = CreateObject("VBScript.RegExp") backup_ok_flag_re.Pattern = "^mysqlbackup completed OK!$" ' according to http://dev.mysql.com/doc/mysql-enterprise-backup/3.10/en/mysqlbackup.html, we can rely on this appearing at the end of the backup log when the backup succeeds log_filename = folder & ".log" ' each /path/to/backup/dir has a corresponding /path/to/backup/dir.log ' open log file on error resume next ' ensure the errors aren't fatal err.Clear set f = fso.OpenTextFile(log_filename) ' on Windows, this may fail when opening the current log file due to file locking ' grep log file for the flag message is_backup_dir_valid = false if err.Number = 0 then ' if (opening file succeded) do until f.AtEndOfStream line = f.ReadLine if backup_ok_flag_re.Test(line) then is_backup_dir_valid = true exit do end if loop f.close else err.Clear end if end function private function prepare_command set my_utils = new Utilities ret_val = True command_call = """" & command & """ --defaults-file=""" & backups_home & "\" & profile_file & """" ' Adds the compress parameter if needed if compress then if my_utils.check_version_at_least(command, 3,10,0) then ' lz4 is the default so id it is selected only sets the --compress option if compress_method = "lz4" then command_call = command_call & " --compress" ' Otherwise using --compress-method makes --compress to be not needed else command_call = command_call & " --compress-method=" & compress_method ' Level is only specified if not using the default value if compress_level <> "1" then command_call = command_call & " --compress-level=" & compress_level end if end if else command_call = command_call & " --compress" end if end if ' Get the right path parameter, path and running type path_param = " --backup-dir" ' If the backup is incremental if incremental then base_folder = get_incremental_base_folder() if base_folder <> "" then if incremental_with_redo_log_only then command_call = command_call & " --incremental-with-redo-log-only --incremental-base=dir:" & """" & base_folder & """" else command_call = command_call & " --incremental --incremental-base=dir:" & """" & base_folder & """" end if backup_dir = inc_backup_dir path_param = " --incremental-backup-dir" else Wscript.Echo "ERROR: Unable to run incremental backup without a base folder." ret_val = false end if else if skip_unused_pages then command_call = command_call & " --skip-unused-pages" end if end if ' Sets the needed backup paths set_backup_paths() ' Adds the backup folder to the command command_call = command_call & " " & path_param & "=""" & backup_dir & """" if to_single_file then command_call = command_call & " --backup-image=""" & file_name & """" if use_tts <> "0" then tts_value="with-minimum-locking" if use_tts = 2 then tts_value="with-full-locking" end if command_call = command_call & " --use-tts=" & tts_value end if if report_progress then command_call = command_call & " --show-progress=stdout" if use_encryption_password then command_call = command_call & " --encrypt-password=" & encryption_password end if command_call = command_call & " " & bkcommand & " > """ & log_path & """ 2>&1" prepare_command = ret_val end function public sub print_usage() Wscript.Echo "BACKUP " Wscript.Echo "WHERE : : is the UUID of the profile to be used for the backup process." Wscript.Echo " : indicates if the backup will be compressed. (1 or 0)" Wscript.Echo " : indicates the backup should be incremental. (1 or 0)" Wscript.Echo " : indicates the backup will be done to an image file. (1 or 0)" Wscript.Echo " : indicates the backup should be compressed. (1 or 0)" Wscript.Echo " : indicates the backup operation to be done, could be backup or backup-and-apply-log" Wscript.Echo " [file_name] : indicates the target name for the backup file or folder" Wscript.Echo Wscript.Echo end sub public function execute() ret_val = 1 if read_params() then read_profile_data() ' Creates the scripting shell to execute the backup set shell = WScript.CreateObject("WScript.Shell") if prepare_command() then shell.Run "%comspec% /c """ & command_call & " & del *.running""", 0 set dictionary = CreateObject("Scripting.Dictionary") dictionary.add "LOG_PATH", log_path set dumper = new JSONDumper WScript.Echo dumper.json_object(dictionary) ret_val = 0 end if else print_usage() end if execute = ret_val end function end class class MEBUpdateScheduling ' These will be received as parameters private change private profile_file private old_label private old_fb_schedule private old_ib_schedule ' These will be read from the profile file private profile private uuid private new_label private new_fb_schedule private new_ib_schedule ' These are for internal use private fso private backups_home private frequency private month_day private week_day private hour private minute private compress private apply_log private sub Class_Initialize() file_name = "" set fso = CreateObject("Scripting.FileSystemObject") ' Sets the backups home to the parent folder of this script backups_home = fso.GetParentFolderName(Wscript.ScriptFullName) ' Initializes these to False so they serve their purpose ' in the case of a DELETE new_fb_schedule = "False" new_ib_schedule = "False" end sub private function read_params() read_params = false if Wscript.Arguments.Count > 2 then change = Wscript.Arguments.Item(1) if change = "NEW" then param_count = 3 else param_count = 6 end if if Wscript.Arguments.Count = param_count then profile_file = Wscript.Arguments.Item(2) if change <> "NEW" then old_label = Wscript.Arguments.Item(3) old_fb_schedule = Wscript.Arguments.Item(4) old_ib_schedule = Wscript.Arguments.Item(5) end if read_params = true end if end if end function sub init_profile() set profile = new ConfigReader file_name = backups_home & "\" & profile_file & ".cnf" profile.load(file_name) new_label = profile.get_value("meb_manager", "label", "") uuid = profile.get_value("meb_manager", "uuid", "") new_fb_schedule = profile.get_value("meb_manager", "full_backups_enabled", "") new_ib_schedule = profile.get_value("meb_manager", "inc_backups_enabled", "") compress = profile.get_value("meb_manager", "compress", "") apply_log = profile.get_value("meb_manager", "apply_log", "") end sub sub read_profile_data(backup_type) frequency = profile.get_value("meb_manager", backup_type & "_backups_frequency", "") month_day = profile.get_value("meb_manager", backup_type & "_backups_month_day", "") week_day = profile.get_value("meb_manager", backup_type & "_backups_week_days", "") hour = profile.get_value("meb_manager", backup_type & "_backups_hour", "") minute = profile.get_value("meb_manager", backup_type & "_backups_minute", "") end sub private function create_backup_command_call(backup_type) command = "cscript /nologo \""" & Wscript.ScriptFullName & "\""" command = command & " BACKUP " & uuid & ".cnf" real_compress = "0" real_incremental = "0" if backup_type = "full" then if compress = "True" and apply_log = "False" then real_compress = "1" end if else real_incremental = "1" end if command = command & " " & real_compress command = command & " " & real_incremental command = command & " 0" ' To single file command = command & " 1" ' Report Progress if apply_log = "True" and backup_type = "full" then command = command & " backup-and-apply-log" else command = command & " backup" end if create_backup_command_call = command end function private function get_unschedule_command(backup_type) get_unschedule_command = "schtasks /Delete /TN ""MySQLBackup\" & uuid & "-" & old_label & "-" & backup_type & """ /F" end function private function get_schedule_command(backup_type) command = "schtasks /Create /TN ""MySQLBackup\" & uuid & "-" & new_label & "-" & backup_type & """ /SC " hour = right("00" & hour, 2) minute = right("00" & minute, 2) select case frequency case "0" 'hourly command = command & "HOURLY /ST 00:" & minute case "1" 'Daily command = command & "DAILY /ST " & hour & ":" & minute case "2" 'Weekly weekdays = array("SUN", "MON","TUE","WED","THU","FRI","SAT") wd_string = "" wd_numbers = split(week_day, ",") for each day_number in wd_numbers day_index = Int(day_number) wd_string = wd_string & weekdays(day_index) & "," next wd_string= left(wd_string, len(wd_string) - 1) command = command & "WEEKLY /D " & wd_string & " /ST " & hour & ":" & minute case "3" 'Monthly command = command & "MONTHLY /D " & month_day & " /ST " & hour & ":" & minute end select get_schedule_command = command & " /RU SYSTEM /TR """ & create_backup_command_call(backup_type) & """ /F" end function public sub print_usage() Wscript.Echo "UPDATE_SCHEDULING " Wscript.Echo "WHERE : : Indicates the operation being done with the profile: NEW, UPDATE, DELETE." Wscript.Echo " : is the UUID of the profile to be used for the scheduling." Wscript.Echo " [] : indicates the label under which the jobs were scheduled. Applies on UPDATE and DELETE changes." Wscript.Echo " [] : indicates if the profile was scheduled for full backup. (0 or 1). Applies on UPDATE and DELETE changes." Wscript.Echo " []: indicates if the profile was scheduled for incremental backup. (0 or 1). Applies on UPDATE and DELETE changes." Wscript.Echo Wscript.Echo end sub public function exec_command(shell, command) set exec = shell.Exec(command) do while exec.status = 0 WScript.Echo exec.StdOut.ReadAll() WScript.Echo exec.StdErr.ReadAll() loop exec_command = exec.exitcode end function public function execute() ret_val = 1 if read_params() then ret_val = 0 set shell = WScript.CreateObject("WScript.Shell") ' Profile data would NOT be read on DELETE actions. if change <> "DELETE" then init_profile() end if ' Unscheduling would NOT occur on NEW profiles if change <> "NEW" then if old_label <> new_label or old_fb_schedule = "1" and new_fb_schedule = "False" then command = get_unschedule_command("full") ret_val = ret_val + exec_command(shell, command) end if if old_label <> new_label or (old_ib_schedule = "1" and new_ib_schedule = "False") then command = get_unschedule_command("inc") ret_val = ret_val + exec_command(shell, command) end if end if if new_fb_schedule = "True" then read_profile_data("full") command = get_schedule_command("full") ret_val = ret_val + exec_command(shell, command) end if if new_ib_schedule = "True" then read_profile_data("inc") command = get_schedule_command("inc") ret_val = ret_val + exec_command(shell, command) end if else print_usage() end if execute = ret_val end function end class class MEBPropagateSettings private datadir private datafile private fso private backups_home private sub Class_Initialize() datadir = "" set fso = CreateObject("Scripting.FileSystemObject") ' Sets the backups home to the parent folder of this script backups_home = fso.GetParentFolderName(Wscript.ScriptFullName) end sub public function read_params() ret_val = false if Wscript.Arguments.Count = 2 then datafile = Wscript.Arguments.Item(1) ret_val = true end if read_params = ret_val end function public sub print_usage() Wscript.Echo "PROPAGATE_DATA " Wscript.Echo Wscript.Echo "WHERE : : is the path to the datadir of the server instance for which the profiles are" Wscript.Echo " being loaded. (There could be more than one instance on the same box)." Wscript.Echo end sub public function execute() ret_val = 1 if read_params() then ret_val = 0 ' Loads the information to be propagated set updater = new ConfigUpdater source_config_file = backups_home & "\" & datafile updater.load_config(source_config_file) datadir = updater.config.get_value("target", "datadir", "") if datadir <> "" then Set master_data = CreateObject("Scripting.Dictionary") Set backups_home_folder = fso.GetFolder(backups_home) Set all_files = backups_home_folder.Files for each file in all_files profile_issues = 0 if ucase(fso.GetExtensionName(file.name)) = "CNF" then set profile = new ConfigReader profile.load(backups_home & "\" & file.name) ' Verifies the datadir to ensure it belongs to the requested instance command = profile.get_value("meb_manager", "command", "") profile_datadir = profile.get_value("mysqlbackup", "datadir", "") if UCase(profile_datadir) = UCase(datadir) then updater.update(backups_home & "\" & file.name) end if end if next else WScript.Echo("Data propagation file is missing the target datadir.") end if ' Deletes the temporary file fso.DeleteFile source_config_file else print_usage() end if end function end class class MEBGetProfiles private datadir private fso private backups_home private meb_command private sub Class_Initialize() datadir = "" set fso = CreateObject("Scripting.FileSystemObject") ' Sets the backups home to the parent folder of this script backups_home = fso.GetParentFolderName(Wscript.ScriptFullName) end sub public function read_params() ret_val = false if Wscript.Arguments.Count = 3 then meb_command = Wscript.Arguments.Item(1) datadir = Wscript.Arguments.Item(2) if Right(datadir,1) <> "\" then datadir = datadir & "\" ret_val = true end if read_params = ret_val end function public sub print_usage() Wscript.Echo "GET_PROFILES " Wscript.Echo Wscript.Echo "WHERE : : is the path to a valid MEB executable" Wscript.Echo " : is the path to the datadir of the server instance for which the profiles are" Wscript.Echo " being loaded. (There could be more than one instance on the same box)." Wscript.Echo end sub public function execute() if read_params() then Set master_data = CreateObject("Scripting.Dictionary") Set backups_home_folder = fso.GetFolder(backups_home) Set all_files = backups_home_folder.Files for each file in all_files profile_issues = 0 if ucase(fso.GetExtensionName(file.name)) = "CNF" then set profile = new ConfigReader profile.load(backups_home & "\" & file.name) ' Verifies the datadir to ensure it belongs to the requested instance profile_command = profile.get_value("meb_manager", "command", "") ' Checks that the profile command matches the one received as parameter if profile_command <> meb_command then profile_issues = profile_issues or 16 end if profile_datadir = profile.get_value("mysqlbackup", "datadir", "") if UCase(profile_datadir) = UCase(datadir) then set data = CreateObject("Scripting.Dictionary") data.add "LABEL", profile.get_value("meb_manager", "label", "") data.add "PARTIAL", profile.get_value("meb_manager", "partial", "") data.add "USING_TTS", profile.get_value("meb_manager", "using_tts", "0") data.add "BACKUP_DIR", profile.get_value("mysqlbackup", "backup_dir", "") ' Gets the available space data.add "AVAILABLE", get_available_space(data("BACKUP_DIR")) ' Validates the backups folder for write permission if not is_dir_writable(data("BACKUP_DIR")) then profile_issues = profile_issues or 1 end if ' Gets the full schedule data e = profile.get_value("meb_manager", "full_backups_enabled", "") f = profile.get_value("meb_manager", "full_backups_frequency", "") md = profile.get_value("meb_manager", "full_backups_month_day", "") wd = profile.get_value("meb_manager", "full_backups_week_days", "") h = profile.get_value("meb_manager", "full_backups_hour", "") m = profile.get_value("meb_manager", "full_backups_minute", "") data.add "FSCHEDULE", e & "-" & f & "-" & md & "-" & wd & "-" & h & "-" & m ' Gets the incremental schedule data e = profile.get_value("meb_manager", "inc_backups_enabled", "") f = profile.get_value("meb_manager", "inc_backups_frequency", "") md = profile.get_value("meb_manager", "inc_backups_month_day", "") wd = profile.get_value("meb_manager", "inc_backups_week_days", "") h = profile.get_value("meb_manager", "inc_backups_hour", "") m = profile.get_value("meb_manager", "inc_backups_minute", "") data.add "ISCHEDULE", e & "-" & f & "-" & md & "-" & wd & "-" & h & "-" & m ' Validates Partial Backup Options set my_utils = new Utilities ' Profiles with version 0 could have an UUID as the value for the "include" field ' It was used to make all of the InnoDB tables NOT matching the selection criteria on initial ' versions of MEB. ' On MEB 3.9.0 this became invalid as an error would be generated so ' If found in a profile, we need to report the invalid configuration on the UI. profile_version = CInt(profile.get_value("meb_manager", "version", "0")) if profile_version = "0" and my_utils.check_version_at_least(meb_command, 3,9,0) then include = profile.get_value("mysqlbackup", "include", "") if include <> "" then set my_reg_exp = New RegExp my_reg_exp.Pattern = "^[\dA-Fa-f]{8}-([\dA-Fa-f]{4}-){3}[\dA-Fa-f]{12}$" set my_matches = my_reg_exp.Execute(include) if my_matches.count > 0 then profile_issues = profile_issues or 2 end if end if end if if my_utils.check_version_at_least(meb_command, 3,10,0) then include = profile.get_value("mysqlbackup", "include", "") databases = profile.get_value("mysqlbackup", "include", "") if len(include) > 0 or len(databases) > 0 then profile_issues = profile_issues or 8 end if else include = profile.get_value("mysqlbackup", "include_tables", "") exclude = profile.get_value("mysqlbackup", "exclude_tables", "") if len(include) > 0 or len(exclude) > 0 then profile_issues = profile_issues or 4 end if end if ' The VALID item will contain a numeric valid describing the issues encountered on the profile ' Validation. Each issue should be assigned a value of 2^x so the different issues can be joined ' using bitwise operations ' 1 : Indicates the backup folder is not valid to store the backups. ' 2 : Indicates a partial backup profile using a regular expression on the include parameter. ' data.add "VALID", CStr(profile_issues) master_data.add backups_home & "\" & file.name, data end if end if next ' If any, prints the profile data if master_data.count > 0 then set dumper = new JSONDumper Wscript.Echo dumper.json_object(master_data) execute = 0 end if else: print_usage() execute = 1 end if end function private function is_dir_writable(path) ret_val = "False" if fso.FolderExists(path) then set folder = fso.GetFolder(path) set file = folder.CreateTextFile("test.txt") if not file is Nothing then ret_val = "True" file.Close() fso.DeleteFile(path & "\" & "test.txt") end if end if is_dir_writable = ret_val end function private function get_available_space(path): suffixes = Array("B", "KB", "MB", "GB", "TB", "PB", "EB") set drive = fso.GetDrive(fso.GetDriveName(path)) total = drive.TotalSize available = drive.AvailableSpace limit = 1024 index = 0 while total > limit total = total/limit available = available/limit index = index + 1 wend get_available_space = CStr(FormatNumber(available, 2)) & CStr(suffixes(index)) & " of " & CStr(FormatNumber(total, 2)) & CStr(suffixes(index)) & " available." end function end class set processor = new MEBCommandProcessor WScript.Quit(processor.execute())