Skip to content
This repository has been archived by the owner on Dec 21, 2024. It is now read-only.

Active Directory Tab is now functional #100

Merged
merged 10 commits into from
Mar 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static async Task<ObservableCollection<Command>> GetADCommands()
if (psOutput.HadErrors)
{
string errorMessage = "Internal Error: An error occurred while retrieving the Active Directory commands: " +
$"({string.Join(" ", psOutput.StdErr)})";
$"{string.Join(" ", psOutput.StdErr)}";
MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
throw new InvalidPowerShellStateException(errorMessage);
}
Expand Down
53 changes: 53 additions & 0 deletions ActiveDirectoryQuerier/ActiveDirectoryInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Management.Automation.Runspaces;
using ActiveDirectoryQuerier.PowerShell;

namespace ActiveDirectoryQuerier;

public class ActiveDirectoryInfo
{
private readonly PSExecutor _psExecutor = new();

public Dictionary<string, Func<Task<PSOutput>>> AvailableOptions { get; } = new();

public ActiveDirectoryInfo()
{
AvailableOptions.Add("Get user on domain", GetADUsers);
AvailableOptions.Add("Get computers on domain", GetADComputers);
AvailableOptions.Add("Get IPv4 of each system on domain", GetADIPv4Addresses);
AvailableOptions.Add("Get IPv6 of each system on domain", GetADIPv6Addresses);
}

// ReSharper disable once InconsistentNaming
private async Task<PSOutput> GetADUsers()
{
Command psCommand = new("Get-ADUser");
psCommand.Parameters.Add("Filter", "*");
return await _psExecutor.ExecuteAsync(psCommand);
}

// ReSharper disable once InconsistentNaming
private async Task<PSOutput> GetADComputers()
{
Command psCommand = new("Get-ADComputer");
psCommand.Parameters.Add("Filter", "*");
return await _psExecutor.ExecuteAsync(psCommand);
}

// ReSharper disable once InconsistentNaming
private async Task<PSOutput> GetADIPv4Addresses()
{
Command psCommand = new("Get-ADComputer");
psCommand.Parameters.Add("Filter", "*");
psCommand.Parameters.Add("Properties", "IPv4Address");
return await _psExecutor.ExecuteAsync(psCommand);
}

// ReSharper disable once InconsistentNaming
private async Task<PSOutput> GetADIPv6Addresses()
{
Command psCommand = new("Get-ADComputer");
psCommand.Parameters.Add("Filter", "*");
psCommand.Parameters.Add("Properties", "IPv6Address");
return await _psExecutor.ExecuteAsync(psCommand);
}
}
4 changes: 2 additions & 2 deletions ActiveDirectoryQuerier/ActiveDirectoryQuerier.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<!-- Project Info. -->
<Title>Active Directory Querier</Title>
<Version>0.6.0</Version>
<Version>0.7.0</Version>
<Authors>Hunter T., Pieter, Joseph</Authors>
<RepositoryUrl>https://github.com/StrangeRanger/Active-Directory-Querier</RepositoryUrl>

Expand All @@ -22,7 +22,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.18" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.3.11" />
</ItemGroup>

</Project>
7 changes: 3 additions & 4 deletions ActiveDirectoryQuerier/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,14 @@
<RowDefinition MinHeight="100" />
</Grid.RowDefinitions>
<Grid Margin="0,0,0,49" Grid.Row="0">
<!-- TODO: Pieter: Bind button to another property inside the MainWindowViewModel -->
<Button Content="Execute" HorizontalAlignment="Left" Margin="368,43,0,0"
VerticalAlignment="Top" Height="23" Width="95" />
VerticalAlignment="Top" Height="23" Width="95"
Command="{Binding ExecuteQueryAsyncFromActiveDirectoryInfoRelay}"/>
<ComboBox HorizontalAlignment="Left" Margin="31,44,0,0" VerticalAlignment="Top"
Width="207" Height="26"
SelectedItem="{Binding SelectedCommandFromComboBoxInActiveDirectoryInfo}"
ItemsSource="{Binding AvailableOptionsFromComboBoxInActiveDirectoryInfo}" />
ItemsSource="{Binding AvailableOptionsFromComboBoxInActiveDirectoryInfo.AvailableOptions.Keys }" />
</Grid>
<!-- TODO: Pieter: Bind this to a property in the MainWindowViewModel -->
<TextBox Grid.Row="1" Text="{Binding ConsoleOutputInActiveDirectoryInfo.ConsoleOutput}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="10" IsReadOnly="True" TextWrapping="Wrap"
Expand Down
148 changes: 91 additions & 57 deletions ActiveDirectoryQuerier/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using ActiveDirectoryQuerier.PowerShell;
using ActiveDirectoryQuerier.Queries;
using ActiveDirectoryQuerier.ViewModels;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Win32;

namespace ActiveDirectoryQuerier;
Expand All @@ -30,7 +31,7 @@
private AppConsole _consoleOutputInQueryBuilder;
private AppConsole _consoleOutputInActiveDirectoryInfo;
private Command? _selectedCommandFromComboBoxInQueryBuilder;
private Command? _selectedCommandFromComboBoxInActiveDirectoryInfo;
private string? _selectedCommandFromComboBoxInActiveDirectoryInfo;
private ObservableCollection<Button>? _buttons; // TODO: Rename to be more descriptive.

// [[ Other fields ]] ----------------------------------------------------------- //
Expand All @@ -39,6 +40,7 @@
private readonly CustomQueries _customQuery;
private readonly PSExecutor _psExecutor;
private Query? _isEditing;
private readonly ActiveDirectoryInfo _activeDirectoryInfo = new();

// [ Properties ] --------------------------------------------------------------- //
// [[ Properties for backing fields ]] ------------------------------------------ //
Expand Down Expand Up @@ -104,13 +106,13 @@
// No need to load parameters if the command is null.
if (value is not null)
{
// TODO: Figure out how to resolve the warning about the async method not being awaited!!!
// TODO: Figure out how to resolve the warning about the async method not being awaited...
LoadCommandParametersAsync(value);

Check warning on line 110 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 110 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
}
}
}

public Command? SelectedCommandFromComboBoxInActiveDirectoryInfo
public string? SelectedCommandFromComboBoxInActiveDirectoryInfo
{
get => _selectedCommandFromComboBoxInActiveDirectoryInfo;
set {
Expand All @@ -122,9 +124,7 @@
}
}

public List<string> AvailableOptionsFromComboBoxInActiveDirectoryInfo {
get;
} = new() { "Get user on domain", "Get computers on domain", "Get IP of each system on domain" };
public ActiveDirectoryInfo AvailableOptionsFromComboBoxInActiveDirectoryInfo { get; } = new();

public ObservableCollection<Button> QueryButtonStackPanel => _buttons ??= new ObservableCollection<Button>();

Expand All @@ -151,7 +151,7 @@
public ICommand SaveQueryRelay { get; }
public ICommand ClearQueryBuilderRelay { get; }
public ICommand ExecuteQueryFromQueryBuilderRelay { get; }
public ICommand ExecuteQueryFromActiveDirectoryInfoRelay { get; } // TODO: Pieter use this for execution button
public ICommand ExecuteQueryAsyncFromActiveDirectoryInfoRelay { get; }
public ICommand AddCommandComboBoxRelay { get; }
public ICommand AddCommandParameterComboBoxRelay { get; }
public ICommand RemoveCommandParameterComboBoxRelay { get; }
Expand All @@ -161,18 +161,8 @@
public ICommand ClearConsoleOutputInQueryBuilderRelay { get; }
public ICommand ImportQueryFileRelay { get; }
public ICommand CreateNewQueryFileRelay { get; }
public ICommand ClearConsoleOutputInActiveDirectoryInfoRelay { get; } // TODO: Impliment functionality.....

/* TODO: for Pieter
*
* Create a property that contains the ComboBox dropdown options/text.
*
* Create a property that will contain the selected item.
*
* Create a property to act as the relay to the execution button.
*/

// NEW CODE
public ICommand ClearConsoleOutputInActiveDirectoryInfoRelay { get; }

// [ Constructor ] ------------------------------------------------------------- //

public MainWindowViewModel()
Expand All @@ -192,12 +182,10 @@
OutputToCsvFileRelay = new RelayCommand(OutputExecutionResultsToCsvFileAsync);
OutputToTextFileRelay = new RelayCommand(OutputExecutionResultsToTextFileAsync);
ExportConsoleOutputRelay = new RelayCommand(ExportConsoleOutputToFile);
// TODO: Figure out how resolve the warning about the async method not being awaited.
ExecuteQueryFromQueryBuilderRelay = new RelayCommand(
_ => ExecuteQuery(_consoleOutputInQueryBuilder));
// TODO: Figure out how resolve the warning about the async method not being awaited.
ExecuteQueryFromActiveDirectoryInfoRelay = new RelayCommand(
_ => ExecuteQuery(_consoleOutputInActiveDirectoryInfo));
_ => ExecuteQueryAsync(_consoleOutputInQueryBuilder));
ExecuteQueryAsyncFromActiveDirectoryInfoRelay =
new RelayCommand(ExecuteQueryAsyncFromComboBoxInActiveDirectoryInfo);
ImportQueryFileRelay = new RelayCommand(ImportQueryFile);
CreateNewQueryFileRelay = new RelayCommand(CreateNewQueryFile);
AddCommandParameterComboBoxRelay = new RelayCommand(AddParameterComboBoxInQueryBuilder);
Expand All @@ -212,30 +200,49 @@
ClearConsoleOutputInActiveDirectoryInfoRelay = new RelayCommand(
_ => ClearConsoleOutput(_consoleOutputInActiveDirectoryInfo));
ClearQueryBuilderRelay = new RelayCommand(ClearQueryBuilder);

/* TODO: For Pieter
* Connect the relay property to for the execute button to you method that performs the execution.
*/

// TODO: Figure out how resolve the warning about the async method not being awaited.

InitializeActiveDirectoryCommandsAsync();
LoadSavedQueriesFromFile(); // Calls method to deserialize and load buttons.
}

// [ Methods ] ----------------------------------------------------------------- //

/* TODO: Info for Pieter to get started
* Create a class (outside of this one) that will contain three methods, all of each will perform one specific
* action, such as getting the users on the domain, getting computers on the domain, and getting the IP of each
* system on the domain.
* - The methods in this class should use the powershell executor to execute the specific command.
* - Make method async, and utilize the async execute methods in the powershell executor.
*
* public async Task<the return type> MethodName() { } // don't forget to use await when dealing with async
* methods.
*
* In this class, create a method or two, that will be used to execute the specific selected action.
*/
private async void ExecuteQueryAsyncFromComboBoxInActiveDirectoryInfo(object _)
{
if (SelectedCommandFromComboBoxInActiveDirectoryInfo is null)
{
Trace.WriteLine("No command selected.");
MessageBox.Show("You must first select an option to execute.",
"Warning",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}

string selectedOption = SelectedCommandFromComboBoxInActiveDirectoryInfo;
if (_activeDirectoryInfo.AvailableOptions.TryGetValue(selectedOption, out var method))
{
PSOutput result = await method.Invoke();

if (result.HadErrors)
{
ConsoleOutputInActiveDirectoryInfo.Append(result.StdErr);
}
else
{
ConsoleOutputInActiveDirectoryInfo.Append(result.StdOut);
}
}
// This is more of an internal error catch, as even through this command shouldn't fail, it's better safe than
// sorry.
else
{
string errorMessage =
"Internal Error: The selected option was not found in the dictionary: " + $"{selectedOption}";
MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
throw new KeyNotFoundException("The selected option was not found in the dictionary.");
}
}

private void ClearConsoleOutput(AppConsole appConsole)
{
Expand Down Expand Up @@ -266,8 +273,8 @@

_isEditing = currentQuery;
QueryEditingEnabled = true;
QueryName = currentQuery.QueryName;

Check warning on line 276 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference assignment.

Check warning on line 276 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference assignment.
QueryDescription = currentQuery.QueryDescription;

Check warning on line 277 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference assignment.

Check warning on line 277 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference assignment.

// Fill in the commandName
Command chosenCommand = ADCommands.FirstOrDefault(item => item.CommandText == currentQuery.PSCommandName)!;
Expand All @@ -287,7 +294,7 @@
}

// Fill in Parameters and values
for (var i = 0; i < currentQuery.PSCommandParameters.Length; i++)

Check warning on line 297 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Dereference of a possibly null reference.
{
Trace.WriteLine(currentQuery.PSCommandParameters[i]);

Expand All @@ -297,13 +304,15 @@
// Fill in the parameter boxes
DynamicallyAvailableADCommandParametersComboBox[i].SelectedParameter = currentQuery.PSCommandParameters[i];
DynamicallyAvailableADCommandParameterValueTextBox[i].SelectedParameterValue =
currentQuery.PSCommandParameterValues[i];

Check warning on line 307 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Dereference of a possibly null reference.
}
}

private async void ExecuteQueryFromQueryStackPanel(object queryButton)
{
if (queryButton is not Button currentButton)
var currentButton = queryButton as Button;

if (currentButton is null)
{
Trace.WriteLine("No button selected.");
MessageBox.Show("To execute a query, you must first select a query.",
Expand All @@ -312,6 +321,7 @@
MessageBoxImage.Warning);
return;
}


var buttonQuery = (Query)currentButton.Tag;
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder, buttonQuery.Command);
Expand Down Expand Up @@ -395,19 +405,21 @@
}
}

// TODO: Possibly change Task to void?
private async Task ExecuteQuery(AppConsole appConsole, Command? command = null)
// It's okay to suppress this warning because this method is called within the constructor. There is more than enough
// time for the method to complete before the user interacts with the GUI.
#pragma warning disable S3168
private async void ExecuteQueryAsync(AppConsole appConsole, Command? command = null)
{
await ExecuteQueryCoreAsync(appConsole, command);
}

// TODO: Possibly change Task to void?
private async Task InitializeActiveDirectoryCommandsAsync()
private async void InitializeActiveDirectoryCommandsAsync()
{
ObservableCollection<Command> list = await ADCommandsFetcher.GetADCommands();
ADCommands = new ObservableCollection<Command>(list);
OnPropertyChanged(nameof(ADCommands));
}
#pragma warning restore S3168

private async Task LoadCommandParametersAsync(Command? selectedCommand)
{
Expand Down Expand Up @@ -448,7 +460,7 @@
{
// Add selected parameters and their values to the command.
UpdateSelectedCommand();
result = await _psExecutor.ExecuteAsync(SelectedCommandFromComboBoxInQueryBuilder);

Check warning on line 463 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference argument for parameter 'command' in 'Task<PSOutput> PSExecutor.ExecuteAsync(Command command)'.
}

if (result.HadErrors)
Expand Down Expand Up @@ -491,7 +503,20 @@

private async void OutputExecutionResultsToTextFileAsync(object _)
{
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder);

if (_.GetType() == typeof(Button))
{
var currentButton = _ as Button;
Query buttonQuery;

buttonQuery = (Query)currentButton!.Tag;
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder, buttonQuery.Command);

}
else
{
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder);
}

// Filepath
// Write the text to a file & prompt user for the location
Expand Down Expand Up @@ -519,7 +544,20 @@
/// <param name="_">Represents the object that the command is bound to</param>
private async void OutputExecutionResultsToCsvFileAsync(object _)
{
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder);

if (_.GetType() == typeof(Button))
{
var currentButton = _ as Button;
Query buttonQuery;

buttonQuery = (Query)currentButton!.Tag;
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder, buttonQuery.Command);

}
else
{
await ExecuteQueryCoreAsync(ConsoleOutputInQueryBuilder);
}

var csv = new StringBuilder();
string[] output = ConsoleOutputInQueryBuilder.ConsoleOutput.Split(' ', '\n');
Expand Down Expand Up @@ -570,10 +608,6 @@
/// <summary>
/// This method is for getting the currently selected command at anytime
/// </summary>
/// <note>
/// TODO: !Still in the works!
/// TODO: Does this method do the same thing an another method?
/// </note>
private void UpdateSelectedCommand()
{
if (SelectedCommandFromComboBoxInQueryBuilder is null)
Expand Down Expand Up @@ -642,7 +676,7 @@
// TODO: Possibly provide more comprehensive error handling.
catch (Exception ex)
{
Trace.WriteLine(ex);
MessageBox.Show(ex.Message);
}
}

Expand Down Expand Up @@ -754,7 +788,7 @@
"Warning",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return null; // TODO: Figure out what to return here!!!

Check warning on line 791 in ActiveDirectoryQuerier/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Possible null reference return.
}

GetCurrentQuery();
Expand All @@ -770,9 +804,9 @@
MenuItem menuItem1 =
new() { Header = "Execute", Command = ExecuteQueryFromQueryStackPanelRelay, CommandParameter = newButton };

MenuItem outputToCsv = new() { Header = "Output to CSV", Command = OutputToCsvFileRelay };
MenuItem outputToText = new() { Header = "Output to Text", Command = OutputToTextFileRelay };
MenuItem outputToConsole = new() { Header = "Execute to Console", Command = ExecuteQueryFromQueryBuilderRelay };
MenuItem outputToCsv = new() { Header = "Output to CSV", Command = OutputToCsvFileRelay, CommandParameter = newButton };
MenuItem outputToText = new() { Header = "Output to Text", Command = OutputToTextFileRelay, CommandParameter = newButton };
MenuItem outputToConsole = new() { Header = "Execute to Console", Command = ExecuteQueryFromQueryStackPanelRelay, CommandParameter = newButton };

menuItem1.Items.Add(outputToCsv);
menuItem1.Items.Add(outputToText);
Expand Down
Loading