From 289d527e5f51367c75fbb10ddc4cbdd4b7fea056 Mon Sep 17 00:00:00 2001 From: Hunter T Date: Tue, 16 Jan 2024 15:24:08 -0800 Subject: [PATCH 01/11] Command Class Overhaul - Utilizing built in PowerShell Command class - Refactor large amounts of code - Can now execute commands via both Command object and string (May be changed) - Many small changes --- FAFB-PowerShell-Tool.Tests/GuiCommandTest.cs | 49 ---------- .../InternalCommandTest.cs | 20 ---- .../PowerShellExecutorTest.cs | 17 ++-- FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs | 17 ++-- FAFB-PowerShell-Tool/MainWindow.xaml.cs | 17 ++-- FAFB-PowerShell-Tool/PSSaveOptions.cs | 98 +++++++++---------- .../{Commands => }/ActiveDirectoryCommands.cs | 12 ++- .../PowerShell/CommandParameters.cs | 40 ++++++++ .../PowerShell/Commands/GuiCommand.cs | 55 ----------- .../PowerShell/Commands/ICommand.cs | 15 --- .../PowerShell/Commands/InternalCommand.cs | 33 ------- .../PowerShell/PowerShellExecutor.cs | 78 ++++++++++----- 12 files changed, 172 insertions(+), 279 deletions(-) delete mode 100644 FAFB-PowerShell-Tool.Tests/GuiCommandTest.cs delete mode 100644 FAFB-PowerShell-Tool.Tests/InternalCommandTest.cs rename FAFB-PowerShell-Tool/PowerShell/{Commands => }/ActiveDirectoryCommands.cs (74%) create mode 100644 FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs delete mode 100644 FAFB-PowerShell-Tool/PowerShell/Commands/GuiCommand.cs delete mode 100644 FAFB-PowerShell-Tool/PowerShell/Commands/ICommand.cs delete mode 100644 FAFB-PowerShell-Tool/PowerShell/Commands/InternalCommand.cs diff --git a/FAFB-PowerShell-Tool.Tests/GuiCommandTest.cs b/FAFB-PowerShell-Tool.Tests/GuiCommandTest.cs deleted file mode 100644 index cd0a5c2..0000000 --- a/FAFB-PowerShell-Tool.Tests/GuiCommandTest.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FAFB_PowerShell_Tool.PowerShell.Commands; - -namespace FAFB_PowerShell_Tool.Tests; - -public class GuiCommandTest -{ - [Fact] - public void CommandNameIsCorrect() - { - GuiCommand command = new("Get-ADUser"); - Assert.Equal("Get-ADUser", command.CommandName); - } - - [Fact] - public void CommandStringGetIsCorrectWhenParametersAreSet() - { - GuiCommand command = new("Get-ADUser", new[] {"-Identity", "Test"}); - Assert.Equal("Get-ADUser -Identity Test", command.CommandString); - Assert.Equal("-Identity", command.Parameters![0]); - Assert.Equal("Test", command.Parameters![1]); - } - - [Fact] - public void PossibleParametersThrowsInvalidOperationExceptionWhenEmpty() - { - GuiCommand command = new("Get-ADUser"); - Assert.Throws(() => command.PossibleParameters); - } - - [Fact] - public async Task PossibleParametersReturnsCorrectly() - { - GuiCommand command = new("Get-ADUser"); - await command.LoadCommandParametersAsync(); - Assert.Contains("-Identity", command.PossibleParameters); - Assert.True(command.PossibleParameters.Count > 0); - } - - [Fact] - public async Task PossibleParametersReturnsCorrectlyWhenCalledTwice() - { - GuiCommand command = new("Get-ADUser"); - await command.LoadCommandParametersAsync(); - await command.LoadCommandParametersAsync(); - Assert.Contains("-Identity", command.PossibleParameters); - Assert.True(command.PossibleParameters.Count > 0); - Assert.Equal(command.PossibleParameters.Count, command.PossibleParameters.Distinct().Count()); - } -} diff --git a/FAFB-PowerShell-Tool.Tests/InternalCommandTest.cs b/FAFB-PowerShell-Tool.Tests/InternalCommandTest.cs deleted file mode 100644 index 9dcd584..0000000 --- a/FAFB-PowerShell-Tool.Tests/InternalCommandTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FAFB_PowerShell_Tool.PowerShell.Commands; - -namespace FAFB_PowerShell_Tool.Tests; - -public class InternalCommandTest -{ - [Fact] - public void CommandNameIsCorrect() - { - InternalCommand command = new("Get-ADUser"); - Assert.Equal("Get-ADUser", command.CommandName); - } - - [Fact] - public void CommandStringGetIsCorrectWhenParametersAreSet() - { - InternalCommand command = new("Get-ADUser", new[] {"-Identity", "Test"}); - Assert.Equal("Get-ADUser -Identity Test", command.CommandString); - } -} \ No newline at end of file diff --git a/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs b/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs index 24d0b69..f55ecac 100644 --- a/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs +++ b/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs @@ -1,5 +1,5 @@ -using FAFB_PowerShell_Tool.PowerShell; -using FAFB_PowerShell_Tool.PowerShell.Commands; +using System.Management.Automation.Runspaces; +using FAFB_PowerShell_Tool.PowerShell; using ArgumentException = System.ArgumentException; namespace FAFB_PowerShell_Tool.Tests; @@ -10,7 +10,7 @@ public class PowerShellExecutorTest public void ExecuteCommandReturnsAreCorrect() { PowerShellExecutor powerShell = new(); - ReturnValues values = powerShell.Execute(new InternalCommand("Get-Process")); + ReturnValues values = powerShell.Execute(new Command("Get-Process")); Assert.False(values.HadErrors); Assert.NotEmpty(values.StdOut); Assert.Empty(values.StdErr); @@ -20,7 +20,7 @@ public void ExecuteCommandReturnsAreCorrect() public void ExecuteBadCommandReturnsAreCorrect() { PowerShellExecutor powerShell = new(); - ReturnValues values = powerShell.Execute(new InternalCommand("BadCommand")); + ReturnValues values = powerShell.Execute(new Command("BadCommand")); Assert.True(values.HadErrors); Assert.Empty(values.StdOut); Assert.NotEmpty(values.StdErr); @@ -30,10 +30,9 @@ public void ExecuteBadCommandReturnsAreCorrect() public void ExecuteBadInternalCommandThrowsInvalidOperationException() { PowerShellExecutor powerShell = new(); - Assert.Throws(() => powerShell.Execute(new InternalCommand(""))); - Assert.Throws(() => powerShell.Execute(new InternalCommand(" "))); - Assert.Throws(() => powerShell.Execute(new InternalCommand(null!))); - Assert.Throws(() => powerShell.Execute(new InternalCommand(string.Empty))); + Assert.Throws(() => powerShell.Execute(new Command(""))); + Assert.Throws(() => powerShell.Execute(new Command(" "))); + Assert.Throws(() => powerShell.Execute(new Command(null!))); + Assert.Throws(() => powerShell.Execute(new Command(string.Empty))); } - } diff --git a/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs b/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs index 4f6729a..6917488 100644 --- a/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs +++ b/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs @@ -1,4 +1,5 @@ -using FAFB_PowerShell_Tool.PowerShell.Commands; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; using FAFB_PowerShell_Tool; using FAFB_PowerShell_Tool.PowerShell; @@ -6,18 +7,20 @@ namespace FAFB_PowerShell_Tool.Tests; public class SaveOptionsTest { - [Fact] + /*[Fact] public void SaveToCSVAppendsParameter() { var saveOptions = new PSSaveOptions(); - InternalCommand command = new("Get-ADUser", new[] { "-Identity", "Test" }); + Command commandString = new("Get-ADUser"); + commandString.Parameters.Add("Identity", "Test"); - InternalCommand returnedCommand = saveOptions.OutputToCSV(command); + InternalCommand returnedCommand = saveOptions.OutputToCSV(commandString); Assert.Equal("Get-ADUser -Identity Test | Export-CSV ..\\..\\..\\SavedOutput\\output.csv", returnedCommand.CommandString); - } - [Fact] + }*/ + + /*[Fact] public void ExecuteOutputToCSV() { PSSaveOptions pssave = new PSSaveOptions(); @@ -28,7 +31,7 @@ public void ExecuteOutputToCSV() //Check if a csv was made Assert.True(File.Exists("..\\..\\..\\SavedOutput\\output.csv")); //File.Delete("..\\..\\..\\FAFB-PowerShell-Tool\\SavedOutput\\output.csv"); - } + }*/ diff --git a/FAFB-PowerShell-Tool/MainWindow.xaml.cs b/FAFB-PowerShell-Tool/MainWindow.xaml.cs index d6d1596..de8f52e 100644 --- a/FAFB-PowerShell-Tool/MainWindow.xaml.cs +++ b/FAFB-PowerShell-Tool/MainWindow.xaml.cs @@ -1,9 +1,9 @@ using System.Collections.ObjectModel; using System.Diagnostics; +using System.Management.Automation.Runspaces; using System.Windows; using System.Windows.Controls; using FAFB_PowerShell_Tool.PowerShell; -using FAFB_PowerShell_Tool.PowerShell.Commands; namespace FAFB_PowerShell_Tool; @@ -26,9 +26,9 @@ public MainWindow() private async void MainWindow_Loaded(object sender, RoutedEventArgs e) { ComboBox comboBoxCommandList = ComboBoxCommandList; - ObservableCollection list = await ActiveDirectoryCommands.GetActiveDirectoryCommands(); + ObservableCollection list = await ActiveDirectoryCommands.GetActiveDirectoryCommands(); comboBoxCommandList.ItemsSource = list; - comboBoxCommandList.DisplayMemberPath = "CommandName"; + comboBoxCommandList.DisplayMemberPath = "CommandText"; FillCustomQueries(); } @@ -41,16 +41,17 @@ private async void MainWindow_Loaded(object sender, RoutedEventArgs e) private async void MComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { ComboBox comboBox = sender as ComboBox ?? throw new InvalidOperationException(); + CommandParameters commandParameters = new(); - if (comboBox.SelectedItem is not GuiCommand selectedCommand) + if (comboBox.SelectedItem is not Command selectedCommand) { return; } // Set the ItemsSource for the parameters ComboBox. ComboBox comboBoxParameters = ComboBoxCommandParameterList; - await selectedCommand.LoadCommandParametersAsync(); // Lazy loading. - comboBoxParameters.ItemsSource = selectedCommand.PossibleParameters; + await commandParameters.LoadCommandParametersAsync(selectedCommand); // Lazy loading. + comboBoxParameters.ItemsSource = commandParameters.PossibleParameters; } // TODO: Test to see if this works as it should... @@ -61,7 +62,7 @@ private void MSaveQueryButton(object sender, RoutedEventArgs e) try { // Get the Command - GuiCommand? command = ComboBoxCommandList.SelectedValue as GuiCommand; + Command? command = ComboBoxCommandList.SelectedValue as Command; // string commandParameters = cmbParameters.Text; Button newButton = new() { @@ -70,7 +71,7 @@ private void MSaveQueryButton(object sender, RoutedEventArgs e) Height = 48 }; // Adds the query to the Queries serializable list - CQ.Queries.Add(command.CommandName); + CQ.Queries.Add(command.CommandText); // Adds the button in the stack panel ButtonStackPanel.Children.Add(newButton); // Saves the Queries to the file diff --git a/FAFB-PowerShell-Tool/PSSaveOptions.cs b/FAFB-PowerShell-Tool/PSSaveOptions.cs index 90f5cd7..9df7b6e 100644 --- a/FAFB-PowerShell-Tool/PSSaveOptions.cs +++ b/FAFB-PowerShell-Tool/PSSaveOptions.cs @@ -1,65 +1,61 @@ using FAFB_PowerShell_Tool.PowerShell; -using FAFB_PowerShell_Tool.PowerShell.Commands; using System; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; -namespace FAFB_PowerShell_Tool +namespace FAFB_PowerShell_Tool; + +/// +/// This class *might* be used for housing the save options +/// +public class PSSaveOptions { /// - /// This class *might* be used for housing the save options + /// This would be to output the query to a csv, will need to eventually adapt to a guicommand as well most likely /// - public class PSSaveOptions + /// This is the internal command that we want to output to a csv + /*public Command OutputToCSV(Command commandString) { - /// - /// Empty Contructor - /// - public PSSaveOptions() { } - /// - /// This would be to output the query to a csv, will need to eventually adapt to a guicommand as well most likely - /// - /// This is the internal command that we want to output to a csv - public InternalCommand OutputToCSV(InternalCommand commandString) - { - //Checks to see if the parameters are null, - if (commandString.Parameters == null || commandString.Parameters.Length == 0) - { - //if they are null then make a new array with the output option, currently to relative path to SavedOutput folder - commandString.Parameters = new[] { "| Export-CSV ..\\..\\..\\SavedOutput\\output.csv" }; - } - else - { - // If it is not null we will clone the parameters and add the output parameter to the new string and then set the array - string[] temp = commandString.Parameters; - Array.Resize(ref temp, temp.Length + 1); - temp[temp.Length - 1] = "| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"; - //setting - commandString.Parameters = temp; - } - //return the commandString with the updated parameters - return commandString; - } - /// - /// This would be to have the query output to a message box - /// - public InternalCommand OutputToMessageBox(InternalCommand commandSting) - { - return null; - } - /// - /// This will be to save the query to a text file - /// - public void SaveToTxt(ReturnValues results) + //Checks to see if the parameters are null, + if (commandString.Parameters == null || commandString.Parameters.Count == 0) { - + //if they are null then make a new array with the output option, currently to relative path to SavedOutput folder + commandString.Parameters.Add("| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"); } - /// - /// This is used for saving the file to a PS1 powershell file - /// - public void SaveToPS(ReturnValues results) + else { - + // If it is not null we will clone the parameters and add the output parameter to the new string and then set the array + string[] temp = commandString.Parameters; + Array.Resize(ref temp, temp.Length + 1); + temp[temp.Length - 1] = "| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"; + //setting + commandString.Parameters = temp; } + //return the commandString with the updated parameters + return commandString; + }*/ + + /// + /// This would be to have the query output to a message box + /// + public InternalCommand OutputToMessageBox(InternalCommand commandSting) + { + return null; + } + + /// + /// This will be to save the query to a text file + /// + public void SaveToTxt(ReturnValues results) + { - + } + + /// + /// This is used for saving the file to a PS1 powershell file + /// + public void SaveToPS(ReturnValues results) + { } -} +} \ No newline at end of file diff --git a/FAFB-PowerShell-Tool/PowerShell/Commands/ActiveDirectoryCommands.cs b/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs similarity index 74% rename from FAFB-PowerShell-Tool/PowerShell/Commands/ActiveDirectoryCommands.cs rename to FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs index 27cec96..9d1ae53 100644 --- a/FAFB-PowerShell-Tool/PowerShell/Commands/ActiveDirectoryCommands.cs +++ b/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Reflection.Metadata; -namespace FAFB_PowerShell_Tool.PowerShell.Commands; +namespace FAFB_PowerShell_Tool.PowerShell; /// /// Commands from the ActiveDirectory PowerShell module. @@ -13,11 +15,11 @@ public static class ActiveDirectoryCommands /// /// Returns a list of commands in the ActiveDirectory PowerShell module. /// Thrown when an error has occurred when executing PowerShell commands. - public static async Task> GetActiveDirectoryCommands() + public static async Task> GetActiveDirectoryCommands() { PowerShellExecutor powerShellExecutor = new(); - ObservableCollection commandList = new(); - InternalCommand commandString = new("Get-Command", new[] { "-Module", "ActiveDirectory" }); + ObservableCollection commandList = new(); + string commandString = "Get-Command -Module ActiveDirectory"; ReturnValues commandListTemp = await powerShellExecutor.ExecuteAsync(commandString); if (commandListTemp.HadErrors) @@ -28,7 +30,7 @@ public static async Task> GetActiveDirectoryCom foreach (var command in commandListTemp.StdOut) { - commandList.Add(new GuiCommand(command)); + commandList.Add(new Command(command)); } return commandList; diff --git a/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs b/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs new file mode 100644 index 0000000..7b380b0 --- /dev/null +++ b/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs @@ -0,0 +1,40 @@ +using System.Collections.ObjectModel; +using System.Management.Automation.Runspaces; + +namespace FAFB_PowerShell_Tool.PowerShell; + +public class CommandParameters +{ + private readonly ObservableCollection _possibleParameters = new(); + public ObservableCollection PossibleParameters + { + get { + if (_possibleParameters.Count != 0) + { + return _possibleParameters; + } + + throw new InvalidOperationException("PossibleParameters has not been populated via 'LoadCommandParametersAsync'."); + } + } + + /// + /// Loads the possible parameters for the selected command into the '_possibleParameters' collection. + /// + /// A collection of parameter names for 'CommandName' + public async Task LoadCommandParametersAsync(Command commandObject) + { + string commandString = $"Get-Command {commandObject.CommandText} | Select -ExpandProperty Parameters | ForEach-Object {{ $_.Keys }}"; + + if (_possibleParameters.Count == 0) + { + PowerShellExecutor powerShellExecutor = new(); + ReturnValues tmpList = await powerShellExecutor.ExecuteAsync(commandString); + + foreach (var command in tmpList.StdOut) + { + _possibleParameters.Add($"-{command}"); + } + } + } +} \ No newline at end of file diff --git a/FAFB-PowerShell-Tool/PowerShell/Commands/GuiCommand.cs b/FAFB-PowerShell-Tool/PowerShell/Commands/GuiCommand.cs deleted file mode 100644 index 54e410c..0000000 --- a/FAFB-PowerShell-Tool/PowerShell/Commands/GuiCommand.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.ObjectModel; - -namespace FAFB_PowerShell_Tool.PowerShell.Commands; - -/// -/// Commands that are intended to be selected and used via the GUI. -/// -/// -/// In addition to the base class, this class also contains a collection of possible parameters for the selected -/// command. -/// -/// The selected command. -/// Selected parameters for 'commandName'. -public class GuiCommand : InternalCommand -{ - private readonly ObservableCollection _possibleParameters = new(); - // NOTE: Calling 'LoadCommandParametersAsync' from this property's getter will cause the application to hang. This - // is why the property throws an exception if the collection is empty, and requires the user to call the - // 'LoadCommandParametersAsync' method before accessing the collection. - public ObservableCollection PossibleParameters - { - get { - if (_possibleParameters.Count != 0) - { - return _possibleParameters; - } - - throw new InvalidOperationException( - "PossibleParameters has not been populated via 'LoadCommandParametersAsync'."); - } - } - - public GuiCommand(string commandName, string[]? parameters = null) : base(commandName, parameters) - { } - - /// - /// Loads the possible parameters for the selected command into the '_possibleParameters' collection. - /// - /// A collection of parameter names for 'CommandName' - public async Task LoadCommandParametersAsync() - { - GuiCommand guiCommandString = new("Get-Command", new[] { CommandName, "| Select -ExpandProperty Parameters | ForEach-Object { $_.Keys }" }); - - if (_possibleParameters.Count == 0) - { - PowerShellExecutor powerShellExecutor = new(); - ReturnValues tmpList = await powerShellExecutor.ExecuteAsync(guiCommandString); - - foreach (var command in tmpList.StdOut) - { - _possibleParameters.Add($"-{command}"); - } - } - } -} diff --git a/FAFB-PowerShell-Tool/PowerShell/Commands/ICommand.cs b/FAFB-PowerShell-Tool/PowerShell/Commands/ICommand.cs deleted file mode 100644 index 0ad0e57..0000000 --- a/FAFB-PowerShell-Tool/PowerShell/Commands/ICommand.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace FAFB_PowerShell_Tool.PowerShell.Commands; - -/// -/// Interface for commands. -/// -/// -/// This interface is used to ensure that when executing a PowerShell command via 'PowerShellExecutor', the command type -/// passed to the method has to be of type 'ICommand'. -/// -public interface ICommand -{ - string CommandName { get; } - string[]? Parameters { get; set; } - string CommandString { get; } -} \ No newline at end of file diff --git a/FAFB-PowerShell-Tool/PowerShell/Commands/InternalCommand.cs b/FAFB-PowerShell-Tool/PowerShell/Commands/InternalCommand.cs deleted file mode 100644 index f00272c..0000000 --- a/FAFB-PowerShell-Tool/PowerShell/Commands/InternalCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace FAFB_PowerShell_Tool.PowerShell.Commands; - -/// -/// Commands that are not intended to be used by the user/GUI, but rather by the program itself. -/// -/// -/// The separation of commands into 'InternalCommand' and 'GuiCommand' is because the internal commands don't need to -/// know the possible parameters for the command, whereas the GUI commands do. -/// -public class InternalCommand : ICommand -{ - public string CommandName { get; } - public string[]? Parameters { get; set; } - public string CommandString => Parameters is null ? CommandName : $"{CommandName} {string.Join(" ", Parameters)}"; - - /// - /// Commands that are not intended to be used by the user, but rather by the program itself. - /// - /// The selected command. - /// Selected parameters for 'commandName'. - /// Thrown when 'commandName' is null, contains whitespace, or is blank - public InternalCommand(string commandName, string[]? parameters = null) - { - if (string.IsNullOrWhiteSpace(commandName)) - { - //MessageBoxOutput.Show("Command name cannot be null or whitespace.", MessageBoxOutput.OutputType.Error); - throw new ArgumentException("Command name cannot be null or whitespace.", nameof(commandName)); - } - - CommandName = commandName; - Parameters = parameters; - } -} \ No newline at end of file diff --git a/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs b/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs index 01b2254..1235058 100644 --- a/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs +++ b/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs @@ -1,6 +1,7 @@ -using System.IO; +using System.Collections.ObjectModel; using System.Management.Automation; -using FAFB_PowerShell_Tool.PowerShell.Commands; +using System.Management.Automation.Runspaces; +using System.Text; namespace FAFB_PowerShell_Tool.PowerShell; @@ -11,6 +12,7 @@ public class PowerShellExecutor { private readonly System.Management.Automation.PowerShell _powerShell; + public PowerShellExecutor() { _powerShell = System.Management.Automation.PowerShell.Create(); @@ -20,46 +22,52 @@ public PowerShellExecutor() _powerShell.Commands.Clear(); } - /// - /// Executes a PowerShell command synchronously. - /// - /// Of type 'ICommand'. - /// The command and parameters in a single string. - /// The processed and formatted results of the executed PowerShell command. - public ReturnValues Execute(T commandString) where T : ICommand + public ReturnValues Execute(Command commandString) + { + return ExecuteInternal(CommandToString(commandString)); + } + + public ReturnValues Execute(string commandString) + { + return ExecuteInternal(commandString); + } + + public async Task ExecuteAsync(Command commandString) + { + return await ExecuteInternalAsync(CommandToString(commandString)); + } + + public async Task ExecuteAsync(string commandString) { - ValidateCommandString(commandString); - _powerShell.AddScript(commandString.CommandString); - var results = _powerShell.Invoke(); + return await ExecuteInternalAsync(commandString); + } + + private ReturnValues ExecuteInternal(string command) + { + ValidateCommandString(command); + _powerShell.AddScript(command); + Collection results = _powerShell.Invoke(); return ProcessPowerShellResults(results); } - /// - /// Executes a PowerShell command asynchronously. - /// - /// Of type 'ICommand'. - /// The command and parameters in a single string. - /// The processed and formatted results of the executed PowerShell command. - public async Task ExecuteAsync(T commandString) where T : ICommand + private async Task ExecuteInternalAsync(string command) { - ValidateCommandString(commandString); - _powerShell.AddScript(commandString.CommandString); - var results = await _powerShell.InvokeAsync(); + ValidateCommandString(command); + _powerShell.AddScript(command); + PSDataCollection results = await _powerShell.InvokeAsync(); return await ProcessPowerShellResultsAsync(results); } /// /// Checks if the command string is null, contains whitespace, or is blank. /// - /// Of type 'ICommand'. /// The command and parameters in a single string. /// Thrown when 'commandName' is null, contains whitespace, or is blank - private void ValidateCommandString(T commandString) where T : ICommand + private void ValidateCommandString(string commandString) { - if (string.IsNullOrWhiteSpace(commandString.CommandString)) + if (string.IsNullOrWhiteSpace(commandString)) { - MessageBoxOutput.Show("Command text cannot be null or whitespace.", MessageBoxOutput.OutputType.Error); - throw new ArgumentException("Command text cannot be null or whitespace.", nameof(commandString)); + throw new ArgumentException("Command string cannot be null, contain whitespace, or be blank."); } } @@ -99,5 +107,21 @@ private Task ProcessPowerShellResultsAsync(IEnumerable r { return Task.FromResult(ProcessPowerShellResults(results)); } + + public string CommandToString(Command command) + { + StringBuilder commandString = new StringBuilder(); + + // Add the command name + commandString.Append(command.CommandText); + + // Iterate over the parameters and add them to the string + foreach (var param in command.Parameters) + { + commandString.Append($" -{param.Name} {param.Value}"); + } + + return commandString.ToString(); + } } From be048f9c390fda52390107b9a9609d8202ba19c2 Mon Sep 17 00:00:00 2001 From: Hunter T Date: Thu, 18 Jan 2024 09:49:52 -0800 Subject: [PATCH 02/11] Format with clang-format --- .../CustomQueriesTest.cs | 4 +-- FAFB-PowerShell-Tool.Tests/GlobalUsings.cs | 2 +- .../PowerShellExecutorTest.cs | 4 +-- FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs | 15 ++++----- FAFB-PowerShell-Tool/PSSaveOptions.cs | 33 ++++++++----------- .../PowerShell/ActiveDirectoryCommands.cs | 5 +-- .../PowerShell/CommandParameters.cs | 12 ++++--- .../PowerShell/PowerShellExecutor.cs | 6 ++-- 8 files changed, 37 insertions(+), 44 deletions(-) diff --git a/FAFB-PowerShell-Tool.Tests/CustomQueriesTest.cs b/FAFB-PowerShell-Tool.Tests/CustomQueriesTest.cs index b66f7a6..e6dcd5c 100644 --- a/FAFB-PowerShell-Tool.Tests/CustomQueriesTest.cs +++ b/FAFB-PowerShell-Tool.Tests/CustomQueriesTest.cs @@ -1,6 +1,4 @@ namespace FAFB_PowerShell_Tool.Tests; public class CustomQueriesTest -{ - -} \ No newline at end of file +{ } diff --git a/FAFB-PowerShell-Tool.Tests/GlobalUsings.cs b/FAFB-PowerShell-Tool.Tests/GlobalUsings.cs index 8c927eb..c802f44 100644 --- a/FAFB-PowerShell-Tool.Tests/GlobalUsings.cs +++ b/FAFB-PowerShell-Tool.Tests/GlobalUsings.cs @@ -1 +1 @@ -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs b/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs index f55ecac..4fed025 100644 --- a/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs +++ b/FAFB-PowerShell-Tool.Tests/PowerShellExecutorTest.cs @@ -15,7 +15,7 @@ public void ExecuteCommandReturnsAreCorrect() Assert.NotEmpty(values.StdOut); Assert.Empty(values.StdErr); } - + [Fact] public void ExecuteBadCommandReturnsAreCorrect() { @@ -25,7 +25,7 @@ public void ExecuteBadCommandReturnsAreCorrect() Assert.Empty(values.StdOut); Assert.NotEmpty(values.StdErr); } - + [Fact] public void ExecuteBadInternalCommandThrowsInvalidOperationException() { diff --git a/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs b/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs index 6917488..3b75e06 100644 --- a/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs +++ b/FAFB-PowerShell-Tool.Tests/SaveOptionsTest.cs @@ -1,4 +1,4 @@ -using System.Management.Automation.Internal; +using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using FAFB_PowerShell_Tool; using FAFB_PowerShell_Tool.PowerShell; @@ -17,9 +17,10 @@ public void SaveToCSVAppendsParameter() InternalCommand returnedCommand = saveOptions.OutputToCSV(commandString); - Assert.Equal("Get-ADUser -Identity Test | Export-CSV ..\\..\\..\\SavedOutput\\output.csv", returnedCommand.CommandString); + Assert.Equal("Get-ADUser -Identity Test | Export-CSV ..\\..\\..\\SavedOutput\\output.csv", + returnedCommand.CommandString); }*/ - + /*[Fact] public void ExecuteOutputToCSV() { @@ -28,12 +29,8 @@ public void ExecuteOutputToCSV() InternalCommand test_command = new("get-process"); ReturnValues temprv = executor.Execute(pssave.OutputToCSV(test_command)); - //Check if a csv was made + //Check if a csv was made Assert.True(File.Exists("..\\..\\..\\SavedOutput\\output.csv")); //File.Delete("..\\..\\..\\FAFB-PowerShell-Tool\\SavedOutput\\output.csv"); }*/ - - - - -} \ No newline at end of file +} diff --git a/FAFB-PowerShell-Tool/PSSaveOptions.cs b/FAFB-PowerShell-Tool/PSSaveOptions.cs index 9df7b6e..3ec1b60 100644 --- a/FAFB-PowerShell-Tool/PSSaveOptions.cs +++ b/FAFB-PowerShell-Tool/PSSaveOptions.cs @@ -6,12 +6,12 @@ namespace FAFB_PowerShell_Tool; /// -/// This class *might* be used for housing the save options +/// This class *might* be used for housing the save options /// public class PSSaveOptions { /// - /// This would be to output the query to a csv, will need to eventually adapt to a guicommand as well most likely + /// This would be to output the query to a csv, will need to eventually adapt to a guicommand as well most likely /// /// This is the internal command that we want to output to a csv /*public Command OutputToCSV(Command commandString) @@ -19,43 +19,38 @@ public class PSSaveOptions //Checks to see if the parameters are null, if (commandString.Parameters == null || commandString.Parameters.Count == 0) { - //if they are null then make a new array with the output option, currently to relative path to SavedOutput folder - commandString.Parameters.Add("| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"); + //if they are null then make a new array with the output option, currently to relative path to SavedOutput + folder commandString.Parameters.Add("| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"); } else { - // If it is not null we will clone the parameters and add the output parameter to the new string and then set the array - string[] temp = commandString.Parameters; - Array.Resize(ref temp, temp.Length + 1); - temp[temp.Length - 1] = "| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"; + // If it is not null we will clone the parameters and add the output parameter to the new string and then + set the array string[] temp = commandString.Parameters; Array.Resize(ref temp, temp.Length + 1); temp[temp.Length - + 1] = "| Export-CSV ..\\..\\..\\SavedOutput\\output.csv"; //setting commandString.Parameters = temp; } //return the commandString with the updated parameters return commandString; }*/ - + /// /// This would be to have the query output to a message box /// - public InternalCommand OutputToMessageBox(InternalCommand commandSting) + public InternalCommand OutputToMessageBox(InternalCommand commandSting) { return null; } - + /// - /// This will be to save the query to a text file + /// This will be to save the query to a text file /// public void SaveToTxt(ReturnValues results) - { + { } - } - /// /// This is used for saving the file to a PS1 powershell file /// public void SaveToPS(ReturnValues results) - { - - } -} \ No newline at end of file + { } +} diff --git a/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs b/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs index 9d1ae53..f21a432 100644 --- a/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs +++ b/FAFB-PowerShell-Tool/PowerShell/ActiveDirectoryCommands.cs @@ -14,7 +14,8 @@ public static class ActiveDirectoryCommands /// This method will return a list of commands in the ActiveDirectory PowerShell module. /// /// Returns a list of commands in the ActiveDirectory PowerShell module. - /// Thrown when an error has occurred when executing PowerShell commands. + /// Thrown when an error has occurred when executing PowerShell + /// commands. public static async Task> GetActiveDirectoryCommands() { PowerShellExecutor powerShellExecutor = new(); @@ -25,7 +26,7 @@ public static async Task> GetActiveDirectoryComman if (commandListTemp.HadErrors) { MessageBoxOutput.Show(string.Join(" ", commandListTemp.StdErr), MessageBoxOutput.OutputType.Error); - throw new InvalidPowerShellStateException(); // TODO: Make exception output more info... + throw new InvalidPowerShellStateException(); // TODO: Make exception output more info... } foreach (var command in commandListTemp.StdOut) diff --git a/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs b/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs index 7b380b0..a46b0ee 100644 --- a/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs +++ b/FAFB-PowerShell-Tool/PowerShell/CommandParameters.cs @@ -13,18 +13,20 @@ public ObservableCollection PossibleParameters { return _possibleParameters; } - - throw new InvalidOperationException("PossibleParameters has not been populated via 'LoadCommandParametersAsync'."); + + throw new InvalidOperationException( + "PossibleParameters has not been populated via 'LoadCommandParametersAsync'."); } } - + /// /// Loads the possible parameters for the selected command into the '_possibleParameters' collection. /// /// A collection of parameter names for 'CommandName' public async Task LoadCommandParametersAsync(Command commandObject) { - string commandString = $"Get-Command {commandObject.CommandText} | Select -ExpandProperty Parameters | ForEach-Object {{ $_.Keys }}"; + string commandString = + $"Get-Command {commandObject.CommandText} | Select -ExpandProperty Parameters | ForEach-Object {{ $_.Keys }}"; if (_possibleParameters.Count == 0) { @@ -37,4 +39,4 @@ public async Task LoadCommandParametersAsync(Command commandObject) } } } -} \ No newline at end of file +} diff --git a/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs b/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs index 1235058..d450e96 100644 --- a/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs +++ b/FAFB-PowerShell-Tool/PowerShell/PowerShellExecutor.cs @@ -12,7 +12,6 @@ public class PowerShellExecutor { private readonly System.Management.Automation.PowerShell _powerShell; - public PowerShellExecutor() { _powerShell = System.Management.Automation.PowerShell.Create(); @@ -62,7 +61,8 @@ private async Task ExecuteInternalAsync(string command) /// Checks if the command string is null, contains whitespace, or is blank. /// /// The command and parameters in a single string. - /// Thrown when 'commandName' is null, contains whitespace, or is blank + /// Thrown when 'commandName' is null, contains whitespace, or is + /// blank private void ValidateCommandString(string commandString) { if (string.IsNullOrWhiteSpace(commandString)) @@ -107,7 +107,7 @@ private Task ProcessPowerShellResultsAsync(IEnumerable r { return Task.FromResult(ProcessPowerShellResults(results)); } - + public string CommandToString(Command command) { StringBuilder commandString = new StringBuilder(); From c54e66925df302b23d21d498b6103d571d9e929d Mon Sep 17 00:00:00 2001 From: Hunter T Date: Thu, 18 Jan 2024 15:04:47 -0800 Subject: [PATCH 03/11] Refactor to implement the MVVM pattern --- FAFB-PowerShell-Tool/MainWindow.xaml | 63 +++++++--- FAFB-PowerShell-Tool/MainWindow.xaml.cs | 125 +------------------- FAFB-PowerShell-Tool/MainWindowViewModel.cs | 91 ++++++++++++++ 3 files changed, 137 insertions(+), 142 deletions(-) create mode 100644 FAFB-PowerShell-Tool/MainWindowViewModel.cs diff --git a/FAFB-PowerShell-Tool/MainWindow.xaml b/FAFB-PowerShell-Tool/MainWindow.xaml index 579235b..dbbd50a 100644 --- a/FAFB-PowerShell-Tool/MainWindow.xaml +++ b/FAFB-PowerShell-Tool/MainWindow.xaml @@ -6,6 +6,10 @@ xmlns:local="clr-namespace:FAFB_PowerShell_Tool" mc:Ignorable="d" Title="MainWindow" Height="500" Width="800"> + + + + @@ -44,9 +48,14 @@ - - - + + @@ -58,9 +67,7 @@ - + @@ -75,16 +82,22 @@ - - - - - - - - - - + + @@ -97,7 +110,21 @@ -