diff --git a/About.xaml.cs b/About.xaml.cs deleted file mode 100644 index 0b4f00a..0000000 --- a/About.xaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Windows; - -namespace TheFlightSims.HyperVDPD -{ - public partial class About : Window - { - public About() - { - InitializeComponent(); - } - } -} diff --git a/AddDevice.xaml.cs b/AddDevice.xaml.cs index 77f9fd9..093d0bb 100644 --- a/AddDevice.xaml.cs +++ b/AddDevice.xaml.cs @@ -2,15 +2,41 @@ using System.Linq; using System.Management; using System.Windows; + +using TheFlightSims.HyperVDPD.DefaultUI; using TheFlightSims.HyperVDPD.WMIProperties; +/* + * Primary namespace for HyperV-DPD application + * It contains the main window and all related methods for the core application + */ namespace TheFlightSims.HyperVDPD { + /* + * Add Device Window class + * It is used to add device into the selected VM + */ public partial class AddDevice : Window { + //////////////////////////////////////////////////////////////// + /// Global Properties and Constructors Region + /// This region contains global properties and constructors + /// for the MachineMethods class. + //////////////////////////////////////////////////////////////// + + /* + * Global properties + * deviceId: Device ID of the selected device + * machine: WMI namespace of the machine + */ + protected string deviceId; protected MachineMethods machine; + /* + * Constructor of the MainWindow class + * Initializes the components and sets up event handlers + */ public AddDevice(MachineMethods machine) { this.machine = machine; @@ -18,12 +44,19 @@ public AddDevice(MachineMethods machine) InitializeComponent(); } + + //////////////////////////////////////////////////////////////// + /// User Action Methods Region + /// This region contains methods that handle user actions. + /// For example, button clicks, changes in order. + //////////////////////////////////////////////////////////////// + /* - * Button and UI methods behaviour.... + * Actions for button "Add Device" */ - private void AddDeviceButton_Click(object sender, RoutedEventArgs e) { + // If the selected item is not null (nothing is selected), proceed if (DeviceList.SelectedItem != null) { deviceId = DeviceList.SelectedItem.GetType().GetProperty("DeviceId").GetValue(DeviceList.SelectedItem, null).ToString(); @@ -31,7 +64,8 @@ private void AddDeviceButton_Click(object sender, RoutedEventArgs e) } else { - MessageBox.Show( + // Notify user if nothing is selected + _ = MessageBox.Show( "Please select a device to add.", "Warning", MessageBoxButton.OK, @@ -40,53 +74,78 @@ private void AddDeviceButton_Click(object sender, RoutedEventArgs e) } } + /* + * Actions for button "Close" + */ private void AddDeviceCloseButton_Click(object sender, RoutedEventArgs e) { Close(); } + //////////////////////////////////////////////////////////////// + /// Non-User Action Methods Region + /// + /// This region contains methods that do not handle user actions. + /// + /// Think about this is the back-end section. + /// It should not be in a seperated class, because it directly interacts with the UI elements. + //////////////////////////////////////////////////////////////// + /* - * Non-button methods + * Start getting the device ID */ - public string GetDeviceId() { + UpdateDevices(); - ShowDialog(); + _ = ShowDialog(); + // Return the device ID back to the main windows return deviceId; } - private void UpdateDevices() + /* + * Update device list + */ + private async void UpdateDevices() { + // Clear the device list DeviceList.Items.Clear(); try { - foreach (ManagementObject device in machine.GetObjects("Win32_PnPEntity", "Status, PNPClass, Name, DeviceID").Cast()) + // Loop for devices in Win32_PnPEntity + using (ManagementObjectCollection pnpEntityList = machine.GetObjects("Win32_PnPEntity", "Status, PNPClass, Name, DeviceID")) { - string deviceStatus = device["Status"]?.ToString() ?? "Unknown"; - string deviceType = device["PNPClass"]?.ToString() ?? "Unknown"; - string deviceName = device["Name"]?.ToString() ?? "Unknown"; - string deviceId = device["DeviceID"]?.ToString() ?? "Unknown"; - - if (!deviceId.StartsWith("PCI")) + foreach (ManagementObject device in pnpEntityList.Cast()) { - continue; - } + string deviceStatus = device["Status"]?.ToString() ?? "Unknown"; + string deviceType = device["PNPClass"]?.ToString() ?? "Unknown"; + string deviceName = device["Name"]?.ToString() ?? "Unknown"; + string deviceId = device["DeviceID"]?.ToString() ?? "Unknown"; - DeviceList.Items.Add(new - { - DeviceStatus = deviceStatus, - DeviceType = deviceType, - DeviceName = deviceName, - DeviceId = deviceId - }); + // If the device is not PCI, skip to the next device + if (!deviceId.StartsWith("PCI")) + { + device.Dispose(); + continue; + } + + _ = DeviceList.Items.Add(new + { + DeviceStatus = deviceStatus, + DeviceType = deviceType, + DeviceName = deviceName, + DeviceId = deviceId + }); + + device.Dispose(); + } } } catch (Exception ex) { - WMIDefaultValues.HandleException(ex, machine.GetComputerName()); + (new ExceptionView()).HandleException(ex); } } } diff --git a/ChangeMemorySpace.xaml.cs b/ChangeMemorySpace.xaml.cs index ab7f3f0..08885c2 100644 --- a/ChangeMemorySpace.xaml.cs +++ b/ChangeMemorySpace.xaml.cs @@ -1,29 +1,51 @@ -using System; -using System.Windows; +using System.Windows; +/* + * Primary namespace for HyperV-DPD application + * It contains the main window and all related methods for the core application + */ namespace TheFlightSims.HyperVDPD { + /* + * Change Memory Space Window + */ public partial class ChangeMemorySpace : Window { + //////////////////////////////////////////////////////////////// + /// Global Properties and Constructors Region + /// This region contains global properties and constructors + /// for the MachineMethods class. + //////////////////////////////////////////////////////////////// + /* * Global properties + * memRange as tuple of low and high memory */ - protected (UInt64 lowMem, UInt64 highMem) memRange; + protected (ulong lowMem, ulong highMem) memRange; + // Constructor of the Change Memory space window public ChangeMemorySpace(string vmName) { Title += $" for {vmName}"; InitializeComponent(); } + //////////////////////////////////////////////////////////////// + /// User Action Methods Region + /// This region contains methods that handle user actions. + /// For example, button clicks, changes in order. + //////////////////////////////////////////////////////////////// + /* - * Button-based methods + * Actions for button "Confirm" */ private void Confirm_Button(object sender, RoutedEventArgs e) { - if (UInt64.TryParse(LowMemory_TextBox.Text, out UInt64 lowMemCompare) && UInt64.TryParse(HighMemory_TextBox.Text, out UInt64 highMemCompare)) + // Try to parse number in the box + if (ulong.TryParse(LowMemory_TextBox.Text, out ulong lowMemCompare) && ulong.TryParse(HighMemory_TextBox.Text, out ulong highMemCompare)) { - if ((lowMemCompare >= 128 && lowMemCompare <= 3584) && (highMemCompare >= 4096 && highMemCompare <= (UInt64.MaxValue - 2))) + // Check for low MMIO and high MMIO. If valid, return the value + if ((lowMemCompare >= 128 && lowMemCompare <= 3584) && (highMemCompare >= 4096 && highMemCompare <= (ulong.MaxValue - 2))) { memRange.lowMem = lowMemCompare; memRange.highMem = highMemCompare; @@ -31,7 +53,8 @@ private void Confirm_Button(object sender, RoutedEventArgs e) } else { - MessageBox.Show( + // Notify user for invalid valid input + _ = MessageBox.Show( "Make sure the Low MMIO Gap is in range (128, 3584) and High MMIO is larger than 4096", "Warning", MessageBoxButton.OK, @@ -41,7 +64,7 @@ private void Confirm_Button(object sender, RoutedEventArgs e) } else { - MessageBox.Show( + _ = MessageBox.Show( "Please enter a positive integer in both box.", "Warning", MessageBoxButton.OK, @@ -50,17 +73,29 @@ private void Confirm_Button(object sender, RoutedEventArgs e) } } + /* + * Action for button "Cancel" + */ private void Cancel_Button(object sender, RoutedEventArgs e) { Close(); } + //////////////////////////////////////////////////////////////// + /// Non-User Action Methods Region + /// + /// This region contains methods that do not handle user actions. + /// + /// Think about this is the back-end section. + /// It should not be in a seperated class, because it directly interacts with the UI elements. + //////////////////////////////////////////////////////////////// + /* - * Non-button methods + * Return Value */ - public (UInt64, UInt64) ReturnValue() + public (ulong, ulong) ReturnValue() { - ShowDialog(); + _ = ShowDialog(); return memRange; } diff --git a/CheckForAssignableDevice.xaml.cs b/CheckForAssignableDevice.xaml.cs index 7742a89..f795da6 100644 --- a/CheckForAssignableDevice.xaml.cs +++ b/CheckForAssignableDevice.xaml.cs @@ -4,159 +4,342 @@ using System.Management; using System.Threading.Tasks; using System.Windows; +using TheFlightSims.HyperVDPD.DefaultUI; using TheFlightSims.HyperVDPD.WMIProperties; +/* + * Primary namespace for HyperV-DPD application + * It contains the main window and all related methods for the core application + */ namespace TheFlightSims.HyperVDPD { + /* + * Check for assignable device window + * It is used to check for devices that are assignable for PCIP + */ public partial class CheckForAssignableDevice : Window { + //////////////////////////////////////////////////////////////// + /// Global Properties and Constructors Region + /// This region contains global properties and constructors + /// for the MachineMethods class. + //////////////////////////////////////////////////////////////// + + /* + * Global properties + * machine: Instance of MachineMethods class to interact with WMI + */ protected MachineMethods machine; + /* + * Constructor of the class + */ public CheckForAssignableDevice(MachineMethods machine) { this.machine = machine; - InitializeComponent(); } + //////////////////////////////////////////////////////////////// + /// User Action Methods Region + /// This region contains methods that handle user actions. + /// For example, button clicks, changes in order. + //////////////////////////////////////////////////////////////// + /* - * Button and UI methods behaviour.... + * Actions for "Check" button */ private async void Check_Button(object sender, RoutedEventArgs e) { await CheckDevices(); } + /* + * Actions for "Cancel" button + */ private void Cancel_Button(object sender, RoutedEventArgs e) { Close(); } + //////////////////////////////////////////////////////////////// + /// Non-User Action Methods Region + /// + /// This region contains methods that do not handle user actions. + /// + /// Think about this is the back-end section. + /// It should not be in a seperated class, because it directly interacts with the UI elements. + //////////////////////////////////////////////////////////////// + /* - * Non-button methods + * Task for Check devices */ private async Task CheckDevices() { - + // Clear the device list AssignableDevice_ListView.Items.Clear(); + /* + * Check for assignable devices + * + * How does it work? + * 1. It looks up for device data in + * - Win32_PnPEntity, to get its Hardware ID, Hardware Friendly Name (Caption), and current status. + * Construct all data into a list, follows this structure of a tuple: + * (string deviceID, string devCaption, devStatus) + * + * - Win32_PnPDevice, to find if the device is actual device + * Construct all data in a hash set, where the devices are Hardware ID unique. + * + * - Win32_PNPAllocatedResource, to find the starting address of the Hardware ID. + * Note: A device may have multiple starting memory + * Construct into a list, follows this structure: + * (UInt64 startingAddress, string deviceID) + * + * - Win32_DeviceMemoryAddress, to look up for possible address range, and calculate the required memory + * Construct into a hash map, follows this structure: + * + * + * 2. For each device data, put into the boxes. Converting values from MOF structure into processable data can be seen below. + * 3. From the data structure, try to validate if a device from the Win32_PnPEntity matches the following condition + * - The device is PCI device (required) + * - The device exists in Win32_PnPDevice (required) + * - If possible, check for the memory gap. Note that if the device is disabled, the gap cannot be calculated + * If any the required condition does not meet, notify user for the "unable to assignment" and display for memory gap as "N/A" + * If unable to calculate the gap, just display "N/A" + * + * Note: + * - All device ID must be normalized with a single "\" and not "\\" + * - Normally, the WMI object's property is formed into multivalue, and value seperation with validation is required. + * - Check for null, empty, or default (fallback) value in each case you get the value from the WMI. + * - Disposing the resouce after using it + * + * References: + * - https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-pnpentity + * - https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-pnpdevice + * - https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-pnpallocatedresource + * - https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-devicememoryaddress + * + * Limitation: + * Each resource has its own limitations. See below + */ try { await Task.Run(() => { machine.Connect("root\\cimv2"); - foreach (ManagementObject device in machine.GetObjects("Win32_PnPEntity", "DeviceID, Caption, Status").Cast()) + // API calls to get all PnP entities + ManagementObjectCollection pnpDeviceEntity = machine.GetObjects("Win32_PnPEntity", "DeviceID, Caption, Status"); + ManagementObjectCollection pnpDevice = machine.GetObjects("Win32_PnPDevice", "SystemElement"); + ManagementObjectCollection pnpDeviceAllocatedResource = machine.GetObjects("Win32_PNPAllocatedResource", "Antecedent, Dependent"); + ManagementObjectCollection pnpDeviceMemorySpace = machine.GetObjects("Win32_DeviceMemoryAddress", "StartingAddress, EndingAddress"); + + /* + * Device list on the computer + * + * pnpDeviceEntityMap - List + * + * Data structure + * deviceId (string): The ID of the device on the system. Example: PCI\VEN_10DE&DEV_2520&SUBSYS_12F21462&REV_A1 + * devCaption (string): Actual device name. Example: NVIDIA GeForce RTX 3060 Laptop GPU + * devStatus (string): The status of the device. See the Win32_PnPEntity for detailed status info + */ + List<(string deviceId, string devCaption, string devStatus)> pnpDeviceEntityMap = new List<(string, string, string)>(); + foreach (ManagementObject device in pnpDeviceEntity.Cast()) { string deviceId = device["DeviceID"]?.ToString(); string devCaption = device["Caption"]?.ToString(); string devStatus = device["Status"]?.ToString(); - if (deviceId == null || devCaption == null || devStatus == null) + if (!string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(devCaption) && !string.IsNullOrEmpty(devStatus)) { - device.Dispose(); - continue; + // Don't forget to normalize the device id + pnpDeviceEntityMap.Add( + (deviceId.Replace("\\\\", "\\").Trim(), devCaption, devStatus) + ); } - if (deviceId.StartsWith("PCI\\")) - { - bool isAssignable = true; - HashSet startingAddresses = new HashSet(); - string deviceNote = string.Empty; - double memoryGap = 0; + // Dispose after using it + device.Dispose(); + } - foreach (ManagementObject actualPnPDevice in machine.GetObjects("Win32_PnPDevice", "SameElement, SystemElement").Cast()) + /* + * PnP device on the computer + * + * pnpDeviceSet - Hash Set + * + * Data structure + * actualPnpDevString (string): the device id exist on the class + * + * How does the WMI property contribute for data + * SystemElement: \\\root\cimv2:Win32_PnPEntity.DeviceID="" + * + */ + HashSet pnpDeviceSet = new HashSet(); + foreach (ManagementObject device in pnpDevice.Cast()) + { + string PnpDevString = device["SystemElement"]?.ToString(); + + if (!string.IsNullOrEmpty(PnpDevString)) + { + // Seperate between the path and pointer + string[] SystemElementArr = PnpDevString.Split(':'); + + // In case the pointer does not exist (Really?) + if (SystemElementArr.Length < 2) { - string actualPnpDevString = actualPnPDevice["SystemElement"]?.ToString(); + // Dispose the device and go on + device.Dispose(); + continue; + } - if (actualPnpDevString == null) - { - isAssignable = false; - actualPnPDevice.Dispose(); - continue; - } + // The array[1] is the pointer. + string actualPnpDevString = SystemElementArr[1].Replace("Win32_PnPEntity.DeviceID=", "").Trim('"'); + + // Add into the hash set. Don't forget to normalize + pnpDeviceSet.Add(actualPnpDevString.Replace("\\\\", "\\")); + } - if (actualPnpDevString.Contains(deviceId.Replace("\\", "\\\\"))) - { - isAssignable = true; - break; - } - else - { - isAssignable = false; - } - } + // Dispose the device instance + device.Dispose(); + } + + /* + * PnP Device Allocation Map + * + * pnpDeviceAllocatedResourceMap - List + * + * Data structure + * startingAddress (UInt64): starting address + * deviceID (string): device ID + * + * How does the WMI property contribute for data + * Antecedent: \\\root\cimv2:Win32_DeviceMemoryAddress.StartingAddress="" + * Dependent: \\\root\cimv2:Win32_PnPEntity.DeviceID="" + */ + List<(UInt64 startingAddress, string deviceID)> pnpDeviceAllocatedResourceMap = new List<(UInt64 startingAddress, string deviceID)>(); + foreach (ManagementObject resource in pnpDeviceAllocatedResource.Cast()) + { + string antecedentRes = resource["Antecedent"]?.ToString(); + string dependentRes = resource["Dependent"]?.ToString(); + + if (!string.IsNullOrEmpty(antecedentRes) && !string.IsNullOrEmpty(dependentRes)) + { + // Seperate between the path and pointer + string[] AntecedentArr = antecedentRes.Split(':'); + string[] DependentArr = dependentRes.Split(':'); - if (!isAssignable) + // In case the pointer does not exist (Really?) + if (AntecedentArr.Length < 2 || DependentArr.Length < 2) { - deviceNote += ((deviceNote.Length == 0) ? "" : "\n") + "The PCI device is not support either Express Endpoint, Embedded Endpoint, or Legacy Express Endpoint."; + // Dispose the device and go on + resource.Dispose(); + continue; } - foreach (ManagementObject deviceResource in machine.GetObjects("Win32_PNPAllocatedResource", "Antecedent, Dependent").Cast()) - { - string devResDependent = deviceResource["Dependent"]?.ToString(); + // The array[1] is the pointer. + UInt64 startingAddress = UInt64.Parse(AntecedentArr[1].Split('=')[1].Trim('"')); + string deviceID = DependentArr[1].Replace("Win32_PnPEntity.DeviceID=", "").Trim('"'); - if (devResDependent == null) - { - deviceResource.Dispose(); - continue; - } + // Add into the hash set. Don't forget to normalize + pnpDeviceAllocatedResourceMap.Add( + (startingAddress, deviceID.Replace("\\\\", "\\")) + ); + } - if (devResDependent.Contains(deviceId.Replace("\\", "\\\\"))) - { - startingAddresses.Add((deviceResource["Antecedent"].ToString().Split('=')[1]).Replace("\"", "")); - } + resource.Dispose(); + } - deviceResource.Dispose(); - } + /* + * PnP Device Memory Space Map + * + * pnpDeviceMemorySpaceMap - Dictionary/Hash Map + * + * Data structure + * startingAddress (UInt64): Starting address (unique) + * endingAddress (UInt64): Ending address + */ + Dictionary pnpDeviceMemorySpaceMap = new Dictionary(); + foreach (ManagementObject memorySpaces in pnpDeviceMemorySpace.Cast()) + { + string startingAddress = memorySpaces["StartingAddress"]?.ToString(); + string endingAddress = memorySpaces["EndingAddress"]?.ToString(); - if (!devStatus.ToLower().Equals("ok")) - { - deviceNote += ((deviceNote.Length == 0) ? "" : "\n") + "Device is disabled. Please re-enable the device to let the program check the memory gap."; - isAssignable = false; - } + if (!string.IsNullOrEmpty(startingAddress) && !string.IsNullOrEmpty(endingAddress)) + { + pnpDeviceMemorySpaceMap.Add( + UInt64.Parse(startingAddress), + UInt64.Parse(endingAddress) + ); + } - if (isAssignable) - { - foreach (ManagementObject deviceMem in machine.GetObjects("Win32_DeviceMemoryAddress", "StartingAddress, EndingAddress").Cast()) - { - string startAddrString = deviceMem["StartingAddress"]?.ToString(); - string endAddrString = deviceMem["EndingAddress"]?.ToString(); + memorySpaces.Dispose(); + } - if (startAddrString == null || endAddrString == null) - { - deviceMem.Dispose(); - continue; - } + // Dispose all resources, since the data has been processed + pnpDeviceEntity.Dispose(); + pnpDevice.Dispose(); + pnpDeviceAllocatedResource.Dispose(); + pnpDeviceMemorySpace.Dispose(); - if (startingAddresses.Contains(startAddrString)) + foreach ((string id, string caption, string status) in pnpDeviceEntityMap) + { + bool isPciDevice = true, isActualDevice = true, isEnabled = true; + string deviceNote = string.Empty; + UInt64 memoryGap = 0; + + if (!id.StartsWith("PCI\\")) + { + isPciDevice = false; + deviceNote += ((deviceNote.Length == 0) ? "" : "\n") + "The device is not a PCI."; + } + + if (!pnpDeviceSet.Contains(id)) + { + isActualDevice = false; + deviceNote += ((deviceNote.Length == 0) ? "" : "\n") + + "The PCI device is not support either Express Endpoint, Embedded Endpoint, or Legacy Express Endpoint."; + } + + if (!string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase)) + { + isEnabled = false; + deviceNote += ((deviceNote.Length == 0) ? "" : "\n") + + "Device is disabled. Please re-enable the device to let the program check the memory gap."; + } + + if (isPciDevice && isActualDevice && isEnabled) + { + foreach ((UInt64 startMemory, string deviceId) in pnpDeviceAllocatedResourceMap) + { + if (id.Equals(deviceId)) + { + if (pnpDeviceMemorySpaceMap.ContainsKey(startMemory)) { - memoryGap += Math.Ceiling((Double.Parse(endAddrString) - Double.Parse(startAddrString)) / 1048576.0); + UInt64 endAddr = pnpDeviceMemorySpaceMap[startMemory]; + memoryGap += (endAddr - startMemory + 1048575UL) / 1048576UL; } - - deviceMem.Dispose(); } } + } - Dispatcher.Invoke(() => + Dispatcher.Invoke(() => + { + _ = AssignableDevice_ListView.Items.Add(new { - AssignableDevice_ListView.Items.Add(new - { - DeviceId = deviceId, - DeviceName = devCaption, - DeviceGap = (!isAssignable) ? WMIDefaultValues.notAvailable : $"{memoryGap} MB", - DeviceNote = deviceNote - }); + DeviceId = id, + DeviceName = caption, + DeviceGap = (isPciDevice && isActualDevice && isEnabled) ? $"{memoryGap} MB" : "N/A", + DeviceNote = deviceNote }); - } - - device.Dispose(); + }); } }); } catch (Exception ex) { - WMIDefaultValues.HandleException(ex, machine.GetComputerName()); + (new ExceptionView()).HandleException(ex); } } } diff --git a/CheckForAssignableDevices.xaml b/CheckForAssignableDevices.xaml deleted file mode 100644 index bc285ba..0000000 --- a/CheckForAssignableDevices.xaml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CheckForAssignableDevices.xaml.cs b/CheckForAssignableDevices.xaml.cs deleted file mode 100644 index b56cba2..0000000 --- a/CheckForAssignableDevices.xaml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; - -namespace DDAGUI -{ - /// - /// Interaction logic for CheckForAssignableDevices.xaml - /// - public partial class CheckForAssignableDevices : Window - { - public CheckForAssignableDevices() - { - InitializeComponent(); - } - } -} diff --git a/ConnectForm.xaml b/ConnectForm.xaml deleted file mode 100644 index 26d3f0b..0000000 --- a/ConnectForm.xaml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ConnectForm.xaml.cs b/ConnectForm.xaml.cs deleted file mode 100644 index 9860c7c..0000000 --- a/ConnectForm.xaml.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Security; -using System.Windows; - -namespace TheFlightSims.HyperVDPD -{ - public partial class ConnectForm : Window - { - /* - * Global properties - */ - private bool isConnectLocally; - - public ConnectForm() - { - isConnectLocally = false; - InitializeComponent(); - } - - /* - * Button-based methods - */ - - private void ConnetToExternal_Button(object sender, RoutedEventArgs e) - { - if (ComputerName_TextBox.Text.Length == 0 || UserName_TextBox.Text.Length == 0) - { - MessageBox.Show( - "Please don't leave computer name and user name empty", - "Error", - MessageBoxButton.OK, - MessageBoxImage.Warning - ); - } - else - { - DialogResult = true; - } - } - - private void ConnectToLocal_Button(object sender, RoutedEventArgs e) - { - isConnectLocally = true; - DialogResult = true; - } - - private void Cancel_Button(object sender, RoutedEventArgs e) - { - Close(); - } - - /* - * Non-button methods - */ - public (string, string, SecureString) ReturnValue() - { - ShowDialog(); - - string computerName = (isConnectLocally) ? "localhost" : ComputerName_TextBox.Text; - string userName = (isConnectLocally) ? "" : UserName_TextBox.Text; - SecureString password = new SecureString(); - - if (!isConnectLocally) - { - foreach (char c in Password_TextBox.Password) - { - password.AppendChar(c); - } - } - - return (computerName, userName, password); - } - } -} diff --git a/About.xaml b/DefaultUI/About.xaml similarity index 94% rename from About.xaml rename to DefaultUI/About.xaml index 98f4455..fbc2483 100644 --- a/About.xaml +++ b/DefaultUI/About.xaml @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DefaultUI/ConnectForm.xaml.cs b/DefaultUI/ConnectForm.xaml.cs new file mode 100644 index 0000000..0701ee5 --- /dev/null +++ b/DefaultUI/ConnectForm.xaml.cs @@ -0,0 +1,121 @@ +using System.Security; +using System.Windows; + +/* The Default UI includes + * 1. ConnectForm.xaml - A form to connect to a local or remote computer + * 2. ExceptionView.xaml - A window to handle exception messages + * 3. About.xaml - A window to display information about the application + */ +namespace TheFlightSims.HyperVDPD.DefaultUI +{ + /* + * Connection Form class + * It is used to get connection information from the user + * Expected behaviour: + * 1. User enter the computer name, username, and password to connect to a remote computer + * 2. The application tries verify the connection information + * 3. If what user enters is valid, the form closes and returns the information + */ + public partial class ConnectForm : Window + { + + //////////////////////////////////////////////////////////////// + /// Global Properties and Constructors Region + /// This region contains global properties and constructors + /// for the MachineMethods class. + //////////////////////////////////////////////////////////////// + + /* + * Global properties + * isConnectLocally: A boolean to indicate whether the user wants to connect to the local computer + */ + private bool isConnectLocally; + + // Constructor of the ConnectForm class + public ConnectForm() + { + isConnectLocally = false; + InitializeComponent(); + } + + //////////////////////////////////////////////////////////////// + /// User Action Methods Region + /// This region contains methods that handle user actions. + /// For example, button clicks, changes in order. + //////////////////////////////////////////////////////////////// + + /* + * Connect to External Button Click Event - The "Connect" button + * Validates the input fields and sets the DialogResult to true if valid + */ + private void ConnetToExternal_Button(object sender, RoutedEventArgs e) + { + // Validate that none of the input fields are empty + if ( + ComputerName_TextBox.Text.Length == 0 || + UserName_TextBox.Text.Length == 0 || + Password_TextBox.Password.Length == 0) + { + _ = MessageBox.Show("Please don't leave anything empty", "Error", MessageBoxButton.OK, MessageBoxImage.Warning); + } + else + { + // Close the form with a DialogResult of true + DialogResult = true; + } + } + + /* + * Connect to Local Button Click Event - The "Connect to Local" button + * Sets the isConnectLocally flag to true and sets the DialogResult to true + */ + private void ConnectToLocal_Button(object sender, RoutedEventArgs e) + { + isConnectLocally = true; + DialogResult = true; + } + + /* + * Cancel Button Click Event - The "Cancel" button + * Closes the form without setting a DialogResult + */ + private void Cancel_Button(object sender, RoutedEventArgs e) + { + Close(); + } + + //////////////////////////////////////////////////////////////// + /// Non-User Action Methods Region + /// + /// This region contains methods that do not handle user actions. + /// + /// Think about this is the back-end section. + /// It should not be in a seperated class, because it directly interacts with the UI elements. + //////////////////////////////////////////////////////////////// + + /* + * Return Value Method + * Returns a tuple containing the computer name, username, and password as a SecureString + */ + public (string, string, SecureString) ReturnValue() + { + _ = ShowDialog(); + + // Prepare the return values based on whether connecting locally or remotely + string computerName = (isConnectLocally) ? "localhost" : ComputerName_TextBox.Text; + string userName = (isConnectLocally) ? "" : UserName_TextBox.Text; + SecureString password = new SecureString(); + + // Only populate the password if connecting remotely + if (!isConnectLocally) + { + foreach (char c in Password_TextBox.Password) + { + password.AppendChar(c); + } + } + + return (computerName, userName, password); + } + } +} diff --git a/DefaultUI/ExceptionView.xaml b/DefaultUI/ExceptionView.xaml new file mode 100644 index 0000000..9aa9267 --- /dev/null +++ b/DefaultUI/ExceptionView.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + An exception has occurred in the application. + + You may choose to close the application or ignore the error. + + + + + + + + + + + + + + + + + + + + + + diff --git a/DefaultUI/ExceptionView.xaml.cs b/DefaultUI/ExceptionView.xaml.cs new file mode 100644 index 0000000..7eba910 --- /dev/null +++ b/DefaultUI/ExceptionView.xaml.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using System.Windows; + +/* The Default UI includes + * 1. ConnectForm.xaml - A form to connect to a local or remote computer + * 2. ExceptionView.xaml - A window to handle exception messages + */ +namespace TheFlightSims.HyperVDPD.DefaultUI +{ + /* + * Exception View class + * It is used to handle exception messages + * + * Excepted behaviour: + * 1. An exception is passed to the HandleException method + * 2. If the exception is of an allowed type, show the exception message + * and give the user the option to ignore it; else, force close the application + */ + public partial class ExceptionView : Window + { + //////////////////////////////////////////////////////////////// + /// Global Properties and Constructors Region + /// This region contains global properties and constructors + /// for the MachineMethods class. + //////////////////////////////////////////////////////////////// + + /* + * Global properties + * allowedExceptionTypes: A list of exception types that are allowed to be ignored + */ + private readonly HashSet allowedExceptionTypes = new HashSet + { + typeof(UnauthorizedAccessException), + typeof(COMException), + typeof(ManagementException), + typeof(NullReferenceException) + }; + + // Constructor of the Exception View class + public ExceptionView() + { + InitializeComponent(); + } + + //////////////////////////////////////////////////////////////// + /// Non-User Action Methods Region + /// + /// This region contains methods that do not handle user actions. + /// + /// Think about this is the back-end section. + /// It should not be in a seperated class, because it directly interacts with the UI elements. + //////////////////////////////////////////////////////////////// + + /* + * Click Ignore Exception Button - The "Ignore" button + * Let user ignore the exception and continue using the application + */ + private void Click_IgnoreException(object sender, RoutedEventArgs e) + { + Close(); + } + + /* + * Click Close Application Button - The "Close Application" button + * Close the application in case of a critical or unhandled exception + */ + private void Click_CloseApp(object sender, RoutedEventArgs e) + { + Application.Current.Shutdown(); + } + + //////////////////////////////////////////////////////////////// + /// User Action Methods Region + /// This region contains methods that handle user actions. + /// For example, button clicks, changes in order. + //////////////////////////////////////////////////////////////// + + /* + * Handle Exception Method + * Get the exception info, and decide whether to allow the user to ignore it + */ + public void HandleException(Exception e) + { + if (e == null) + { + return; + } + + // Check if the exception type is in the allowed list + bool isAllowed = this.allowedExceptionTypes.Any(t => t.IsInstanceOfType(e)); + + if (!isAllowed) + { + ExceptionMessage_TextBlock.Text = "An unexpected error has occurred.\n" + + "You should close the application and check for the system"; + Button_Ignore.Visibility = Visibility.Collapsed; + } + +#if !DEBUG + DetailedExceptionDetail_TextBox.Text = e.Message; +#else + DetailedExceptionDetail_TextBox.Text = e.ToString(); +#endif + + ShowDialog(); + } + } +} diff --git a/HyperV-DPD.csproj b/HyperV-DPD.csproj index aeaff1a..0cb05c2 100644 --- a/HyperV-DPD.csproj +++ b/HyperV-DPD.csproj @@ -118,20 +118,22 @@ CheckForAssignableDevice.xaml - - ConnectForm.xaml - - + About.xaml AddDevice.xaml + + ConnectForm.xaml + + + ExceptionView.xaml + HyperVStatus.xaml - Designer MSBuild:Compile @@ -140,17 +142,21 @@ Designer MSBuild:Compile - - MSBuild:Compile + Designer + MSBuild:Compile - + Designer MSBuild:Compile - + + MSBuild:Compile Designer + + MSBuild:Compile + Designer Designer diff --git a/HyperVStatus.xaml b/HyperVStatus.xaml index 3edb5d9..58dec96 100644 --- a/HyperVStatus.xaml +++ b/HyperVStatus.xaml @@ -32,13 +32,13 @@ - - - + + +