diff --git a/TODO..md b/TODO..md index 4f9e347..c5c5f7d 100755 --- a/TODO..md +++ b/TODO..md @@ -1,11 +1,8 @@ ### File System - recursive ls -- ls a single file -- rm with wildcard ### Task Scheduler - all in one (create, execute, delete) -- add task run modifiers (run every X min/hour) - load task xml ### Registry diff --git a/src/slingerpkg/__init__.py b/src/slingerpkg/__init__.py index 75cf27e..576f0b1 100755 --- a/src/slingerpkg/__init__.py +++ b/src/slingerpkg/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.4.0' +__version__ = '0.5.0' __package__ = 'slinger' \ No newline at end of file diff --git a/src/slingerpkg/lib/dcetransport.py b/src/slingerpkg/lib/dcetransport.py index ec1c791..c5bfbd0 100755 --- a/src/slingerpkg/lib/dcetransport.py +++ b/src/slingerpkg/lib/dcetransport.py @@ -231,11 +231,8 @@ def _create_task(self, task_name, folder_path, task_xml): flags = tsch.TASK_CREATE | tsch.TASK_FLAG_SYSTEM_REQUIRED | tsch.TASK_FLAG_HIDDEN sddl = '' # Security descriptor definition language string (empty string for default permissions) abs_path = folder_path + "\\" + task_name - abs_path = abs_path .replace(r'\\', chr(92)) - print_log(f"Creating Task: {abs_path}") - # Register the task - # tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) - + abs_path = abs_path.replace(r'\\', chr(92)) + response = tsch.hSchRpcRegisterTask(self.dce, abs_path, task_xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) return response diff --git a/src/slingerpkg/lib/schtasks.py b/src/slingerpkg/lib/schtasks.py index 22134f7..fd84aca 100755 --- a/src/slingerpkg/lib/schtasks.py +++ b/src/slingerpkg/lib/schtasks.py @@ -179,8 +179,27 @@ def task_create(self, args): arguments = args.arguments folder_path = args.folder # generate random date in last year using format 2023-01-01T08:00:00 - new_date = generate_random_date() - task_xml = f""" + + if args.date: + new_date = reformat_datetime(args.date) + else: + new_date = generate_random_date() + + interval = None + if args.interval: + #if less than 60, -> PT_M + #if greater than 60, -> PT_H + if int(args.interval) % 60 == 0: + h = int(args.interval) / 60 + interval = f"PT{h}H" + elif int(args.interval) < 60: + interval = f"PT{args.interval}M" + else: + h = round(int(args.interval) / 60) + m = int(args.interval) % 60 + interval = f"PT{h}H{m}M" + + task_xml_once = f""" {new_date} @@ -229,21 +248,84 @@ def task_create(self, args): """ + + task_xml_interval = f""" + + + {new_date} + SYSTEM + {folder_path}\{task_name} + + + + + {interval} + false + + {new_date} + true + + 1 + + + + + + S-1-5-18 + HighestAvailable + + + + IgnoreNew + true + true + true + false + false + + true + false + + true + true + true + false + false + PT72H + 7 + + + + {program} + {xml_escape(arguments)} + + + +""" + task_xml = task_xml_interval if args.interval else task_xml_once #validate_xml(task_xml) self.setup_dce_transport() self.dce_transport._connect('atsvc') - print_info(f"Creating task '{task_name}' in folder '{folder_path}'") + print_info("Using Program: " + program) print_info("Using Arguments: " + arguments) - print_info("Task XML:") - print_log(task_xml) + print_info("Using Date: " + new_date) + print_info("Using Interval: " + interval if args.interval else "Using Interval: None") + print_debug("Task XML:") + print_debug(task_xml) + abs_path = folder_path + "\\" + task_name + abs_path = abs_path.replace(r'\\', chr(92)) + print_log(f"Creating Task: {abs_path}") try: response = self.dce_transport._create_task(task_name, folder_path, task_xml) except Exception as e: if "ERROR_ALREADY_EXISTS" in str(e): print_warning(f"Task '{task_name}' already exists in folder '{folder_path}'") return + else: + print_bad(f"Error creating task '{task_name}': {e}") + return if response['ErrorCode'] == 0: print_log(f"Task '{task_name}' created successfully.") @@ -313,10 +395,11 @@ def task_show_handler(self, args): if not self.folder_list_dict and args.taskid: print_warning("No tasks have been enumerated. Run enumtasks first.") - else: + elif args.task_path: task_arg = args.taskid if args.taskid else args.task_path self.view_task_details(task_arg) - + else: + print_warning("No task specified. Use taskshow -i or taskshow to specify a task.") def task_manager(self): pass diff --git a/src/slingerpkg/lib/smblib.py b/src/slingerpkg/lib/smblib.py index 965a19a..0f80d73 100755 --- a/src/slingerpkg/lib/smblib.py +++ b/src/slingerpkg/lib/smblib.py @@ -83,6 +83,18 @@ def rm_handler(self, args): if args.remote_path == "." or args.remote_path == "" or args.remote_path is None: print_warning("Please specify a file to remove.") return + if args.remote_path == "*": + # get file listing + list_path = self.relative_path + '\\*' if self.relative_path else '*' + files = self.conn.listPath(self.share, list_path) + for f in files: + if f.is_directory() and f.get_longname() in ['.', '..']: + continue + path = ntpath.normpath(ntpath.join(self.relative_path, f.get_longname())) + print_debug(f"Removing file {path}") + self.conn.deleteFile(self.share, path) + print_info(f"File Removed {path}") + return path = ntpath.normpath(ntpath.join(self.relative_path, args.remote_path)) if self.check_if_connected(): @@ -141,16 +153,18 @@ def update_current_path(self): self.current_path = ntpath.normpath(self.share + "\\" + self.relative_path) # validate the directory exists - def is_valid_directory(self, path): + def is_valid_directory(self, path, print_error=True): list_path = path + '\\*' if path else '*' try: self.conn.listPath(self.share, list_path) return True except Exception as e: if "STATUS_STOPPED_ON_SYMLINK" in str(e): - print_warning(f"Remote directory {path} is a symlink.") + if print_error: + print_warning(f"Remote directory {path} is a symlink.") elif "STATUS_NOT_A_DIRECTORY" in str(e): - print_warning(f"Remote object {path} is not a directory.") + if print_error: + print_warning(f"{path} is not a directory.") print_debug(f"Failed to list directory {path} on share {self.share}: {e}", sys.exc_info()) return False @@ -328,16 +342,17 @@ def dir_list(self, args=None): path = os.path.normpath(os.path.join(self.relative_path,path)) - if not self.is_valid_directory(path): - print_bad(f"Invalid directory: {path}") - return + #if not self.is_valid_directory(path): + # print_bad(f"Invalid directory: {path}") + #return dirList = [] - if self.share is None: - print_warning("No share is connected. Use the 'use' command to connect to a share.") - return + try: - list_path = path + '\\*' if path else '*' + if not self.is_valid_directory(path, print_error=False) and "\\" not in path: + list_path = path + else: + list_path = path + '\\*' if path else '*' files = self.conn.listPath(self.share, list_path) for f in files: creation_time = (datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=f.get_ctime()/10)).replace(microsecond=0) @@ -358,7 +373,7 @@ def dir_list(self, args=None): suffix = "" else: suffix = path + "\\" - print_info("Showing directory listing for: " + os.path.normpath(self.share + "\\" + suffix)) + print_debug("Showing file listing for: " + os.path.normpath(self.share + "\\" + suffix)) # get sort option from arg.sort sort_option = args.sort @@ -391,5 +406,8 @@ def dir_list(self, args=None): print_log(tabulate(dirList, headers=['Attribs', 'Created', 'LastAccess', 'LastWrite', 'Size', 'Name'], tablefmt='psql')) except Exception as e: - print_debug(f"Failed to list directory {path} on share {self.share}: {e}", sys.exc_info()) - print_bad(f"Failed to list directory {path} on share {self.share}: {e}") + if "STATUS_NO_SUCH_FILE" in str(e): + print_warning(f"Invalid directory or file: {path}") + else: + print_debug(f"Failed to list file or directory {path} on share {self.share}: {e}", sys.exc_info()) + diff --git a/src/slingerpkg/utils/cli.py b/src/slingerpkg/utils/cli.py index 8ddec8f..4ee9717 100755 --- a/src/slingerpkg/utils/cli.py +++ b/src/slingerpkg/utils/cli.py @@ -210,7 +210,7 @@ def setup_cli_parser(slingerClient): parser_taskenum = subparsers.add_parser('enumtasks', help='Enumerate scheduled tasks', description='Enumerate scheduled tasks on the remote server', epilog='Example Usage: enumtasks', aliases=['tasksenum','taskenum']) parser_taskenum.set_defaults(func=slingerClient.enum_task_folders_recursive) # Subparser for 'tasksshow' command - parser_taskshow = subparsers.add_parser('tasksshow', help='Show task details', description='Show details of a specific task on the remote server', epilog='Example Usage: tasksshow -i 123', aliases=['taskshow','showtask']) + parser_taskshow = subparsers.add_parser('taskshow', help='Show task details', description='Show details of a specific task on the remote server', epilog='Example Usage: tasksshow -i 123', aliases=['tasksshow','showtask']) taskshowgroup = parser_taskshow.add_mutually_exclusive_group(required=True) taskshowgroup.add_argument('-i', '--taskid', type=int, help='Specify the ID of the task to show') taskshowgroup.add_argument('task_path', type=str, nargs='?', help='Specify the full path of the task to show') @@ -220,9 +220,11 @@ def setup_cli_parser(slingerClient): # Subparser for 'taskcreate' command parser_taskcreate = subparsers.add_parser('taskcreate', help='Create a new task', description='Create a new scheduled task on the remote server', epilog="Example Usage: taskcreate -n newtask -p cmd.exe -a '/c ipconfig /all > C:\\test' -f \\\\Windows", aliases=['taskadd'], formatter_class=argparse.RawDescriptionHelpFormatter) parser_taskcreate.add_argument('-n', '--name', required=True, help='Specify the name of the new task') - parser_taskcreate.add_argument('-p', '--program', required=True, help='Specify the program to run in the task') - parser_taskcreate.add_argument('-a', '--arguments', required=True, help='Specify the arguments to pass to the program') - parser_taskcreate.add_argument('-f', '--folder', required=True, default="\\", help='Specify the folder to create the task in') + parser_taskcreate.add_argument('-p', '--program', required=True, help='Specify the program to run (cmd.exe)') + parser_taskcreate.add_argument('-a', '--arguments', required=False, help='Specify the arguments to pass to the program') + parser_taskcreate.add_argument('-f', '--folder', required=False, default="", help='Specify the folder to create the task in') + parser_taskcreate.add_argument('-i', '--interval', required=False, default=None, help='Specify an interval in minutes to run the task') + parser_taskcreate.add_argument('-d', '--date', required=False, default=None, help='Specify the date to start the task (2099-12-31 14:01:00)') parser_taskcreate.set_defaults(func=slingerClient.task_create) # Subparser for 'taskrun' command diff --git a/src/slingerpkg/utils/common.py b/src/slingerpkg/utils/common.py index 8c42a68..4c2e5a3 100755 --- a/src/slingerpkg/utils/common.py +++ b/src/slingerpkg/utils/common.py @@ -73,6 +73,18 @@ def generate_random_date(lower_time_bound=None): random_date = lower_time_bound + datetime.timedelta(seconds=random_second) return random_date.strftime("%Y-%m-%dT%H:%M:%S") +def reformat_datetime(datetime_str): + original_format = "%Y-%m-%d %H:%M:%S" # Assuming the original format is "%Y-%m-%d %H:%M:%S" + new_format = "%Y-%m-%dT%H:%M:%S" # Desired format "%Y-%m-%dT%H:%M:%S" + + # Parse the original datetime string + dt = datetime.datetime.strptime(datetime_str, original_format) + + # Convert the datetime object to the desired format + formatted_datetime = dt.strftime(new_format) + + return formatted_datetime + def xml_escape(data): replace_table = { "&": "&",