From 162dd8803751610b1c8cedab6bf3abca2dc2e8d8 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Mon, 4 May 2026 17:44:43 -0400 Subject: [PATCH 1/8] =?UTF-8?q?New=20Files=20=E2=80=A2=20FolderHashScanFor?= =?UTF-8?q?m.cs=20=E2=80=94=20New=20dialog=20for=20folder-based=20hash=20f?= =?UTF-8?q?ile=20scanning=20with=20browse,=20subfolder=20toggle,=20file=20?= =?UTF-8?q?checklist,=20and=20hash=20type=20selection=20Modified=20Files?= =?UTF-8?q?=20CustomRuleConditionsPanel.cs=20=E2=80=A2=20Added=20hash=20mo?= =?UTF-8?q?de=20panel=20with=20Single=20File,=20Multiple=20Files=20radio?= =?UTF-8?q?=20buttons=20and=20Folder=20Scan...=20button=20=E2=80=A2=20"Fol?= =?UTF-8?q?der=20Scan..."=20button=20directly=20opens=20FolderHashScanForm?= =?UTF-8?q?=20dialog=20=E2=80=A2=20Multi-file=20and=20folder=20scan=20mode?= =?UTF-8?q?s=20batch=20files=20into=20a=20single=20FolderScan-type=20rule?= =?UTF-8?q?=20using=20New-CIPolicy=20-ScanPath=20(instead=20of=20one=20Pow?= =?UTF-8?q?erShell=20call=20per=20file)=20=E2=80=A2=20Selected=20files=20a?= =?UTF-8?q?re=20copied=20to=20a=20temp=20folder=20preserving=20subfolder?= =?UTF-8?q?=20structure=20from=20the=20source=20=E2=80=A2=20Stores=20Sourc?= =?UTF-8?q?eFolderPath=20and=20HashTypesToKeep=20on=20the=20rule=20for=20d?= =?UTF-8?q?ownstream=20processing=20FolderHashScanForm.cs=20=E2=80=A2=20Br?= =?UTF-8?q?owse=20folder=20with=20optional=20Include=20subfolders=20checkb?= =?UTF-8?q?ox=20=E2=80=A2=20Scan=20Folder=20button=20enumerates=20files=20?= =?UTF-8?q?by=20common=20PE/script=20extensions=20=E2=80=A2=20Select=20All?= =?UTF-8?q?=20/=20Deselect=20All=20for=20the=20file=20checklist=20?= =?UTF-8?q?=E2=80=A2=20Hash=20type=20checkboxes:=20Hash=20SHA1,=20Hash=20S?= =?UTF-8?q?HA256,=20Hash=20Page=20SHA1,=20Hash=20Page=20SHA256,=20and=20Al?= =?UTF-8?q?l=20(toggle)=20=E2=80=A2=20Exposes=20SelectedFiles,=20SelectedH?= =?UTF-8?q?ashTypes,=20SourceFolderPath,=20IncludeSubfolders,=20AllFilesSe?= =?UTF-8?q?lected=20=E2=80=A2=20Dark=20mode=20support=20SigningRules=5FCon?= =?UTF-8?q?trol.cs=20=E2=80=A2=20Added=20AddRuleToTableWithoutClosing()=20?= =?UTF-8?q?method=20to=20support=20batch=20rule=20insertion=20without=20cl?= =?UTF-8?q?osing=20the=20custom=20rules=20panel=20Policy.cs=20(PolicyCusto?= =?UTF-8?q?mRules=20class)=20=E2=80=A2=20Added=20HashTypesToKeep=20propert?= =?UTF-8?q?y=20(HashSet)=20=E2=80=94=20hash=20types=20to=20retain?= =?UTF-8?q?=20when=20filtering=20generated=20policy=20XML=20=E2=80=A2=20Ad?= =?UTF-8?q?ded=20SourceFolderPath=20property=20(string)=20=E2=80=94=20orig?= =?UTF-8?q?inal=20folder=20path=20for=20FriendlyName=20correction=20MainFo?= =?UTF-8?q?rm.cs=20=E2=80=A2=20Added=20using=20System.Linq=20=E2=80=A2=20F?= =?UTF-8?q?riendlyName=20fix:=20after=20scan,=20replaces=20temp=20folder?= =?UTF-8?q?=20path=20in=20each=20rule's=20FriendlyName=20with=20the=20orig?= =?UTF-8?q?inal=20source=20folder=20path=20(preserving=20subfolders)=20?= =?UTF-8?q?=E2=80=A2=20Hash=20type=20filtering:=20removes=20unwanted=20has?= =?UTF-8?q?h=20types=20(Hash=20SHA1,=20Hash=20Page=20SHA256,=20etc.)=20fro?= =?UTF-8?q?m=20generated=20policy=20based=20on=20user's=20checkbox=20selec?= =?UTF-8?q?tions=20=E2=80=A2=20Progress=20bar=20improvements:=20=E2=80=A2?= =?UTF-8?q?=20ProcessCustomValueRules(BackgroundWorker,=20SiPolicy)=20now?= =?UTF-8?q?=20reports=20incremental=20progress=200=E2=80=9325%=20per=20rul?= =?UTF-8?q?e=20=E2=80=A2=20ProcessSignerRules(BackgroundWorker,=20SiPolicy?= =?UTF-8?q?)=20shows=20"Processing=20rule=20X=20of=20Y=20..."=20with=20acc?= =?UTF-8?q?urate=20counts=20=E2=80=A2=20FolderScan=20shows=20phased=20stat?= =?UTF-8?q?us:=20"Scanning=20folder:=20...",=20"Scan=20complete.=20Applyin?= =?UTF-8?q?g=20hash=20type=20filters=20...",=20"Filtered:=20kept=20X=20of?= =?UTF-8?q?=20Y=20hash=20rules.",=20"Merging=20scanned=20policy=20rules=20?= =?UTF-8?q?..."=20=E2=80=A2=20ProgressChanged=20handler=20respects=20custo?= =?UTF-8?q?m=20UserState=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/app/src/FolderHashScanForm.cs | 465 ++++++++++++++++++ .../app/src/CustomRuleConditionsPanel.cs | 319 +++++++++++- WDAC-Policy-Wizard/app/src/MainForm.cs | 101 +++- WDAC-Policy-Wizard/app/src/Policy.cs | 6 + .../app/src/SigningRules_Control.cs | 30 ++ 5 files changed, 911 insertions(+), 10 deletions(-) create mode 100644 WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs diff --git a/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs b/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs new file mode 100644 index 00000000..195911ad --- /dev/null +++ b/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs @@ -0,0 +1,465 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace WDAC_Wizard +{ + /// + /// Form that allows users to scan a folder for files and select which ones to create hash rules for. + /// + public class FolderHashScanForm : Form + { + private Label labelFolderPath; + private TextBox textBoxFolderPath; + private Button buttonBrowseFolder; + private CheckBox checkBoxSubfolders; + private Button buttonScan; + private CheckedListBox checkedListBoxFiles; + private Button buttonSelectAll; + private Button buttonDeselectAll; + private Button buttonOK; + private Button buttonCancel; + private Label labelStatus; + private Label labelHashTypes; + private CheckBox checkBoxHashSha1; + private CheckBox checkBoxHashSha256; + private CheckBox checkBoxHashPageSha1; + private CheckBox checkBoxHashPageSha256; + private CheckBox checkBoxHashAll; + private Panel panelHashTypes; + + /// + /// The list of file paths the user selected + /// + public List SelectedFiles { get; private set; } + + /// + /// The original folder path that was scanned + /// + public string SourceFolderPath { get; private set; } + + /// + /// Whether subfolders were included in the scan + /// + public bool IncludeSubfolders { get; private set; } + + /// + /// Whether all scanned files were selected (no unchecking) + /// + public bool AllFilesSelected { get; private set; } + + /// + /// The hash types selected by the user to keep in the generated policy. + /// Values match FriendlyName patterns: "Hash Sha1", "Hash Sha256", "Hash Page Sha1", "Hash Page Sha256" + /// + public HashSet SelectedHashTypes { get; private set; } + + public FolderHashScanForm() + { + SelectedFiles = new List(); + SelectedHashTypes = new HashSet(StringComparer.OrdinalIgnoreCase); + InitializeComponents(); + } + + private void InitializeComponents() + { + this.Text = "Folder Hash Scan"; + this.Size = new Size(700, 600); + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.StartPosition = FormStartPosition.CenterParent; + + int yPos = 15; + + // Folder path label + labelFolderPath = new Label + { + Text = "Folder Path:", + Location = new Point(15, yPos), + Size = new Size(80, 20), + Font = new Font("Tahoma", 9F) + }; + this.Controls.Add(labelFolderPath); + + // Folder path textbox + textBoxFolderPath = new TextBox + { + Location = new Point(100, yPos), + Size = new Size(460, 24), + Font = new Font("Tahoma", 9F), + ReadOnly = true + }; + this.Controls.Add(textBoxFolderPath); + + // Browse button + buttonBrowseFolder = new Button + { + Text = "Browse...", + Location = new Point(570, yPos - 2), + Size = new Size(90, 26), + Font = new Font("Tahoma", 9F) + }; + buttonBrowseFolder.Click += ButtonBrowseFolder_Click; + this.Controls.Add(buttonBrowseFolder); + + yPos += 35; + + // Include subfolders checkbox + checkBoxSubfolders = new CheckBox + { + Text = "Include subfolders", + Location = new Point(100, yPos), + Size = new Size(160, 22), + Font = new Font("Tahoma", 9F), + Checked = false + }; + this.Controls.Add(checkBoxSubfolders); + + // Hash type checkboxes + labelHashTypes = new Label + { + Text = "Hash Types to Keep:", + Location = new Point(280, yPos), + Size = new Size(130, 20), + Font = new Font("Tahoma", 9F) + }; + this.Controls.Add(labelHashTypes); + + panelHashTypes = new Panel + { + Location = new Point(15, yPos + 25), + Size = new Size(650, 28) + }; + + checkBoxHashAll = new CheckBox + { + Text = "All", + Location = new Point(0, 2), + Size = new Size(50, 22), + Font = new Font("Tahoma", 9F), + Checked = true + }; + checkBoxHashAll.CheckedChanged += CheckBoxHashAll_CheckedChanged; + panelHashTypes.Controls.Add(checkBoxHashAll); + + checkBoxHashSha1 = new CheckBox + { + Text = "Hash SHA1", + Location = new Point(55, 2), + Size = new Size(100, 22), + Font = new Font("Tahoma", 9F), + Checked = true + }; + panelHashTypes.Controls.Add(checkBoxHashSha1); + + checkBoxHashSha256 = new CheckBox + { + Text = "Hash SHA256", + Location = new Point(160, 2), + Size = new Size(115, 22), + Font = new Font("Tahoma", 9F), + Checked = true + }; + panelHashTypes.Controls.Add(checkBoxHashSha256); + + checkBoxHashPageSha1 = new CheckBox + { + Text = "Hash Page SHA1", + Location = new Point(280, 2), + Size = new Size(130, 22), + Font = new Font("Tahoma", 9F), + Checked = true + }; + panelHashTypes.Controls.Add(checkBoxHashPageSha1); + + checkBoxHashPageSha256 = new CheckBox + { + Text = "Hash Page SHA256", + Location = new Point(415, 2), + Size = new Size(145, 22), + Font = new Font("Tahoma", 9F), + Checked = true + }; + panelHashTypes.Controls.Add(checkBoxHashPageSha256); + + this.Controls.Add(panelHashTypes); + + yPos += 55; + + // Scan button - on its own row for visibility + buttonScan = new Button + { + Text = "Scan Folder", + Location = new Point(100, yPos - 2), + Size = new Size(120, 28), + Font = new Font("Tahoma", 9F, FontStyle.Bold) + }; + buttonScan.Click += ButtonScan_Click; + this.Controls.Add(buttonScan); + + yPos += 35; + + // Status label + labelStatus = new Label + { + Text = "Select a folder and click Scan to find files.", + Location = new Point(15, yPos), + Size = new Size(650, 20), + Font = new Font("Tahoma", 9F) + }; + this.Controls.Add(labelStatus); + + yPos += 25; + + // Checked list box for files + checkedListBoxFiles = new CheckedListBox + { + Location = new Point(15, yPos), + Size = new Size(645, 320), + Font = new Font("Tahoma", 8.5F), + CheckOnClick = true, + HorizontalScrollbar = true + }; + this.Controls.Add(checkedListBoxFiles); + + yPos += 330; + + // Select All / Deselect All buttons + buttonSelectAll = new Button + { + Text = "Select All", + Location = new Point(15, yPos), + Size = new Size(90, 28), + Font = new Font("Tahoma", 9F) + }; + buttonSelectAll.Click += (s, e) => + { + for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) + checkedListBoxFiles.SetItemChecked(i, true); + }; + this.Controls.Add(buttonSelectAll); + + buttonDeselectAll = new Button + { + Text = "Deselect All", + Location = new Point(115, yPos), + Size = new Size(100, 28), + Font = new Font("Tahoma", 9F) + }; + buttonDeselectAll.Click += (s, e) => + { + for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) + checkedListBoxFiles.SetItemChecked(i, false); + }; + this.Controls.Add(buttonDeselectAll); + + // OK / Cancel buttons + buttonCancel = new Button + { + Text = "Cancel", + Location = new Point(560, yPos), + Size = new Size(100, 28), + Font = new Font("Tahoma", 9F), + DialogResult = DialogResult.Cancel + }; + this.Controls.Add(buttonCancel); + + buttonOK = new Button + { + Text = "Add Selected", + Location = new Point(450, yPos), + Size = new Size(100, 28), + Font = new Font("Tahoma", 9F) + }; + buttonOK.Click += ButtonOK_Click; + this.Controls.Add(buttonOK); + + this.AcceptButton = buttonOK; + this.CancelButton = buttonCancel; + + // Apply dark mode if needed + if (Properties.Settings.Default.useDarkMode) + { + ApplyDarkMode(); + } + } + + private void ApplyDarkMode() + { + this.BackColor = Color.FromArgb(30, 30, 30); + this.ForeColor = Color.White; + + foreach (Control ctrl in this.Controls) + { + ctrl.ForeColor = Color.White; + if (ctrl is TextBox tb) + { + tb.BackColor = Color.FromArgb(15, 15, 15); + } + else if (ctrl is CheckedListBox clb) + { + clb.BackColor = Color.FromArgb(15, 15, 15); + } + else if (ctrl is Button btn) + { + btn.BackColor = Color.FromArgb(50, 50, 50); + btn.FlatStyle = FlatStyle.Flat; + } + else if (ctrl is Panel pnl) + { + pnl.ForeColor = Color.White; + foreach (Control child in pnl.Controls) + { + child.ForeColor = Color.White; + } + } + } + } + + private void ButtonBrowseFolder_Click(object sender, EventArgs e) + { + // Use OpenFileDialog in folder-picker mode via CommonDialog workaround + // FolderBrowserDialog in .NET 8 supports UseDescriptionForTitle for a modern look + FolderBrowserDialog folderDialog = new FolderBrowserDialog(); + folderDialog.Description = "Select a folder to scan for files"; + folderDialog.UseDescriptionForTitle = true; + folderDialog.ShowNewFolderButton = false; + + if (!string.IsNullOrEmpty(textBoxFolderPath.Text) && Directory.Exists(textBoxFolderPath.Text)) + { + folderDialog.InitialDirectory = textBoxFolderPath.Text; + } + + if (folderDialog.ShowDialog() == DialogResult.OK) + { + textBoxFolderPath.Text = folderDialog.SelectedPath; + folderDialog.Dispose(); + } + } + + private void ButtonScan_Click(object sender, EventArgs e) + { + string folderPath = textBoxFolderPath.Text; + if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath)) + { + labelStatus.Text = "Please select a valid folder path."; + return; + } + + checkedListBoxFiles.Items.Clear(); + + try + { + SearchOption searchOption = checkBoxSubfolders.Checked + ? SearchOption.AllDirectories + : SearchOption.TopDirectoryOnly; + + // Get all files - filter for common PE and script types + string[] extensions = new[] + { + "*.exe", "*.dll", "*.sys", "*.rll", "*.bin", + "*.ps1", "*.bat", "*.vbs", "*.js", + "*.hxs", "*.mui", "*.lex", "*.mof", + "*.msi", "*.msp", "*.ocx", "*.drv", "*.scr", "*.cpl" + }; + + var files = new List(); + foreach (string ext in extensions) + { + try + { + files.AddRange(Directory.GetFiles(folderPath, ext, searchOption)); + } + catch (UnauthorizedAccessException) + { + // Skip folders we can't access + } + } + + // Also allow all files option - if no PE files found, get all files + if (files.Count == 0) + { + try + { + files.AddRange(Directory.GetFiles(folderPath, "*.*", searchOption)); + } + catch (UnauthorizedAccessException) + { + } + } + + files = files.Distinct().OrderBy(f => f).ToList(); + + foreach (string file in files) + { + checkedListBoxFiles.Items.Add(file, true); // Checked by default + } + + labelStatus.Text = $"Found {files.Count} file(s). Select the files to create hash rules for."; + } + catch (Exception ex) + { + labelStatus.Text = $"Error scanning folder: {ex.Message}"; + Logger.Log.AddErrorMsg($"FolderHashScanForm scan error: {ex}"); + } + } + + private void CheckBoxHashAll_CheckedChanged(object sender, EventArgs e) + { + bool isChecked = checkBoxHashAll.Checked; + checkBoxHashSha1.Checked = isChecked; + checkBoxHashSha256.Checked = isChecked; + checkBoxHashPageSha1.Checked = isChecked; + checkBoxHashPageSha256.Checked = isChecked; + } + + private void ButtonOK_Click(object sender, EventArgs e) + { + SelectedFiles.Clear(); + for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) + { + if (checkedListBoxFiles.GetItemChecked(i)) + { + SelectedFiles.Add(checkedListBoxFiles.Items[i].ToString()); + } + } + + // Build the set of hash types to keep + SelectedHashTypes.Clear(); + if (checkBoxHashSha1.Checked) + SelectedHashTypes.Add("Hash Sha1"); + if (checkBoxHashSha256.Checked) + SelectedHashTypes.Add("Hash Sha256"); + if (checkBoxHashPageSha1.Checked) + SelectedHashTypes.Add("Hash Page Sha1"); + if (checkBoxHashPageSha256.Checked) + SelectedHashTypes.Add("Hash Page Sha256"); + // Also handle Authenticode SIP variants (for non-PE files like .js) + if (checkBoxHashSha256.Checked) + SelectedHashTypes.Add("Hash Authenticode SIP Sha256"); + + SourceFolderPath = textBoxFolderPath.Text; + IncludeSubfolders = checkBoxSubfolders.Checked; + AllFilesSelected = (SelectedFiles.Count == checkedListBoxFiles.Items.Count); + + if (SelectedFiles.Count == 0) + { + labelStatus.Text = "Please select at least one file."; + return; + } + + if (SelectedHashTypes.Count == 0) + { + labelStatus.Text = "Please select at least one hash type."; + return; + } + + this.DialogResult = DialogResult.OK; + this.Close(); + } + } +} diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs index 579b9b3f..0079e0dd 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs @@ -36,6 +36,21 @@ public partial class CustomRuleConditionsPanel : Form private string PrevComText = String.Empty; private bool IgnoreInput = false; + // Hash mode UI controls + private Panel panelHashMode; + private RadioButton radioButton_HashSingleFile; + private RadioButton radioButton_HashMultipleFiles; + private Button button_HashFolderScan; + + private enum HashMode + { + SingleFile, + MultipleFiles, + FolderScan + } + + private HashMode hashMode = HashMode.SingleFile; + private enum UIState { RuleConditions = 0, @@ -60,6 +75,117 @@ public CustomRuleConditionsPanel(SigningRules_Control pControl) this.exceptionsControl = null; this.DefaultValues = new string[5]; this.FoundPackages = new List(); + + // Create hash mode panel programmatically + CreateHashModePanel(); + } + + /// + /// Creates the hash mode selection panel (Single File / Multiple Files / Folder Scan) + /// + private void CreateHashModePanel() + { + panelHashMode = new Panel(); + // Position below the checkBox_CustomPath row, left-aligned with the reference file textbox + panelHashMode.Location = new Point( + this.checkBox_CustomPath.Location.X, + this.checkBox_CustomPath.Location.Y + this.checkBox_CustomPath.Height + 4); + panelHashMode.Size = new Size(560, 30); + panelHashMode.Visible = false; + + Label labelHashMode = new Label + { + Text = "Mode:", + Location = new Point(0, 5), + AutoSize = true, + Font = new Font("Tahoma", 9F, FontStyle.Bold) + }; + panelHashMode.Controls.Add(labelHashMode); + + radioButton_HashSingleFile = new RadioButton + { + Text = "Single File", + Location = new Point(55, 3), + AutoSize = true, + Font = new Font("Tahoma", 9F), + Checked = true + }; + radioButton_HashSingleFile.CheckedChanged += HashModeButton_CheckedChanged; + + radioButton_HashMultipleFiles = new RadioButton + { + Text = "Multiple Files", + Location = new Point(170, 3), + AutoSize = true, + Font = new Font("Tahoma", 9F) + }; + radioButton_HashMultipleFiles.CheckedChanged += HashModeButton_CheckedChanged; + + button_HashFolderScan = new Button + { + Text = "Folder Scan...", + Location = new Point(310, 1), + Size = new Size(110, 26), + Font = new Font("Tahoma", 9F, FontStyle.Bold), + FlatStyle = FlatStyle.Standard + }; + button_HashFolderScan.Click += Button_HashFolderScan_Click; + + panelHashMode.Controls.Add(radioButton_HashSingleFile); + panelHashMode.Controls.Add(radioButton_HashMultipleFiles); + panelHashMode.Controls.Add(button_HashFolderScan); + + // Add to the same parent container as panel_FileFolder + this.panel_FileFolder.Parent.Controls.Add(panelHashMode); + panelHashMode.BringToFront(); + + // Apply styling based on mode + if (Properties.Settings.Default.useDarkMode) + { + panelHashMode.BackColor = Color.FromArgb(15, 15, 15); + panelHashMode.ForeColor = Color.White; + foreach (Control ctrl in panelHashMode.Controls) + { + ctrl.ForeColor = Color.White; + ctrl.BackColor = Color.FromArgb(15, 15, 15); + } + button_HashFolderScan.FlatStyle = FlatStyle.Flat; + button_HashFolderScan.BackColor = Color.FromArgb(50, 50, 50); + } + else + { + panelHashMode.BackColor = Color.White; + panelHashMode.ForeColor = Color.Black; + foreach (Control ctrl in panelHashMode.Controls) + { + ctrl.ForeColor = Color.Black; + ctrl.BackColor = Color.White; + } + } + } + + /// + /// Handles hash mode radio button changes + /// + private void HashModeButton_CheckedChanged(object sender, EventArgs e) + { + if (radioButton_HashSingleFile.Checked) + { + hashMode = HashMode.SingleFile; + } + else if (radioButton_HashMultipleFiles.Checked) + { + hashMode = HashMode.MultipleFiles; + } + } + + /// + /// Folder Scan button click - opens FolderHashScanForm directly + /// + private void Button_HashFolderScan_Click(object sender, EventArgs e) + { + hashMode = HashMode.FolderScan; + HandleHashMultiFileOrFolderBrowse(); } /// @@ -785,9 +911,16 @@ private void RuleType_ComboboxChanged(object sender, EventArgs e) this.PolicyCustomRule.SetRuleType(PolicyCustomRules.RuleType.Hash); this.PolicyCustomRule.SetRuleLevel(PolicyCustomRules.RuleLevel.Hash); label_Info.Text = "Creates a rule for a file that is not signed. \r\n" + - "Select the file for which you wish to create a hash rule."; + "Select single file, multiple files, or scan a folder."; this.checkBox_CustomPath.Visible = true; this.checkBox_CustomPath.Text = "Use Custom Hash Values"; + this.panelHashMode.Location = new Point( + this.checkBox_CustomPath.Location.X, + this.checkBox_CustomPath.Location.Y + this.checkBox_CustomPath.Height + 4); + this.panelHashMode.Visible = true; + this.panelHashMode.BringToFront(); + this.radioButton_HashSingleFile.Checked = true; + this.hashMode = HashMode.SingleFile; break; case "COM Object": @@ -882,6 +1015,7 @@ private void ClearCustomRulesPanel(bool clearComboBox = false) //File Path: panel_FileFolder.Visible = false; + panelHashMode.Visible = false; textBox_ReferenceFile.Clear(); // Reset the rule type combobox @@ -916,6 +1050,15 @@ private void Button_Browse_Click(object sender, EventArgs e) return; } + // Handle Hash rule multi-file and folder scan modes + if (this.PolicyCustomRule.Type == PolicyCustomRules.RuleType.Hash + && this.hashMode != HashMode.SingleFile + && !this.PolicyCustomRule.UsingCustomValues) + { + HandleHashMultiFileOrFolderBrowse(); + return; + } + if (this.PolicyCustomRule.Type != PolicyCustomRules.RuleType.FolderPath && this.PolicyCustomRule.Type != PolicyCustomRules.RuleType.FolderScan) { @@ -963,6 +1106,180 @@ private void Button_Browse_Click(object sender, EventArgs e) } } + /// + /// Handles the browse action for Hash rules in Multiple Files or Folder Scan mode. + /// Creates a single FolderScan rule with Hash level for efficient batch processing + /// instead of one PS process per file. + /// + private void HandleHashMultiFileOrFolderBrowse() + { + string scanFolderPath = null; + HashSet hashTypesFilter = null; + string sourceFolderPath = null; + List omitPaths = new List(); + + if (this.hashMode == HashMode.MultipleFiles) + { + // Open multi-select file dialog + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Title = "Select files to create hash rules for"; + openFileDialog.CheckPathExists = true; + openFileDialog.Filter = "Portable Executable Files (*.exe; *.dll; *.rll; *.bin)|*.EXE;*.DLL;*.RLL;*.BIN|" + + "Script Files (*.ps1, *.bat, *.vbs, *.js)|*.PS1;*.BAT;*.VBS;*.JS|" + + "System Files (*.sys, *.hxs, *.mui, *.lex, *.mof)|*.SYS;*.HXS;*.MUI;*.LEX;*.MOF|" + + "All Binary and Script Files (*.exe, ...) |*.EXE;*.DLL;*.RLL;*.BIN;*.PS1;*.BAT;*.VBS;*.JS;*.SYS;*.HXS;*.MUI;*.LEX;*.MOF|" + + "All files (*.*)|*.*"; + openFileDialog.FilterIndex = 4; + openFileDialog.RestoreDirectory = true; + openFileDialog.Multiselect = true; + + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + var selectedFiles = openFileDialog.FileNames; + openFileDialog.Dispose(); + + if (selectedFiles.Length == 0) + { + return; + } + + // Copy selected files to a temp folder for batch scanning + scanFolderPath = Path.Combine(Helper.GetTempFolderPathRoot(), "HashScan_" + DateTime.Now.ToString("HHmmss")); + Directory.CreateDirectory(scanFolderPath); + + foreach (string file in selectedFiles) + { + try + { + string destFile = Path.Combine(scanFolderPath, Path.GetFileName(file)); + // Handle duplicate filenames by appending a guid + if (File.Exists(destFile)) + { + string nameNoExt = Path.GetFileNameWithoutExtension(file); + string ext = Path.GetExtension(file); + destFile = Path.Combine(scanFolderPath, nameNoExt + "_" + Guid.NewGuid().ToString("N").Substring(0, 6) + ext); + } + File.Copy(file, destFile); + } + catch (Exception ex) + { + Logger.Log.AddErrorMsg($"Failed to copy file {file} for hash scan: {ex.Message}"); + } + } + } + else + { + return; + } + } + else if (this.hashMode == HashMode.FolderScan) + { + // Open the folder hash scan form + HashSet hashTypesToKeep = null; + using (var folderScanForm = new FolderHashScanForm()) + { + if (folderScanForm.ShowDialog() == DialogResult.OK) + { + var selectedFiles = folderScanForm.SelectedFiles; + hashTypesToKeep = folderScanForm.SelectedHashTypes; + if (selectedFiles.Count == 0) + { + label_Error.Visible = true; + label_Error.Text = "No files selected."; + return; + } + + // Copy selected files to temp preserving subfolder structure + scanFolderPath = Path.Combine(Helper.GetTempFolderPathRoot(), "HashScan_" + DateTime.Now.ToString("HHmmss")); + Directory.CreateDirectory(scanFolderPath); + + sourceFolderPath = folderScanForm.SourceFolderPath; + + foreach (string file in selectedFiles) + { + try + { + // Preserve relative path from source folder + string relativePath = string.IsNullOrEmpty(sourceFolderPath) || !file.StartsWith(sourceFolderPath, StringComparison.OrdinalIgnoreCase) + ? Path.GetFileName(file) + : file.Substring(sourceFolderPath.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + string destFile = Path.Combine(scanFolderPath, relativePath); + string destDir = Path.GetDirectoryName(destFile); + if (!Directory.Exists(destDir)) + Directory.CreateDirectory(destDir); + + if (File.Exists(destFile)) + { + string nameNoExt = Path.GetFileNameWithoutExtension(destFile); + string ext = Path.GetExtension(destFile); + destFile = Path.Combine(destDir, nameNoExt + "_" + Guid.NewGuid().ToString("N").Substring(0, 6) + ext); + } + File.Copy(file, destFile); + } + catch (Exception ex) + { + Logger.Log.AddErrorMsg($"Failed to copy file {file} for hash scan: {ex.Message}"); + } + } + } + else + { + return; + } + } + + // Store hash types for filtering after scan + if (hashTypesToKeep != null) + { + hashTypesFilter = hashTypesToKeep; + } + } + + if (scanFolderPath == null || !Directory.Exists(scanFolderPath)) + { + label_Error.Visible = true; + label_Error.Text = "Failed to prepare files for scanning."; + return; + } + + // Create a single FolderScan rule with Hash level - this uses New-CIPolicy -ScanPath + // which processes ALL files in one PowerShell call (much faster than one call per file) + PolicyCustomRules rule = new PolicyCustomRules(); + rule.SetRuleType(PolicyCustomRules.RuleType.FolderScan); + rule.SetRuleLevel(PolicyCustomRules.RuleLevel.Hash); + rule.Permission = this.PolicyCustomRule.Permission; + rule.SigningScenarioCheckStates = this.PolicyCustomRule.SigningScenarioCheckStates; + rule.ReferenceFile = scanFolderPath; + rule.Scan.Levels.Add("Hash"); + rule.HashTypesToKeep = hashTypesFilter; + rule.SourceFolderPath = sourceFolderPath; + + int fileCount = Directory.GetFiles(scanFolderPath, "*", SearchOption.TopDirectoryOnly).Length; + string displayPath = sourceFolderPath ?? scanFolderPath; + string[] displayString = new string[5] + { + rule.Permission.ToString(), + "Hash", + $"Hash Folder Scan ({fileCount} files): " + displayPath, + String.Empty, + String.Empty + }; + + this.SigningControl.AddRuleToTableWithoutClosing(displayString, rule, false); + + Logger.Log.AddInfoMsg($"Added folder scan hash rule for {fileCount} files at {scanFolderPath}"); + + // Reset UI + this.PolicyCustomRule = new PolicyCustomRules(); + ClearCustomRulesPanel(true); + this._MainWindow.CustomRuleinProgress = false; + + // Close the panel + this.RuleInEdit = false; + this.SigningControl.CloseCustomRulesPanel(); + } + /// /// Retrieves the file attribute and signer info /// diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 1c81bcd1..68781537 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; +using System.Linq; using System.Windows.Forms; using System.IO; using WDAC_Wizard.src; @@ -851,7 +852,13 @@ private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEve { string process = ""; int progressPercent = e.ProgressPercentage; - if (progressPercent <= 10) + + // Use custom status message if provided via UserState + if (e.UserState is string customMsg && !string.IsNullOrEmpty(customMsg)) + { + process = customMsg; + } + else if (progressPercent <= 10) process = "Building policy rules ..."; else if (progressPercent <= 70) process = "Configuring policy signer and file rules ..."; @@ -1249,23 +1256,28 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) int nCustomRules = this.Policy.CustomRules.Count; int progressVal = 0; + // Count how many rules will actually be processed (Publisher, Hash, FolderScan, Certificate) + int rulesToProcess = this.Policy.CustomRules.Count(r => + !r.UsingCustomValues && + (r.Type == PolicyCustomRules.RuleType.Publisher + || r.Type == PolicyCustomRules.RuleType.Hash + || r.Type == PolicyCustomRules.RuleType.FolderScan + || r.Type == PolicyCustomRules.RuleType.Certificate)); + int rulesProcessed = 0; + // Iterate through all of the custom rules and update the progress bar for (int i = 0; i < nCustomRules; i++) { - progressVal = 25 + i * 60 / nCustomRules; - worker.ReportProgress(progressVal); //Assumes the operations involved with this step take about 70% -- probably should be a little higher - var customRule = this.Policy.CustomRules[i]; // Skip; already handled ALL custom value rules - if(customRule.UsingCustomValues) + if (customRule.UsingCustomValues) { continue; } - // Skip the following rules that are handled by custom rules method - - // File Attributes, PFN rules, file/folder path rules - if (!(customRule.Type == PolicyCustomRules.RuleType.Publisher + // Skip the following rules that are handled by custom rules method + if (!(customRule.Type == PolicyCustomRules.RuleType.Publisher || customRule.Type == PolicyCustomRules.RuleType.Hash || customRule.Type == PolicyCustomRules.RuleType.FolderScan || customRule.Type == PolicyCustomRules.RuleType.Certificate)) @@ -1273,6 +1285,11 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) continue; } + rulesProcessed++; + progressVal = 25 + rulesProcessed * 60 / Math.Max(rulesToProcess, 1); + string statusMsg = $"Processing rule {rulesProcessed} of {rulesToProcess} ..."; + worker.ReportProgress(progressVal, statusMsg); + string tmpPolicyPath = Helper.GetUniquePolicyPath(this.TempFolderPath); // Create a single policy per rule using the Powershell cmdlets with Level=PCACertificate or Publisher @@ -1303,6 +1320,9 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) // Folder Scan -- Invoke the New-CiPolicy PS cmd to generate a CI policy if(customRule.Type == PolicyCustomRules.RuleType.FolderScan) { + string scanPath = customRule.ReferenceFile ?? "folder"; + worker.ReportProgress(30, $"Scanning folder: {scanPath} ..."); + SiPolicy signerSiPolicy; if (this.Policy._PolicyType == WDAC_Policy.PolicyType.BasePolicy) { @@ -1312,10 +1332,65 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) { signerSiPolicy = PSCmdlets.CreateScannedPolicyFromPS(customRule, tmpPolicyPath, this.Policy.BaseToSupplementPath); } - + + worker.ReportProgress(70, "Scan complete. Applying hash type filters ..."); + // Successful Scan completed if (signerSiPolicy != null) { + // Fix FriendlyName: replace temp scan path with original source folder path + if (!string.IsNullOrEmpty(customRule.SourceFolderPath) + && signerSiPolicy.FileRules != null) + { + string tempPath = customRule.ReferenceFile; + foreach (var item in signerSiPolicy.FileRules) + { + if (item is Allow allow && !string.IsNullOrEmpty(allow.FriendlyName) + && allow.FriendlyName.Contains(tempPath, StringComparison.OrdinalIgnoreCase)) + { + allow.FriendlyName = allow.FriendlyName.Replace(tempPath, customRule.SourceFolderPath, StringComparison.OrdinalIgnoreCase); + } + else if (item is Deny deny && !string.IsNullOrEmpty(deny.FriendlyName) + && deny.FriendlyName.Contains(tempPath, StringComparison.OrdinalIgnoreCase)) + { + deny.FriendlyName = deny.FriendlyName.Replace(tempPath, customRule.SourceFolderPath, StringComparison.OrdinalIgnoreCase); + } + } + } + + // Filter hash types if the user selected specific types to keep + if (customRule.HashTypesToKeep != null && customRule.HashTypesToKeep.Count > 0 + && signerSiPolicy.FileRules != null) + { + int beforeCount = signerSiPolicy.FileRules.Length; + signerSiPolicy.FileRules = signerSiPolicy.FileRules.Where(item => + { + string friendlyName = null; + if (item is Allow allow) + friendlyName = allow.FriendlyName; + else if (item is Deny deny) + friendlyName = deny.FriendlyName; + + // If it's not a hash rule (no FriendlyName with hash pattern), keep it + if (string.IsNullOrEmpty(friendlyName)) + return true; + + // Check if FriendlyName ends with one of the selected hash type patterns + foreach (string hashType in customRule.HashTypesToKeep) + { + if (friendlyName.EndsWith(hashType, StringComparison.OrdinalIgnoreCase)) + return true; + } + + // If it doesn't match any selected hash type pattern, remove it + return false; + }).ToArray(); + + int afterCount = signerSiPolicy.FileRules.Length; + worker.ReportProgress(75, $"Filtered: kept {afterCount} of {beforeCount} hash rules."); + } + + worker.ReportProgress(80, "Merging scanned policy rules ..."); siPolicy = PolicyHelper.MergePolicies(signerSiPolicy, siPolicy); } } @@ -1345,9 +1420,17 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) /// public SiPolicy ProcessCustomValueRules(BackgroundWorker worker, SiPolicy siPolicyCustomValueRules) { + int totalRules = this.Policy.CustomRules.Count; + int processed = 0; + // Iterate through all of the custom rules and PFN rules and update the progress bar foreach (var customRule in this.Policy.CustomRules) { + processed++; + int progressVal = processed * 25 / Math.Max(totalRules, 1); + string statusMsg = $"Processing custom value rule {processed} of {totalRules} ..."; + worker.ReportProgress(progressVal, statusMsg); + if(customRule.UsingCustomValues) { siPolicyCustomValueRules = HandleCustomValues(customRule, siPolicyCustomValueRules); diff --git a/WDAC-Policy-Wizard/app/src/Policy.cs b/WDAC-Policy-Wizard/app/src/Policy.cs index 19476ebb..00b947af 100644 --- a/WDAC-Policy-Wizard/app/src/Policy.cs +++ b/WDAC-Policy-Wizard/app/src/Policy.cs @@ -640,6 +640,12 @@ public enum RulePermission { Allow, Deny }; // Folder Scan public FolderScan Scan { get; set; } + // Hash types to keep when filtering generated policy XML (e.g., "Hash Sha1", "Hash Sha256", "Hash Page Sha1", "Hash Page Sha256") + public HashSet HashTypesToKeep { get; set; } + + // Original source folder path (used to fix FriendlyName in generated policy when scanning from temp) + public string SourceFolderPath { get; set; } + // Constructors public PolicyCustomRules() { diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 41fb16fd..1d48fed4 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -1196,6 +1196,36 @@ public void AddRuleToTable(string [] displayObjectArray, PolicyCustomRules custo this.customRuleConditionsPanel = null; } + /// + /// Adds a new rule to the DataGrid Table without closing the custom rules panel. + /// Used when adding multiple rules at once (e.g., multi-file hash scan). + /// + /// + /// + /// + public void AddRuleToTableWithoutClosing(string[] displayObjectArray, PolicyCustomRules customRule, bool warnUser) + { + // Attach the int row number we added it to + customRule.RowNumber = this.rulesDataGrid.RowCount - 1; + string action = displayObjectArray[0]; + string level = displayObjectArray[1]; + string name = warnUser ? "*Hash Fallback Possible* " + displayObjectArray[2] : displayObjectArray[2]; + string files = displayObjectArray[3]; + string exceptions = displayObjectArray[4]; + + // Add to the DisplayObject + this.displayObjects.Add(new DisplayObject(action, level, name, files, exceptions)); + this.rulesDataGrid.RowCount += 1; + + // Add custom list to RulesList + this.Policy.CustomRules.Add(customRule); + + // Scroll to bottom to see new rule added to list + this.rulesDataGrid.FirstDisplayedScrollingRowIndex = this.rulesDataGrid.RowCount - 1; + + BubbleUp(); + } + /// /// Nullifies the custom rule conditions panel on form closing /// From 354c4f435be4ec40524ddfe9c15180670bb67c74 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Tue, 5 May 2026 12:45:35 -0400 Subject: [PATCH 2/8] Revert "New Files" This reverts commit 162dd8803751610b1c8cedab6bf3abca2dc2e8d8. --- .../app/app/src/FolderHashScanForm.cs | 465 ------------------ .../app/src/CustomRuleConditionsPanel.cs | 319 +----------- WDAC-Policy-Wizard/app/src/MainForm.cs | 101 +--- WDAC-Policy-Wizard/app/src/Policy.cs | 6 - .../app/src/SigningRules_Control.cs | 30 -- 5 files changed, 10 insertions(+), 911 deletions(-) delete mode 100644 WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs diff --git a/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs b/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs deleted file mode 100644 index 195911ad..00000000 --- a/WDAC-Policy-Wizard/app/app/src/FolderHashScanForm.cs +++ /dev/null @@ -1,465 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Windows.Forms; - -namespace WDAC_Wizard -{ - /// - /// Form that allows users to scan a folder for files and select which ones to create hash rules for. - /// - public class FolderHashScanForm : Form - { - private Label labelFolderPath; - private TextBox textBoxFolderPath; - private Button buttonBrowseFolder; - private CheckBox checkBoxSubfolders; - private Button buttonScan; - private CheckedListBox checkedListBoxFiles; - private Button buttonSelectAll; - private Button buttonDeselectAll; - private Button buttonOK; - private Button buttonCancel; - private Label labelStatus; - private Label labelHashTypes; - private CheckBox checkBoxHashSha1; - private CheckBox checkBoxHashSha256; - private CheckBox checkBoxHashPageSha1; - private CheckBox checkBoxHashPageSha256; - private CheckBox checkBoxHashAll; - private Panel panelHashTypes; - - /// - /// The list of file paths the user selected - /// - public List SelectedFiles { get; private set; } - - /// - /// The original folder path that was scanned - /// - public string SourceFolderPath { get; private set; } - - /// - /// Whether subfolders were included in the scan - /// - public bool IncludeSubfolders { get; private set; } - - /// - /// Whether all scanned files were selected (no unchecking) - /// - public bool AllFilesSelected { get; private set; } - - /// - /// The hash types selected by the user to keep in the generated policy. - /// Values match FriendlyName patterns: "Hash Sha1", "Hash Sha256", "Hash Page Sha1", "Hash Page Sha256" - /// - public HashSet SelectedHashTypes { get; private set; } - - public FolderHashScanForm() - { - SelectedFiles = new List(); - SelectedHashTypes = new HashSet(StringComparer.OrdinalIgnoreCase); - InitializeComponents(); - } - - private void InitializeComponents() - { - this.Text = "Folder Hash Scan"; - this.Size = new Size(700, 600); - this.FormBorderStyle = FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.StartPosition = FormStartPosition.CenterParent; - - int yPos = 15; - - // Folder path label - labelFolderPath = new Label - { - Text = "Folder Path:", - Location = new Point(15, yPos), - Size = new Size(80, 20), - Font = new Font("Tahoma", 9F) - }; - this.Controls.Add(labelFolderPath); - - // Folder path textbox - textBoxFolderPath = new TextBox - { - Location = new Point(100, yPos), - Size = new Size(460, 24), - Font = new Font("Tahoma", 9F), - ReadOnly = true - }; - this.Controls.Add(textBoxFolderPath); - - // Browse button - buttonBrowseFolder = new Button - { - Text = "Browse...", - Location = new Point(570, yPos - 2), - Size = new Size(90, 26), - Font = new Font("Tahoma", 9F) - }; - buttonBrowseFolder.Click += ButtonBrowseFolder_Click; - this.Controls.Add(buttonBrowseFolder); - - yPos += 35; - - // Include subfolders checkbox - checkBoxSubfolders = new CheckBox - { - Text = "Include subfolders", - Location = new Point(100, yPos), - Size = new Size(160, 22), - Font = new Font("Tahoma", 9F), - Checked = false - }; - this.Controls.Add(checkBoxSubfolders); - - // Hash type checkboxes - labelHashTypes = new Label - { - Text = "Hash Types to Keep:", - Location = new Point(280, yPos), - Size = new Size(130, 20), - Font = new Font("Tahoma", 9F) - }; - this.Controls.Add(labelHashTypes); - - panelHashTypes = new Panel - { - Location = new Point(15, yPos + 25), - Size = new Size(650, 28) - }; - - checkBoxHashAll = new CheckBox - { - Text = "All", - Location = new Point(0, 2), - Size = new Size(50, 22), - Font = new Font("Tahoma", 9F), - Checked = true - }; - checkBoxHashAll.CheckedChanged += CheckBoxHashAll_CheckedChanged; - panelHashTypes.Controls.Add(checkBoxHashAll); - - checkBoxHashSha1 = new CheckBox - { - Text = "Hash SHA1", - Location = new Point(55, 2), - Size = new Size(100, 22), - Font = new Font("Tahoma", 9F), - Checked = true - }; - panelHashTypes.Controls.Add(checkBoxHashSha1); - - checkBoxHashSha256 = new CheckBox - { - Text = "Hash SHA256", - Location = new Point(160, 2), - Size = new Size(115, 22), - Font = new Font("Tahoma", 9F), - Checked = true - }; - panelHashTypes.Controls.Add(checkBoxHashSha256); - - checkBoxHashPageSha1 = new CheckBox - { - Text = "Hash Page SHA1", - Location = new Point(280, 2), - Size = new Size(130, 22), - Font = new Font("Tahoma", 9F), - Checked = true - }; - panelHashTypes.Controls.Add(checkBoxHashPageSha1); - - checkBoxHashPageSha256 = new CheckBox - { - Text = "Hash Page SHA256", - Location = new Point(415, 2), - Size = new Size(145, 22), - Font = new Font("Tahoma", 9F), - Checked = true - }; - panelHashTypes.Controls.Add(checkBoxHashPageSha256); - - this.Controls.Add(panelHashTypes); - - yPos += 55; - - // Scan button - on its own row for visibility - buttonScan = new Button - { - Text = "Scan Folder", - Location = new Point(100, yPos - 2), - Size = new Size(120, 28), - Font = new Font("Tahoma", 9F, FontStyle.Bold) - }; - buttonScan.Click += ButtonScan_Click; - this.Controls.Add(buttonScan); - - yPos += 35; - - // Status label - labelStatus = new Label - { - Text = "Select a folder and click Scan to find files.", - Location = new Point(15, yPos), - Size = new Size(650, 20), - Font = new Font("Tahoma", 9F) - }; - this.Controls.Add(labelStatus); - - yPos += 25; - - // Checked list box for files - checkedListBoxFiles = new CheckedListBox - { - Location = new Point(15, yPos), - Size = new Size(645, 320), - Font = new Font("Tahoma", 8.5F), - CheckOnClick = true, - HorizontalScrollbar = true - }; - this.Controls.Add(checkedListBoxFiles); - - yPos += 330; - - // Select All / Deselect All buttons - buttonSelectAll = new Button - { - Text = "Select All", - Location = new Point(15, yPos), - Size = new Size(90, 28), - Font = new Font("Tahoma", 9F) - }; - buttonSelectAll.Click += (s, e) => - { - for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) - checkedListBoxFiles.SetItemChecked(i, true); - }; - this.Controls.Add(buttonSelectAll); - - buttonDeselectAll = new Button - { - Text = "Deselect All", - Location = new Point(115, yPos), - Size = new Size(100, 28), - Font = new Font("Tahoma", 9F) - }; - buttonDeselectAll.Click += (s, e) => - { - for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) - checkedListBoxFiles.SetItemChecked(i, false); - }; - this.Controls.Add(buttonDeselectAll); - - // OK / Cancel buttons - buttonCancel = new Button - { - Text = "Cancel", - Location = new Point(560, yPos), - Size = new Size(100, 28), - Font = new Font("Tahoma", 9F), - DialogResult = DialogResult.Cancel - }; - this.Controls.Add(buttonCancel); - - buttonOK = new Button - { - Text = "Add Selected", - Location = new Point(450, yPos), - Size = new Size(100, 28), - Font = new Font("Tahoma", 9F) - }; - buttonOK.Click += ButtonOK_Click; - this.Controls.Add(buttonOK); - - this.AcceptButton = buttonOK; - this.CancelButton = buttonCancel; - - // Apply dark mode if needed - if (Properties.Settings.Default.useDarkMode) - { - ApplyDarkMode(); - } - } - - private void ApplyDarkMode() - { - this.BackColor = Color.FromArgb(30, 30, 30); - this.ForeColor = Color.White; - - foreach (Control ctrl in this.Controls) - { - ctrl.ForeColor = Color.White; - if (ctrl is TextBox tb) - { - tb.BackColor = Color.FromArgb(15, 15, 15); - } - else if (ctrl is CheckedListBox clb) - { - clb.BackColor = Color.FromArgb(15, 15, 15); - } - else if (ctrl is Button btn) - { - btn.BackColor = Color.FromArgb(50, 50, 50); - btn.FlatStyle = FlatStyle.Flat; - } - else if (ctrl is Panel pnl) - { - pnl.ForeColor = Color.White; - foreach (Control child in pnl.Controls) - { - child.ForeColor = Color.White; - } - } - } - } - - private void ButtonBrowseFolder_Click(object sender, EventArgs e) - { - // Use OpenFileDialog in folder-picker mode via CommonDialog workaround - // FolderBrowserDialog in .NET 8 supports UseDescriptionForTitle for a modern look - FolderBrowserDialog folderDialog = new FolderBrowserDialog(); - folderDialog.Description = "Select a folder to scan for files"; - folderDialog.UseDescriptionForTitle = true; - folderDialog.ShowNewFolderButton = false; - - if (!string.IsNullOrEmpty(textBoxFolderPath.Text) && Directory.Exists(textBoxFolderPath.Text)) - { - folderDialog.InitialDirectory = textBoxFolderPath.Text; - } - - if (folderDialog.ShowDialog() == DialogResult.OK) - { - textBoxFolderPath.Text = folderDialog.SelectedPath; - folderDialog.Dispose(); - } - } - - private void ButtonScan_Click(object sender, EventArgs e) - { - string folderPath = textBoxFolderPath.Text; - if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath)) - { - labelStatus.Text = "Please select a valid folder path."; - return; - } - - checkedListBoxFiles.Items.Clear(); - - try - { - SearchOption searchOption = checkBoxSubfolders.Checked - ? SearchOption.AllDirectories - : SearchOption.TopDirectoryOnly; - - // Get all files - filter for common PE and script types - string[] extensions = new[] - { - "*.exe", "*.dll", "*.sys", "*.rll", "*.bin", - "*.ps1", "*.bat", "*.vbs", "*.js", - "*.hxs", "*.mui", "*.lex", "*.mof", - "*.msi", "*.msp", "*.ocx", "*.drv", "*.scr", "*.cpl" - }; - - var files = new List(); - foreach (string ext in extensions) - { - try - { - files.AddRange(Directory.GetFiles(folderPath, ext, searchOption)); - } - catch (UnauthorizedAccessException) - { - // Skip folders we can't access - } - } - - // Also allow all files option - if no PE files found, get all files - if (files.Count == 0) - { - try - { - files.AddRange(Directory.GetFiles(folderPath, "*.*", searchOption)); - } - catch (UnauthorizedAccessException) - { - } - } - - files = files.Distinct().OrderBy(f => f).ToList(); - - foreach (string file in files) - { - checkedListBoxFiles.Items.Add(file, true); // Checked by default - } - - labelStatus.Text = $"Found {files.Count} file(s). Select the files to create hash rules for."; - } - catch (Exception ex) - { - labelStatus.Text = $"Error scanning folder: {ex.Message}"; - Logger.Log.AddErrorMsg($"FolderHashScanForm scan error: {ex}"); - } - } - - private void CheckBoxHashAll_CheckedChanged(object sender, EventArgs e) - { - bool isChecked = checkBoxHashAll.Checked; - checkBoxHashSha1.Checked = isChecked; - checkBoxHashSha256.Checked = isChecked; - checkBoxHashPageSha1.Checked = isChecked; - checkBoxHashPageSha256.Checked = isChecked; - } - - private void ButtonOK_Click(object sender, EventArgs e) - { - SelectedFiles.Clear(); - for (int i = 0; i < checkedListBoxFiles.Items.Count; i++) - { - if (checkedListBoxFiles.GetItemChecked(i)) - { - SelectedFiles.Add(checkedListBoxFiles.Items[i].ToString()); - } - } - - // Build the set of hash types to keep - SelectedHashTypes.Clear(); - if (checkBoxHashSha1.Checked) - SelectedHashTypes.Add("Hash Sha1"); - if (checkBoxHashSha256.Checked) - SelectedHashTypes.Add("Hash Sha256"); - if (checkBoxHashPageSha1.Checked) - SelectedHashTypes.Add("Hash Page Sha1"); - if (checkBoxHashPageSha256.Checked) - SelectedHashTypes.Add("Hash Page Sha256"); - // Also handle Authenticode SIP variants (for non-PE files like .js) - if (checkBoxHashSha256.Checked) - SelectedHashTypes.Add("Hash Authenticode SIP Sha256"); - - SourceFolderPath = textBoxFolderPath.Text; - IncludeSubfolders = checkBoxSubfolders.Checked; - AllFilesSelected = (SelectedFiles.Count == checkedListBoxFiles.Items.Count); - - if (SelectedFiles.Count == 0) - { - labelStatus.Text = "Please select at least one file."; - return; - } - - if (SelectedHashTypes.Count == 0) - { - labelStatus.Text = "Please select at least one hash type."; - return; - } - - this.DialogResult = DialogResult.OK; - this.Close(); - } - } -} diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs index 0079e0dd..579b9b3f 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs @@ -36,21 +36,6 @@ public partial class CustomRuleConditionsPanel : Form private string PrevComText = String.Empty; private bool IgnoreInput = false; - // Hash mode UI controls - private Panel panelHashMode; - private RadioButton radioButton_HashSingleFile; - private RadioButton radioButton_HashMultipleFiles; - private Button button_HashFolderScan; - - private enum HashMode - { - SingleFile, - MultipleFiles, - FolderScan - } - - private HashMode hashMode = HashMode.SingleFile; - private enum UIState { RuleConditions = 0, @@ -75,117 +60,6 @@ public CustomRuleConditionsPanel(SigningRules_Control pControl) this.exceptionsControl = null; this.DefaultValues = new string[5]; this.FoundPackages = new List(); - - // Create hash mode panel programmatically - CreateHashModePanel(); - } - - /// - /// Creates the hash mode selection panel (Single File / Multiple Files / Folder Scan) - /// - private void CreateHashModePanel() - { - panelHashMode = new Panel(); - // Position below the checkBox_CustomPath row, left-aligned with the reference file textbox - panelHashMode.Location = new Point( - this.checkBox_CustomPath.Location.X, - this.checkBox_CustomPath.Location.Y + this.checkBox_CustomPath.Height + 4); - panelHashMode.Size = new Size(560, 30); - panelHashMode.Visible = false; - - Label labelHashMode = new Label - { - Text = "Mode:", - Location = new Point(0, 5), - AutoSize = true, - Font = new Font("Tahoma", 9F, FontStyle.Bold) - }; - panelHashMode.Controls.Add(labelHashMode); - - radioButton_HashSingleFile = new RadioButton - { - Text = "Single File", - Location = new Point(55, 3), - AutoSize = true, - Font = new Font("Tahoma", 9F), - Checked = true - }; - radioButton_HashSingleFile.CheckedChanged += HashModeButton_CheckedChanged; - - radioButton_HashMultipleFiles = new RadioButton - { - Text = "Multiple Files", - Location = new Point(170, 3), - AutoSize = true, - Font = new Font("Tahoma", 9F) - }; - radioButton_HashMultipleFiles.CheckedChanged += HashModeButton_CheckedChanged; - - button_HashFolderScan = new Button - { - Text = "Folder Scan...", - Location = new Point(310, 1), - Size = new Size(110, 26), - Font = new Font("Tahoma", 9F, FontStyle.Bold), - FlatStyle = FlatStyle.Standard - }; - button_HashFolderScan.Click += Button_HashFolderScan_Click; - - panelHashMode.Controls.Add(radioButton_HashSingleFile); - panelHashMode.Controls.Add(radioButton_HashMultipleFiles); - panelHashMode.Controls.Add(button_HashFolderScan); - - // Add to the same parent container as panel_FileFolder - this.panel_FileFolder.Parent.Controls.Add(panelHashMode); - panelHashMode.BringToFront(); - - // Apply styling based on mode - if (Properties.Settings.Default.useDarkMode) - { - panelHashMode.BackColor = Color.FromArgb(15, 15, 15); - panelHashMode.ForeColor = Color.White; - foreach (Control ctrl in panelHashMode.Controls) - { - ctrl.ForeColor = Color.White; - ctrl.BackColor = Color.FromArgb(15, 15, 15); - } - button_HashFolderScan.FlatStyle = FlatStyle.Flat; - button_HashFolderScan.BackColor = Color.FromArgb(50, 50, 50); - } - else - { - panelHashMode.BackColor = Color.White; - panelHashMode.ForeColor = Color.Black; - foreach (Control ctrl in panelHashMode.Controls) - { - ctrl.ForeColor = Color.Black; - ctrl.BackColor = Color.White; - } - } - } - - /// - /// Handles hash mode radio button changes - /// - private void HashModeButton_CheckedChanged(object sender, EventArgs e) - { - if (radioButton_HashSingleFile.Checked) - { - hashMode = HashMode.SingleFile; - } - else if (radioButton_HashMultipleFiles.Checked) - { - hashMode = HashMode.MultipleFiles; - } - } - - /// - /// Folder Scan button click - opens FolderHashScanForm directly - /// - private void Button_HashFolderScan_Click(object sender, EventArgs e) - { - hashMode = HashMode.FolderScan; - HandleHashMultiFileOrFolderBrowse(); } /// @@ -911,16 +785,9 @@ private void RuleType_ComboboxChanged(object sender, EventArgs e) this.PolicyCustomRule.SetRuleType(PolicyCustomRules.RuleType.Hash); this.PolicyCustomRule.SetRuleLevel(PolicyCustomRules.RuleLevel.Hash); label_Info.Text = "Creates a rule for a file that is not signed. \r\n" + - "Select single file, multiple files, or scan a folder."; + "Select the file for which you wish to create a hash rule."; this.checkBox_CustomPath.Visible = true; this.checkBox_CustomPath.Text = "Use Custom Hash Values"; - this.panelHashMode.Location = new Point( - this.checkBox_CustomPath.Location.X, - this.checkBox_CustomPath.Location.Y + this.checkBox_CustomPath.Height + 4); - this.panelHashMode.Visible = true; - this.panelHashMode.BringToFront(); - this.radioButton_HashSingleFile.Checked = true; - this.hashMode = HashMode.SingleFile; break; case "COM Object": @@ -1015,7 +882,6 @@ private void ClearCustomRulesPanel(bool clearComboBox = false) //File Path: panel_FileFolder.Visible = false; - panelHashMode.Visible = false; textBox_ReferenceFile.Clear(); // Reset the rule type combobox @@ -1050,15 +916,6 @@ private void Button_Browse_Click(object sender, EventArgs e) return; } - // Handle Hash rule multi-file and folder scan modes - if (this.PolicyCustomRule.Type == PolicyCustomRules.RuleType.Hash - && this.hashMode != HashMode.SingleFile - && !this.PolicyCustomRule.UsingCustomValues) - { - HandleHashMultiFileOrFolderBrowse(); - return; - } - if (this.PolicyCustomRule.Type != PolicyCustomRules.RuleType.FolderPath && this.PolicyCustomRule.Type != PolicyCustomRules.RuleType.FolderScan) { @@ -1106,180 +963,6 @@ private void Button_Browse_Click(object sender, EventArgs e) } } - /// - /// Handles the browse action for Hash rules in Multiple Files or Folder Scan mode. - /// Creates a single FolderScan rule with Hash level for efficient batch processing - /// instead of one PS process per file. - /// - private void HandleHashMultiFileOrFolderBrowse() - { - string scanFolderPath = null; - HashSet hashTypesFilter = null; - string sourceFolderPath = null; - List omitPaths = new List(); - - if (this.hashMode == HashMode.MultipleFiles) - { - // Open multi-select file dialog - OpenFileDialog openFileDialog = new OpenFileDialog(); - openFileDialog.Title = "Select files to create hash rules for"; - openFileDialog.CheckPathExists = true; - openFileDialog.Filter = "Portable Executable Files (*.exe; *.dll; *.rll; *.bin)|*.EXE;*.DLL;*.RLL;*.BIN|" + - "Script Files (*.ps1, *.bat, *.vbs, *.js)|*.PS1;*.BAT;*.VBS;*.JS|" + - "System Files (*.sys, *.hxs, *.mui, *.lex, *.mof)|*.SYS;*.HXS;*.MUI;*.LEX;*.MOF|" + - "All Binary and Script Files (*.exe, ...) |*.EXE;*.DLL;*.RLL;*.BIN;*.PS1;*.BAT;*.VBS;*.JS;*.SYS;*.HXS;*.MUI;*.LEX;*.MOF|" + - "All files (*.*)|*.*"; - openFileDialog.FilterIndex = 4; - openFileDialog.RestoreDirectory = true; - openFileDialog.Multiselect = true; - - if (openFileDialog.ShowDialog() == DialogResult.OK) - { - var selectedFiles = openFileDialog.FileNames; - openFileDialog.Dispose(); - - if (selectedFiles.Length == 0) - { - return; - } - - // Copy selected files to a temp folder for batch scanning - scanFolderPath = Path.Combine(Helper.GetTempFolderPathRoot(), "HashScan_" + DateTime.Now.ToString("HHmmss")); - Directory.CreateDirectory(scanFolderPath); - - foreach (string file in selectedFiles) - { - try - { - string destFile = Path.Combine(scanFolderPath, Path.GetFileName(file)); - // Handle duplicate filenames by appending a guid - if (File.Exists(destFile)) - { - string nameNoExt = Path.GetFileNameWithoutExtension(file); - string ext = Path.GetExtension(file); - destFile = Path.Combine(scanFolderPath, nameNoExt + "_" + Guid.NewGuid().ToString("N").Substring(0, 6) + ext); - } - File.Copy(file, destFile); - } - catch (Exception ex) - { - Logger.Log.AddErrorMsg($"Failed to copy file {file} for hash scan: {ex.Message}"); - } - } - } - else - { - return; - } - } - else if (this.hashMode == HashMode.FolderScan) - { - // Open the folder hash scan form - HashSet hashTypesToKeep = null; - using (var folderScanForm = new FolderHashScanForm()) - { - if (folderScanForm.ShowDialog() == DialogResult.OK) - { - var selectedFiles = folderScanForm.SelectedFiles; - hashTypesToKeep = folderScanForm.SelectedHashTypes; - if (selectedFiles.Count == 0) - { - label_Error.Visible = true; - label_Error.Text = "No files selected."; - return; - } - - // Copy selected files to temp preserving subfolder structure - scanFolderPath = Path.Combine(Helper.GetTempFolderPathRoot(), "HashScan_" + DateTime.Now.ToString("HHmmss")); - Directory.CreateDirectory(scanFolderPath); - - sourceFolderPath = folderScanForm.SourceFolderPath; - - foreach (string file in selectedFiles) - { - try - { - // Preserve relative path from source folder - string relativePath = string.IsNullOrEmpty(sourceFolderPath) || !file.StartsWith(sourceFolderPath, StringComparison.OrdinalIgnoreCase) - ? Path.GetFileName(file) - : file.Substring(sourceFolderPath.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - - string destFile = Path.Combine(scanFolderPath, relativePath); - string destDir = Path.GetDirectoryName(destFile); - if (!Directory.Exists(destDir)) - Directory.CreateDirectory(destDir); - - if (File.Exists(destFile)) - { - string nameNoExt = Path.GetFileNameWithoutExtension(destFile); - string ext = Path.GetExtension(destFile); - destFile = Path.Combine(destDir, nameNoExt + "_" + Guid.NewGuid().ToString("N").Substring(0, 6) + ext); - } - File.Copy(file, destFile); - } - catch (Exception ex) - { - Logger.Log.AddErrorMsg($"Failed to copy file {file} for hash scan: {ex.Message}"); - } - } - } - else - { - return; - } - } - - // Store hash types for filtering after scan - if (hashTypesToKeep != null) - { - hashTypesFilter = hashTypesToKeep; - } - } - - if (scanFolderPath == null || !Directory.Exists(scanFolderPath)) - { - label_Error.Visible = true; - label_Error.Text = "Failed to prepare files for scanning."; - return; - } - - // Create a single FolderScan rule with Hash level - this uses New-CIPolicy -ScanPath - // which processes ALL files in one PowerShell call (much faster than one call per file) - PolicyCustomRules rule = new PolicyCustomRules(); - rule.SetRuleType(PolicyCustomRules.RuleType.FolderScan); - rule.SetRuleLevel(PolicyCustomRules.RuleLevel.Hash); - rule.Permission = this.PolicyCustomRule.Permission; - rule.SigningScenarioCheckStates = this.PolicyCustomRule.SigningScenarioCheckStates; - rule.ReferenceFile = scanFolderPath; - rule.Scan.Levels.Add("Hash"); - rule.HashTypesToKeep = hashTypesFilter; - rule.SourceFolderPath = sourceFolderPath; - - int fileCount = Directory.GetFiles(scanFolderPath, "*", SearchOption.TopDirectoryOnly).Length; - string displayPath = sourceFolderPath ?? scanFolderPath; - string[] displayString = new string[5] - { - rule.Permission.ToString(), - "Hash", - $"Hash Folder Scan ({fileCount} files): " + displayPath, - String.Empty, - String.Empty - }; - - this.SigningControl.AddRuleToTableWithoutClosing(displayString, rule, false); - - Logger.Log.AddInfoMsg($"Added folder scan hash rule for {fileCount} files at {scanFolderPath}"); - - // Reset UI - this.PolicyCustomRule = new PolicyCustomRules(); - ClearCustomRulesPanel(true); - this._MainWindow.CustomRuleinProgress = false; - - // Close the panel - this.RuleInEdit = false; - this.SigningControl.CloseCustomRulesPanel(); - } - /// /// Retrieves the file attribute and signer info /// diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 68781537..1c81bcd1 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; -using System.Linq; using System.Windows.Forms; using System.IO; using WDAC_Wizard.src; @@ -852,13 +851,7 @@ private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEve { string process = ""; int progressPercent = e.ProgressPercentage; - - // Use custom status message if provided via UserState - if (e.UserState is string customMsg && !string.IsNullOrEmpty(customMsg)) - { - process = customMsg; - } - else if (progressPercent <= 10) + if (progressPercent <= 10) process = "Building policy rules ..."; else if (progressPercent <= 70) process = "Configuring policy signer and file rules ..."; @@ -1256,28 +1249,23 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) int nCustomRules = this.Policy.CustomRules.Count; int progressVal = 0; - // Count how many rules will actually be processed (Publisher, Hash, FolderScan, Certificate) - int rulesToProcess = this.Policy.CustomRules.Count(r => - !r.UsingCustomValues && - (r.Type == PolicyCustomRules.RuleType.Publisher - || r.Type == PolicyCustomRules.RuleType.Hash - || r.Type == PolicyCustomRules.RuleType.FolderScan - || r.Type == PolicyCustomRules.RuleType.Certificate)); - int rulesProcessed = 0; - // Iterate through all of the custom rules and update the progress bar for (int i = 0; i < nCustomRules; i++) { + progressVal = 25 + i * 60 / nCustomRules; + worker.ReportProgress(progressVal); //Assumes the operations involved with this step take about 70% -- probably should be a little higher + var customRule = this.Policy.CustomRules[i]; // Skip; already handled ALL custom value rules - if (customRule.UsingCustomValues) + if(customRule.UsingCustomValues) { continue; } - // Skip the following rules that are handled by custom rules method - if (!(customRule.Type == PolicyCustomRules.RuleType.Publisher + // Skip the following rules that are handled by custom rules method - + // File Attributes, PFN rules, file/folder path rules + if (!(customRule.Type == PolicyCustomRules.RuleType.Publisher || customRule.Type == PolicyCustomRules.RuleType.Hash || customRule.Type == PolicyCustomRules.RuleType.FolderScan || customRule.Type == PolicyCustomRules.RuleType.Certificate)) @@ -1285,11 +1273,6 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) continue; } - rulesProcessed++; - progressVal = 25 + rulesProcessed * 60 / Math.Max(rulesToProcess, 1); - string statusMsg = $"Processing rule {rulesProcessed} of {rulesToProcess} ..."; - worker.ReportProgress(progressVal, statusMsg); - string tmpPolicyPath = Helper.GetUniquePolicyPath(this.TempFolderPath); // Create a single policy per rule using the Powershell cmdlets with Level=PCACertificate or Publisher @@ -1320,9 +1303,6 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) // Folder Scan -- Invoke the New-CiPolicy PS cmd to generate a CI policy if(customRule.Type == PolicyCustomRules.RuleType.FolderScan) { - string scanPath = customRule.ReferenceFile ?? "folder"; - worker.ReportProgress(30, $"Scanning folder: {scanPath} ..."); - SiPolicy signerSiPolicy; if (this.Policy._PolicyType == WDAC_Policy.PolicyType.BasePolicy) { @@ -1332,65 +1312,10 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) { signerSiPolicy = PSCmdlets.CreateScannedPolicyFromPS(customRule, tmpPolicyPath, this.Policy.BaseToSupplementPath); } - - worker.ReportProgress(70, "Scan complete. Applying hash type filters ..."); - + // Successful Scan completed if (signerSiPolicy != null) { - // Fix FriendlyName: replace temp scan path with original source folder path - if (!string.IsNullOrEmpty(customRule.SourceFolderPath) - && signerSiPolicy.FileRules != null) - { - string tempPath = customRule.ReferenceFile; - foreach (var item in signerSiPolicy.FileRules) - { - if (item is Allow allow && !string.IsNullOrEmpty(allow.FriendlyName) - && allow.FriendlyName.Contains(tempPath, StringComparison.OrdinalIgnoreCase)) - { - allow.FriendlyName = allow.FriendlyName.Replace(tempPath, customRule.SourceFolderPath, StringComparison.OrdinalIgnoreCase); - } - else if (item is Deny deny && !string.IsNullOrEmpty(deny.FriendlyName) - && deny.FriendlyName.Contains(tempPath, StringComparison.OrdinalIgnoreCase)) - { - deny.FriendlyName = deny.FriendlyName.Replace(tempPath, customRule.SourceFolderPath, StringComparison.OrdinalIgnoreCase); - } - } - } - - // Filter hash types if the user selected specific types to keep - if (customRule.HashTypesToKeep != null && customRule.HashTypesToKeep.Count > 0 - && signerSiPolicy.FileRules != null) - { - int beforeCount = signerSiPolicy.FileRules.Length; - signerSiPolicy.FileRules = signerSiPolicy.FileRules.Where(item => - { - string friendlyName = null; - if (item is Allow allow) - friendlyName = allow.FriendlyName; - else if (item is Deny deny) - friendlyName = deny.FriendlyName; - - // If it's not a hash rule (no FriendlyName with hash pattern), keep it - if (string.IsNullOrEmpty(friendlyName)) - return true; - - // Check if FriendlyName ends with one of the selected hash type patterns - foreach (string hashType in customRule.HashTypesToKeep) - { - if (friendlyName.EndsWith(hashType, StringComparison.OrdinalIgnoreCase)) - return true; - } - - // If it doesn't match any selected hash type pattern, remove it - return false; - }).ToArray(); - - int afterCount = signerSiPolicy.FileRules.Length; - worker.ReportProgress(75, $"Filtered: kept {afterCount} of {beforeCount} hash rules."); - } - - worker.ReportProgress(80, "Merging scanned policy rules ..."); siPolicy = PolicyHelper.MergePolicies(signerSiPolicy, siPolicy); } } @@ -1420,17 +1345,9 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) /// public SiPolicy ProcessCustomValueRules(BackgroundWorker worker, SiPolicy siPolicyCustomValueRules) { - int totalRules = this.Policy.CustomRules.Count; - int processed = 0; - // Iterate through all of the custom rules and PFN rules and update the progress bar foreach (var customRule in this.Policy.CustomRules) { - processed++; - int progressVal = processed * 25 / Math.Max(totalRules, 1); - string statusMsg = $"Processing custom value rule {processed} of {totalRules} ..."; - worker.ReportProgress(progressVal, statusMsg); - if(customRule.UsingCustomValues) { siPolicyCustomValueRules = HandleCustomValues(customRule, siPolicyCustomValueRules); diff --git a/WDAC-Policy-Wizard/app/src/Policy.cs b/WDAC-Policy-Wizard/app/src/Policy.cs index 00b947af..19476ebb 100644 --- a/WDAC-Policy-Wizard/app/src/Policy.cs +++ b/WDAC-Policy-Wizard/app/src/Policy.cs @@ -640,12 +640,6 @@ public enum RulePermission { Allow, Deny }; // Folder Scan public FolderScan Scan { get; set; } - // Hash types to keep when filtering generated policy XML (e.g., "Hash Sha1", "Hash Sha256", "Hash Page Sha1", "Hash Page Sha256") - public HashSet HashTypesToKeep { get; set; } - - // Original source folder path (used to fix FriendlyName in generated policy when scanning from temp) - public string SourceFolderPath { get; set; } - // Constructors public PolicyCustomRules() { diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 1d48fed4..41fb16fd 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -1196,36 +1196,6 @@ public void AddRuleToTable(string [] displayObjectArray, PolicyCustomRules custo this.customRuleConditionsPanel = null; } - /// - /// Adds a new rule to the DataGrid Table without closing the custom rules panel. - /// Used when adding multiple rules at once (e.g., multi-file hash scan). - /// - /// - /// - /// - public void AddRuleToTableWithoutClosing(string[] displayObjectArray, PolicyCustomRules customRule, bool warnUser) - { - // Attach the int row number we added it to - customRule.RowNumber = this.rulesDataGrid.RowCount - 1; - string action = displayObjectArray[0]; - string level = displayObjectArray[1]; - string name = warnUser ? "*Hash Fallback Possible* " + displayObjectArray[2] : displayObjectArray[2]; - string files = displayObjectArray[3]; - string exceptions = displayObjectArray[4]; - - // Add to the DisplayObject - this.displayObjects.Add(new DisplayObject(action, level, name, files, exceptions)); - this.rulesDataGrid.RowCount += 1; - - // Add custom list to RulesList - this.Policy.CustomRules.Add(customRule); - - // Scroll to bottom to see new rule added to list - this.rulesDataGrid.FirstDisplayedScrollingRowIndex = this.rulesDataGrid.RowCount - 1; - - BubbleUp(); - } - /// /// Nullifies the custom rule conditions panel on form closing /// From e7f6333fe256c277d9c3ea709325292e495d92e8 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Tue, 5 May 2026 13:42:14 -0400 Subject: [PATCH 3/8] File Scanner UI updates, smoother controls --- .../src/CustomRuleConditionsPanel.Designer.cs | 24 ++++++--- .../app/src/CustomRuleConditionsPanel.cs | 51 ++++++++++++++++++- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs index 9da2f1a8..e65d9490 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs @@ -154,6 +154,7 @@ private void InitializeComponent() panel_CustomRules.Margin = new System.Windows.Forms.Padding(2); panel_CustomRules.Name = "panel_CustomRules"; panel_CustomRules.Size = new System.Drawing.Size(615, 719); + panel_CustomRules.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; panel_CustomRules.TabIndex = 86; // // appIdPanel @@ -371,17 +372,22 @@ private void InitializeComponent() panelFolderScanConditions.Location = new System.Drawing.Point(552, 630); panelFolderScanConditions.Margin = new System.Windows.Forms.Padding(2); panelFolderScanConditions.Name = "panelFolderScanConditions"; - panelFolderScanConditions.Size = new System.Drawing.Size(537, 359); + panelFolderScanConditions.Size = new System.Drawing.Size(560, 340); + panelFolderScanConditions.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; panelFolderScanConditions.TabIndex = 126; panelFolderScanConditions.Visible = false; // // checkedListBoxOmitPaths // - checkedListBoxOmitPaths.Font = new System.Drawing.Font("Tahoma", 8F); + checkedListBoxOmitPaths.Font = new System.Drawing.Font("Tahoma", 8.5F); checkedListBoxOmitPaths.FormattingEnabled = true; - checkedListBoxOmitPaths.Location = new System.Drawing.Point(8, 260); + checkedListBoxOmitPaths.CheckOnClick = true; + checkedListBoxOmitPaths.HorizontalScrollbar = true; + checkedListBoxOmitPaths.IntegralHeight = false; + checkedListBoxOmitPaths.Location = new System.Drawing.Point(8, 250); checkedListBoxOmitPaths.Name = "checkedListBoxOmitPaths"; - checkedListBoxOmitPaths.Size = new System.Drawing.Size(341, 80); + checkedListBoxOmitPaths.Size = new System.Drawing.Size(500, 80); + checkedListBoxOmitPaths.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; checkedListBoxOmitPaths.TabIndex = 116; // // label12 @@ -401,7 +407,7 @@ private void InitializeComponent() label11.AutoSize = true; label11.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); label11.ForeColor = System.Drawing.Color.Black; - label11.Location = new System.Drawing.Point(5, 232); + label11.Location = new System.Drawing.Point(5, 228); label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label11.Name = "label11"; label11.Size = new System.Drawing.Size(231, 18); @@ -429,13 +435,15 @@ private void InitializeComponent() // checkedListBoxRuleLevels // checkedListBoxRuleLevels.AllowDrop = true; + checkedListBoxRuleLevels.CheckOnClick = true; checkedListBoxRuleLevels.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); checkedListBoxRuleLevels.FormattingEnabled = true; checkedListBoxRuleLevels.Items.AddRange(new object[] { "PcaCertificate", "Publisher", "SignedVersion", "FilePublisher", "FileName", "FilePath", "Hash" }); checkedListBoxRuleLevels.Location = new System.Drawing.Point(8, 121); checkedListBoxRuleLevels.MultiColumn = true; + checkedListBoxRuleLevels.ColumnWidth = 160; checkedListBoxRuleLevels.Name = "checkedListBoxRuleLevels"; - checkedListBoxRuleLevels.Size = new System.Drawing.Size(341, 88); + checkedListBoxRuleLevels.Size = new System.Drawing.Size(500, 100); checkedListBoxRuleLevels.TabIndex = 111; checkedListBoxRuleLevels.DragDrop += RuleLevelsList_DragDropDone; checkedListBoxRuleLevels.DragOver += RuleLevelsList_DragInProgress; @@ -1071,6 +1079,7 @@ private void InitializeComponent() button_CreateRule.Margin = new System.Windows.Forms.Padding(2); button_CreateRule.Name = "button_CreateRule"; button_CreateRule.Size = new System.Drawing.Size(110, 30); + button_CreateRule.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_CreateRule.TabIndex = 92; button_CreateRule.Text = "Create Rule"; button_CreateRule.UseVisualStyleBackColor = false; @@ -1083,6 +1092,7 @@ private void InitializeComponent() button_Next.Margin = new System.Windows.Forms.Padding(2); button_Next.Name = "button_Next"; button_Next.Size = new System.Drawing.Size(99, 30); + button_Next.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Next.TabIndex = 107; button_Next.Text = "Next >"; button_Next.UseVisualStyleBackColor = false; @@ -1195,6 +1205,7 @@ private void InitializeComponent() button_AddException.Margin = new System.Windows.Forms.Padding(2); button_AddException.Name = "button_AddException"; button_AddException.Size = new System.Drawing.Size(110, 30); + button_AddException.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_AddException.TabIndex = 111; button_AddException.Text = "Add Exception"; button_AddException.UseVisualStyleBackColor = false; @@ -1209,6 +1220,7 @@ private void InitializeComponent() button_Back.Margin = new System.Windows.Forms.Padding(2); button_Back.Name = "button_Back"; button_Back.Size = new System.Drawing.Size(99, 30); + button_Back.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Back.TabIndex = 110; button_Back.Text = "< Back"; button_Back.UseVisualStyleBackColor = false; diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs index 579b9b3f..1a92ff92 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs @@ -60,6 +60,44 @@ public CustomRuleConditionsPanel(SigningRules_Control pControl) this.exceptionsControl = null; this.DefaultValues = new string[5]; this.FoundPackages = new List(); + CreateOmitPathsButtons(); + } + + /// + /// Creates Select All / Deselect All buttons above the omit paths list. + /// + private void CreateOmitPathsButtons() + { + var btnSelectAll = new Button + { + Text = "Select All", + Size = new System.Drawing.Size(80, 25), + Font = new System.Drawing.Font("Tahoma", 7.5F), + Location = new System.Drawing.Point(checkedListBoxOmitPaths.Right - 165, checkedListBoxOmitPaths.Top - 28), + Anchor = AnchorStyles.Top | AnchorStyles.Right + }; + btnSelectAll.Click += (s, ev) => + { + for (int i = 0; i < checkedListBoxOmitPaths.Items.Count; i++) + checkedListBoxOmitPaths.SetItemChecked(i, true); + }; + + var btnDeselectAll = new Button + { + Text = "Deselect All", + Size = new System.Drawing.Size(80, 25), + Font = new System.Drawing.Font("Tahoma", 7.5F), + Location = new System.Drawing.Point(checkedListBoxOmitPaths.Right - 80, checkedListBoxOmitPaths.Top - 28), + Anchor = AnchorStyles.Top | AnchorStyles.Right + }; + btnDeselectAll.Click += (s, ev) => + { + for (int i = 0; i < checkedListBoxOmitPaths.Items.Count; i++) + checkedListBoxOmitPaths.SetItemChecked(i, false); + }; + + panelFolderScanConditions.Controls.Add(btnSelectAll); + panelFolderScanConditions.Controls.Add(btnDeselectAll); } /// @@ -817,6 +855,7 @@ private void RuleType_ComboboxChanged(object sender, EventArgs e) this.panelFolderScanConditions.Location = this.checkBox_CustomPath.Location; this.panelFolderScanConditions.Visible = true; this.label_condition.Text = "Scan Path:"; + this.button_Next.Visible = false; break; case "Certificate File": @@ -2734,7 +2773,17 @@ private void LabelFolderScanLearnMore_Click(object sender, EventArgs e) /// private void RuleLevelsList_MouseDown(object sender, MouseEventArgs e) { - if (this.checkedListBoxRuleLevels.SelectedItem == null || e.X < 15 || (e.X > 150 && e.X < 165)) return; // e.X < 15 - left most column checkboxes. 150 < e.X < 165 - right most checkboxes + if (this.checkedListBoxRuleLevels.SelectedItem == null) return; + + // Determine checkbox width relative to each column + int columnWidth = this.checkedListBoxRuleLevels.ColumnWidth > 0 + ? this.checkedListBoxRuleLevels.ColumnWidth + : this.checkedListBoxRuleLevels.Width; + int xInColumn = e.X % columnWidth; + + // If click is in the checkbox area (first ~18px of each column), let CheckOnClick handle it + if (xInColumn < 18) return; + this.checkedListBoxRuleLevels.DoDragDrop(this.checkedListBoxRuleLevels.SelectedItem, DragDropEffects.Move); } From 1b15dc38b4aa42f8cdfbdbd4b0667ed4c66fce4c Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Tue, 5 May 2026 15:37:49 -0400 Subject: [PATCH 4/8] =?UTF-8?q?PSCmdlets.cs=20=E2=80=A2=20Added=20-NoLogo?= =?UTF-8?q?=20-NonInteractive=20flags=20to=20PowerShell=20invocation=20to?= =?UTF-8?q?=20reduce=20startup=20overhead=20=E2=80=A2=20Fixed=20potential?= =?UTF-8?q?=20deadlock:=20moved=20StandardOutput.ReadToEnd()=20and=20Stand?= =?UTF-8?q?ardError.ReadToEnd()=20before=20WaitForExit()=20to=20prevent=20?= =?UTF-8?q?buffer-full=20hang?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MainForm.cs • Fixed progress bar stalling at 25% during Folder Scan by reporting progress after skipping non-applicable rules • Added mid-scan progress report (~55%) before CreateScannedPolicyFromPS(PolicyCustomRules, string, string) so UI shows activity during long scans • Updated progress status text: "Scanning and processing rules (this may take a few minutes) ..." for the 25-80% range --- .../app/MSIX/CreateScannedPolicy.ps1 | 15 ++++++--------- .../app/Scripts/CreateScannedPolicy.ps1 | 15 ++++++--------- WDAC-Policy-Wizard/app/src/MainForm.cs | 17 ++++++++++------- WDAC-Policy-Wizard/app/src/PSCmdlets.cs | 6 +++--- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/WDAC-Policy-Wizard/app/MSIX/CreateScannedPolicy.ps1 b/WDAC-Policy-Wizard/app/MSIX/CreateScannedPolicy.ps1 index 467c71f2..d35fb73a 100644 --- a/WDAC-Policy-Wizard/app/MSIX/CreateScannedPolicy.ps1 +++ b/WDAC-Policy-Wizard/app/MSIX/CreateScannedPolicy.ps1 @@ -16,31 +16,28 @@ param ( ) # Run New-CIPolicy -Scan to generate a policy from a directory -# The command needs to be run twice to generate the full policy. Otherwise, the "An item with the same key has already been added." WARNING prevents the full policy from being generated. +# Use -WarningAction SilentlyContinue to suppress the "An item with the same key has already been added." warning +# which previously required running the command twice as a workaround. if($Deny -eq "False") { if($UserPEs -eq "True") { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -WarningAction SilentlyContinue } else { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -WarningAction SilentlyContinue } } else { if($UserPEs -eq "True") { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny -WarningAction SilentlyContinue } else { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny -WarningAction SilentlyContinue } } # SIG # Begin signature block diff --git a/WDAC-Policy-Wizard/app/Scripts/CreateScannedPolicy.ps1 b/WDAC-Policy-Wizard/app/Scripts/CreateScannedPolicy.ps1 index 467c71f2..d35fb73a 100644 --- a/WDAC-Policy-Wizard/app/Scripts/CreateScannedPolicy.ps1 +++ b/WDAC-Policy-Wizard/app/Scripts/CreateScannedPolicy.ps1 @@ -16,31 +16,28 @@ param ( ) # Run New-CIPolicy -Scan to generate a policy from a directory -# The command needs to be run twice to generate the full policy. Otherwise, the "An item with the same key has already been added." WARNING prevents the full policy from being generated. +# Use -WarningAction SilentlyContinue to suppress the "An item with the same key has already been added." warning +# which previously required running the command twice as a workaround. if($Deny -eq "False") { if($UserPEs -eq "True") { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -WarningAction SilentlyContinue } else { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -WarningAction SilentlyContinue } } else { if($UserPEs -eq "True") { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -UserPEs -Deny -WarningAction SilentlyContinue } else { - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny - New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny + New-CIPolicy -ScanPath $ScanPath -Level $Level -FilePath $PolicyPath -Fallback $Fallback -OmitPaths $PathsToOmit -Deny -WarningAction SilentlyContinue } } # SIG # Begin signature block diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 1c81bcd1..3eb9600a 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -853,10 +853,10 @@ private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEve int progressPercent = e.ProgressPercentage; if (progressPercent <= 10) process = "Building policy rules ..."; - else if (progressPercent <= 70) + else if (progressPercent <= 25) process = "Configuring policy signer and file rules ..."; else if (progressPercent <= 80) - process = "Building custom policy file rules ..."; + process = "Scanning and processing rules (this may take a few minutes) ..."; else if (progressPercent <= 85) process = "Merging custom rules policies ..."; else if (progressPercent <= 95) @@ -1252,9 +1252,6 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) // Iterate through all of the custom rules and update the progress bar for (int i = 0; i < nCustomRules; i++) { - progressVal = 25 + i * 60 / nCustomRules; - worker.ReportProgress(progressVal); //Assumes the operations involved with this step take about 70% -- probably should be a little higher - var customRule = this.Policy.CustomRules[i]; // Skip; already handled ALL custom value rules @@ -1273,6 +1270,9 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) continue; } + progressVal = 25 + i * 60 / nCustomRules; + worker.ReportProgress(progressVal); + string tmpPolicyPath = Helper.GetUniquePolicyPath(this.TempFolderPath); // Create a single policy per rule using the Powershell cmdlets with Level=PCACertificate or Publisher @@ -1288,7 +1288,7 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) siPolicy = PolicyHelper.MergePolicies(signerSiPolicy, siPolicy); } } - + // Hash Rules -- Invoke Powershell cmd to generate if(customRule.Type == PolicyCustomRules.RuleType.Hash) { @@ -1303,6 +1303,9 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) // Folder Scan -- Invoke the New-CiPolicy PS cmd to generate a CI policy if(customRule.Type == PolicyCustomRules.RuleType.FolderScan) { + // Report a mid-range progress so the UI shows scanning activity + worker.ReportProgress(Math.Min(progressVal + 30, 80)); + SiPolicy signerSiPolicy; if (this.Policy._PolicyType == WDAC_Policy.PolicyType.BasePolicy) { @@ -1312,7 +1315,7 @@ public SiPolicy ProcessSignerRules(BackgroundWorker worker, SiPolicy siPolicy) { signerSiPolicy = PSCmdlets.CreateScannedPolicyFromPS(customRule, tmpPolicyPath, this.Policy.BaseToSupplementPath); } - + // Successful Scan completed if (signerSiPolicy != null) { diff --git a/WDAC-Policy-Wizard/app/src/PSCmdlets.cs b/WDAC-Policy-Wizard/app/src/PSCmdlets.cs index 557e6c05..867cdfbc 100644 --- a/WDAC-Policy-Wizard/app/src/PSCmdlets.cs +++ b/WDAC-Policy-Wizard/app/src/PSCmdlets.cs @@ -176,7 +176,7 @@ internal static SiPolicy CreateScannedPolicyFromPS(PolicyCustomRules customRule, deny = "True"; } - string newPolicyScriptCmd = $"-NoProfile -ExecutionPolicy Bypass -File \"{ps1File}\" -ScanPath \"{scanPath}\" " + + string newPolicyScriptCmd = $"-NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File \"{ps1File}\" -ScanPath \"{scanPath}\" " + $"-PolicyPath \"{policyPath}\" -Level {level} -Fallback {fallbacks} -PathsToOmit \"{pathsToOmit}\"" + $" -Deny {deny} -UserPEs {userPEs}"; @@ -201,10 +201,10 @@ internal static SiPolicy CreateScannedPolicyFromPS(PolicyCustomRules customRule, try { process.Start(); - process.WaitForExit(); - + // Read streams asynchronously to avoid deadlocks and allow PS to flush output string output = process.StandardOutput.ReadToEnd(); string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); if (!string.IsNullOrEmpty(error)) { From d8107354b5ec3ad342c5e814e1d5235c3564bff8 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Thu, 7 May 2026 11:19:42 -0400 Subject: [PATCH 5/8] Revert "File Scanner UI updates, smoother controls" This reverts commit e7f6333fe256c277d9c3ea709325292e495d92e8. --- .../src/CustomRuleConditionsPanel.Designer.cs | 24 +++------ .../app/src/CustomRuleConditionsPanel.cs | 51 +------------------ 2 files changed, 7 insertions(+), 68 deletions(-) diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs index e65d9490..9da2f1a8 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs @@ -154,7 +154,6 @@ private void InitializeComponent() panel_CustomRules.Margin = new System.Windows.Forms.Padding(2); panel_CustomRules.Name = "panel_CustomRules"; panel_CustomRules.Size = new System.Drawing.Size(615, 719); - panel_CustomRules.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; panel_CustomRules.TabIndex = 86; // // appIdPanel @@ -372,22 +371,17 @@ private void InitializeComponent() panelFolderScanConditions.Location = new System.Drawing.Point(552, 630); panelFolderScanConditions.Margin = new System.Windows.Forms.Padding(2); panelFolderScanConditions.Name = "panelFolderScanConditions"; - panelFolderScanConditions.Size = new System.Drawing.Size(560, 340); - panelFolderScanConditions.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + panelFolderScanConditions.Size = new System.Drawing.Size(537, 359); panelFolderScanConditions.TabIndex = 126; panelFolderScanConditions.Visible = false; // // checkedListBoxOmitPaths // - checkedListBoxOmitPaths.Font = new System.Drawing.Font("Tahoma", 8.5F); + checkedListBoxOmitPaths.Font = new System.Drawing.Font("Tahoma", 8F); checkedListBoxOmitPaths.FormattingEnabled = true; - checkedListBoxOmitPaths.CheckOnClick = true; - checkedListBoxOmitPaths.HorizontalScrollbar = true; - checkedListBoxOmitPaths.IntegralHeight = false; - checkedListBoxOmitPaths.Location = new System.Drawing.Point(8, 250); + checkedListBoxOmitPaths.Location = new System.Drawing.Point(8, 260); checkedListBoxOmitPaths.Name = "checkedListBoxOmitPaths"; - checkedListBoxOmitPaths.Size = new System.Drawing.Size(500, 80); - checkedListBoxOmitPaths.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + checkedListBoxOmitPaths.Size = new System.Drawing.Size(341, 80); checkedListBoxOmitPaths.TabIndex = 116; // // label12 @@ -407,7 +401,7 @@ private void InitializeComponent() label11.AutoSize = true; label11.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); label11.ForeColor = System.Drawing.Color.Black; - label11.Location = new System.Drawing.Point(5, 228); + label11.Location = new System.Drawing.Point(5, 232); label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label11.Name = "label11"; label11.Size = new System.Drawing.Size(231, 18); @@ -435,15 +429,13 @@ private void InitializeComponent() // checkedListBoxRuleLevels // checkedListBoxRuleLevels.AllowDrop = true; - checkedListBoxRuleLevels.CheckOnClick = true; checkedListBoxRuleLevels.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); checkedListBoxRuleLevels.FormattingEnabled = true; checkedListBoxRuleLevels.Items.AddRange(new object[] { "PcaCertificate", "Publisher", "SignedVersion", "FilePublisher", "FileName", "FilePath", "Hash" }); checkedListBoxRuleLevels.Location = new System.Drawing.Point(8, 121); checkedListBoxRuleLevels.MultiColumn = true; - checkedListBoxRuleLevels.ColumnWidth = 160; checkedListBoxRuleLevels.Name = "checkedListBoxRuleLevels"; - checkedListBoxRuleLevels.Size = new System.Drawing.Size(500, 100); + checkedListBoxRuleLevels.Size = new System.Drawing.Size(341, 88); checkedListBoxRuleLevels.TabIndex = 111; checkedListBoxRuleLevels.DragDrop += RuleLevelsList_DragDropDone; checkedListBoxRuleLevels.DragOver += RuleLevelsList_DragInProgress; @@ -1079,7 +1071,6 @@ private void InitializeComponent() button_CreateRule.Margin = new System.Windows.Forms.Padding(2); button_CreateRule.Name = "button_CreateRule"; button_CreateRule.Size = new System.Drawing.Size(110, 30); - button_CreateRule.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_CreateRule.TabIndex = 92; button_CreateRule.Text = "Create Rule"; button_CreateRule.UseVisualStyleBackColor = false; @@ -1092,7 +1083,6 @@ private void InitializeComponent() button_Next.Margin = new System.Windows.Forms.Padding(2); button_Next.Name = "button_Next"; button_Next.Size = new System.Drawing.Size(99, 30); - button_Next.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Next.TabIndex = 107; button_Next.Text = "Next >"; button_Next.UseVisualStyleBackColor = false; @@ -1205,7 +1195,6 @@ private void InitializeComponent() button_AddException.Margin = new System.Windows.Forms.Padding(2); button_AddException.Name = "button_AddException"; button_AddException.Size = new System.Drawing.Size(110, 30); - button_AddException.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_AddException.TabIndex = 111; button_AddException.Text = "Add Exception"; button_AddException.UseVisualStyleBackColor = false; @@ -1220,7 +1209,6 @@ private void InitializeComponent() button_Back.Margin = new System.Windows.Forms.Padding(2); button_Back.Name = "button_Back"; button_Back.Size = new System.Drawing.Size(99, 30); - button_Back.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Back.TabIndex = 110; button_Back.Text = "< Back"; button_Back.UseVisualStyleBackColor = false; diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs index 1a92ff92..579b9b3f 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs @@ -60,44 +60,6 @@ public CustomRuleConditionsPanel(SigningRules_Control pControl) this.exceptionsControl = null; this.DefaultValues = new string[5]; this.FoundPackages = new List(); - CreateOmitPathsButtons(); - } - - /// - /// Creates Select All / Deselect All buttons above the omit paths list. - /// - private void CreateOmitPathsButtons() - { - var btnSelectAll = new Button - { - Text = "Select All", - Size = new System.Drawing.Size(80, 25), - Font = new System.Drawing.Font("Tahoma", 7.5F), - Location = new System.Drawing.Point(checkedListBoxOmitPaths.Right - 165, checkedListBoxOmitPaths.Top - 28), - Anchor = AnchorStyles.Top | AnchorStyles.Right - }; - btnSelectAll.Click += (s, ev) => - { - for (int i = 0; i < checkedListBoxOmitPaths.Items.Count; i++) - checkedListBoxOmitPaths.SetItemChecked(i, true); - }; - - var btnDeselectAll = new Button - { - Text = "Deselect All", - Size = new System.Drawing.Size(80, 25), - Font = new System.Drawing.Font("Tahoma", 7.5F), - Location = new System.Drawing.Point(checkedListBoxOmitPaths.Right - 80, checkedListBoxOmitPaths.Top - 28), - Anchor = AnchorStyles.Top | AnchorStyles.Right - }; - btnDeselectAll.Click += (s, ev) => - { - for (int i = 0; i < checkedListBoxOmitPaths.Items.Count; i++) - checkedListBoxOmitPaths.SetItemChecked(i, false); - }; - - panelFolderScanConditions.Controls.Add(btnSelectAll); - panelFolderScanConditions.Controls.Add(btnDeselectAll); } /// @@ -855,7 +817,6 @@ private void RuleType_ComboboxChanged(object sender, EventArgs e) this.panelFolderScanConditions.Location = this.checkBox_CustomPath.Location; this.panelFolderScanConditions.Visible = true; this.label_condition.Text = "Scan Path:"; - this.button_Next.Visible = false; break; case "Certificate File": @@ -2773,17 +2734,7 @@ private void LabelFolderScanLearnMore_Click(object sender, EventArgs e) /// private void RuleLevelsList_MouseDown(object sender, MouseEventArgs e) { - if (this.checkedListBoxRuleLevels.SelectedItem == null) return; - - // Determine checkbox width relative to each column - int columnWidth = this.checkedListBoxRuleLevels.ColumnWidth > 0 - ? this.checkedListBoxRuleLevels.ColumnWidth - : this.checkedListBoxRuleLevels.Width; - int xInColumn = e.X % columnWidth; - - // If click is in the checkbox area (first ~18px of each column), let CheckOnClick handle it - if (xInColumn < 18) return; - + if (this.checkedListBoxRuleLevels.SelectedItem == null || e.X < 15 || (e.X > 150 && e.X < 165)) return; // e.X < 15 - left most column checkboxes. 150 < e.X < 165 - right most checkboxes this.checkedListBoxRuleLevels.DoDragDrop(this.checkedListBoxRuleLevels.SelectedItem, DragDropEffects.Move); } From e904f1402bc6547170c373d7b73f1386096d1727 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Sun, 31 May 2026 10:13:43 -0400 Subject: [PATCH 6/8] =?UTF-8?q?=E2=80=A2=20Removed=20the=20forced=20ruleTy?= =?UTF-8?q?peComboBox.SelectedIndex=20=3D=200=20from=20ResetCustomRulesPan?= =?UTF-8?q?el()=20so=20the=20dropdown=20no=20longer=20snaps=20back=20to=20?= =?UTF-8?q?Publisher=20on=20every=20row=20click.=20=E2=80=A2=20Updated=20R?= =?UTF-8?q?owSelectionChanged(object,=20EventArgs)=20and=20EventRowClick(o?= =?UTF-8?q?bject,=20DataGridViewCellEventArgs)=20to=20repopulate=20the=20r?= =?UTF-8?q?ule=20panel=20based=20on=20the=20currently=20selected=20rule=20?= =?UTF-8?q?type=20instead=20of=20hard-coding=20SetPublisherPanel(...).=20?= =?UTF-8?q?=E2=80=A2=20Extracted=20the=20panel-population=20logic=20into?= =?UTF-8?q?=20a=20shared=20RefreshRulePanelForSelectedRow()=20method,=20re?= =?UTF-8?q?used=20by=20both=20the=20row=20handlers=20and=20RuleTypeChanged?= =?UTF-8?q?(object,=20EventArgs).=20=E2=80=A2=20Set=20SelectedRow=20before?= =?UTF-8?q?=20refreshing=20the=20UI=20and=20added=20a=20bounds=20guard.=20?= =?UTF-8?q?=E2=80=A2=20Reordered=20the=20header-click=20(-1)=20sort=20chec?= =?UTF-8?q?k=20in=20EventRowClick(object,=20DataGridViewCellEventArgs)=20s?= =?UTF-8?q?o=20sorting=20still=20works.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/EditWorkflow.Designer.cs | 6 +- .../src/EventLogRuleConfiguration.Designer.cs | 23 ++++--- .../app/src/EventLogRuleConfiguration.cs | 61 ++++++++++++------- .../app/src/Exceptions_Control.Designer.cs | 3 +- .../app/src/Exceptions_Control.cs | 4 ++ .../app/src/GridLayoutHelper.cs | 55 +++++++++++++++++ .../app/src/MainForm.Designer.cs | 8 ++- WDAC-Policy-Wizard/app/src/MainForm.cs | 39 ++++++++++-- .../app/src/PolicyMerge_Control.Designer.cs | 1 + .../app/src/PolicyMerge_Control.cs | 4 ++ .../app/src/SigningRules_Control.Designer.cs | 21 ++++--- .../app/src/SigningRules_Control.cs | 8 +++ 12 files changed, 187 insertions(+), 46 deletions(-) create mode 100644 WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs diff --git a/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs b/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs index 17e2a9f1..14757d5f 100644 --- a/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs @@ -237,7 +237,7 @@ private void InitializeComponent() // buttonParseEventLog.Location = new System.Drawing.Point(343, 72); buttonParseEventLog.Name = "buttonParseEventLog"; - buttonParseEventLog.Size = new System.Drawing.Size(133, 27); + buttonParseEventLog.Size = new System.Drawing.Size(165, 32); buttonParseEventLog.TabIndex = 112; buttonParseEventLog.Text = "Parse Event Logs"; buttonParseEventLog.UseVisualStyleBackColor = true; @@ -248,7 +248,7 @@ private void InitializeComponent() buttonParseLogFile.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); buttonParseLogFile.Location = new System.Drawing.Point(343, 174); buttonParseLogFile.Name = "buttonParseLogFile"; - buttonParseLogFile.Size = new System.Drawing.Size(133, 27); + buttonParseLogFile.Size = new System.Drawing.Size(165, 32); buttonParseLogFile.TabIndex = 113; buttonParseLogFile.Text = "Parse Log File(s)"; buttonParseLogFile.UseVisualStyleBackColor = true; @@ -384,7 +384,7 @@ private void InitializeComponent() buttonParseMDELog.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); buttonParseMDELog.Location = new System.Drawing.Point(342, 273); buttonParseMDELog.Name = "buttonParseMDELog"; - buttonParseMDELog.Size = new System.Drawing.Size(133, 27); + buttonParseMDELog.Size = new System.Drawing.Size(165, 32); buttonParseMDELog.TabIndex = 124; buttonParseMDELog.Text = "Parse Log File(s)"; buttonParseMDELog.UseVisualStyleBackColor = true; diff --git a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs index f8a0c3bb..093181ff 100644 --- a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs @@ -86,6 +86,7 @@ private void InitializeComponent() addButton.Location = new System.Drawing.Point(442, 412); addButton.Name = "addButton"; addButton.Size = new System.Drawing.Size(130, 30); + addButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; addButton.TabIndex = 0; addButton.Text = "+ Add Allow Rule"; addButton.UseVisualStyleBackColor = true; @@ -106,6 +107,7 @@ private void InitializeComponent() publisherRulePanel.Location = new System.Drawing.Point(167, 447); publisherRulePanel.Name = "publisherRulePanel"; publisherRulePanel.Size = new System.Drawing.Size(716, 190); + publisherRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; publisherRulePanel.TabIndex = 9; // // productTextBox @@ -227,6 +229,7 @@ private void InitializeComponent() label3.Location = new System.Drawing.Point(163, 415); label3.Name = "label3"; label3.Size = new System.Drawing.Size(90, 21); + label3.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; label3.TabIndex = 8; label3.Text = "Rule Type:"; // @@ -238,6 +241,7 @@ private void InitializeComponent() ruleTypeComboBox.Location = new System.Drawing.Point(259, 412); ruleTypeComboBox.Name = "ruleTypeComboBox"; ruleTypeComboBox.Size = new System.Drawing.Size(163, 29); + ruleTypeComboBox.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; ruleTypeComboBox.TabIndex = 3; ruleTypeComboBox.SelectedIndexChanged += RuleTypeChanged; // @@ -256,6 +260,7 @@ private void InitializeComponent() fileAttributeRulePanel.Location = new System.Drawing.Point(906, 447); fileAttributeRulePanel.Name = "fileAttributeRulePanel"; fileAttributeRulePanel.Size = new System.Drawing.Size(551, 190); + fileAttributeRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; fileAttributeRulePanel.TabIndex = 21; fileAttributeRulePanel.Visible = false; // @@ -373,7 +378,7 @@ private void InitializeComponent() // eventsDataGridView.AllowUserToDeleteRows = false; eventsDataGridView.AllowUserToResizeRows = false; - eventsDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + eventsDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; eventsDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; eventsDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { addedColumn, eventIdColumn, filenameColumn, productColumn, policyColumn, publisherColumn, issuerTbsHashColumn }); eventsDataGridView.EnableHeadersVisualStyles = false; @@ -387,6 +392,7 @@ private void InitializeComponent() eventsDataGridView.RowTemplate.Height = 24; eventsDataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; eventsDataGridView.Size = new System.Drawing.Size(896, 287); + eventsDataGridView.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; eventsDataGridView.TabIndex = 4; eventsDataGridView.VirtualMode = true; eventsDataGridView.CellClick += EventRowClick; @@ -395,7 +401,7 @@ private void InitializeComponent() // // addedColumn // - addedColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + addedColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; addedColumn.HeaderText = "Added To Policy"; addedColumn.MinimumWidth = 100; addedColumn.Name = "addedColumn"; @@ -405,7 +411,7 @@ private void InitializeComponent() // // eventIdColumn // - eventIdColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + eventIdColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; eventIdColumn.HeaderText = "Event Id"; eventIdColumn.MinimumWidth = 6; eventIdColumn.Name = "eventIdColumn"; @@ -415,7 +421,7 @@ private void InitializeComponent() // // filenameColumn // - filenameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + filenameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; filenameColumn.HeaderText = "Filename"; filenameColumn.MinimumWidth = 6; filenameColumn.Name = "filenameColumn"; @@ -425,7 +431,7 @@ private void InitializeComponent() // // productColumn // - productColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + productColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; productColumn.HeaderText = "Product"; productColumn.MinimumWidth = 6; productColumn.Name = "productColumn"; @@ -435,7 +441,7 @@ private void InitializeComponent() // // policyColumn // - policyColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + policyColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; policyColumn.HeaderText = "Policy Name"; policyColumn.MinimumWidth = 6; policyColumn.Name = "policyColumn"; @@ -445,7 +451,7 @@ private void InitializeComponent() // // publisherColumn // - publisherColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + publisherColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; publisherColumn.HeaderText = "Publisher"; publisherColumn.MinimumWidth = 6; publisherColumn.Name = "publisherColumn"; @@ -482,6 +488,7 @@ private void InitializeComponent() hashRulePanel.Location = new System.Drawing.Point(765, 664); hashRulePanel.Name = "hashRulePanel"; hashRulePanel.Size = new System.Drawing.Size(603, 93); + hashRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; hashRulePanel.TabIndex = 22; hashRulePanel.Visible = false; // @@ -534,6 +541,7 @@ private void InitializeComponent() filePathRulePanel.Location = new System.Drawing.Point(16, 664); filePathRulePanel.Name = "filePathRulePanel"; filePathRulePanel.Size = new System.Drawing.Size(743, 93); + filePathRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; filePathRulePanel.TabIndex = 22; filePathRulePanel.Visible = false; // @@ -595,6 +603,7 @@ private void InitializeComponent() // AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + AutoScroll = true; BackColor = System.Drawing.Color.White; Controls.Add(publisherRulePanel); Controls.Add(label3); diff --git a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs index 50d2995d..1f4d9f44 100644 --- a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs +++ b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs @@ -94,6 +94,10 @@ private void DisplayEvents() this.DisplayObjects.Add(dpObject); this.eventsDataGridView.RowCount += 1; } + + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.eventsDataGridView); } /// @@ -188,15 +192,14 @@ private void RowSelectionChanged(object sender, EventArgs e) return; } - // Set the UI - ResetCustomRulesPanel(); - SetPublisherPanel(this.CiEvents[selectedRow].SignerInfo.IssuerName, - this.CiEvents[selectedRow].SignerInfo.PublisherName, - this.CiEvents[selectedRow].OriginalFilename, - this.CiEvents[selectedRow].FileVersion, - this.CiEvents[selectedRow].ProductName); - + // Update the selected row before refreshing the UI so that the + // rule type panel reflects the newly selected event. this.SelectedRow = selectedRow; + + // Set the UI. Keep the user's current rule type selection and + // repopulate the matching panel for the newly selected row. + ResetCustomRulesPanel(); + RefreshRulePanelForSelectedRow(); } /// @@ -206,14 +209,7 @@ private void RowSelectionChanged(object sender, EventArgs e) /// private void EventRowClick(object sender, DataGridViewCellEventArgs e) { - // Set the UI - ResetCustomRulesPanel(); - int selectedRow = e.RowIndex; - if(selectedRow >= this.CiEvents.Count) - { - return; - } // Header selected, sort table if(selectedRow == -1) @@ -221,14 +217,19 @@ private void EventRowClick(object sender, DataGridViewCellEventArgs e) SortDataGrid(sender, e); return; } - - SetPublisherPanel(this.CiEvents[selectedRow].SignerInfo.IssuerName, - this.CiEvents[selectedRow].SignerInfo.PublisherName, - this.CiEvents[selectedRow].OriginalFilename, - this.CiEvents[selectedRow].FileVersion, - this.CiEvents[selectedRow].ProductName); + if(selectedRow >= this.CiEvents.Count) + { + return; + } + + // Update the selected row before refreshing the UI this.SelectedRow = selectedRow; + + // Set the UI. Keep the user's current rule type selection and + // repopulate the matching panel for the newly selected row. + ResetCustomRulesPanel(); + RefreshRulePanelForSelectedRow(); } /// @@ -410,8 +411,8 @@ private void ResetCustomRulesPanel() this.versionTextBox.Clear(); this.productTextBox.Clear(); - // Dropdown - this.ruleTypeComboBox.SelectedIndex = 0; + // NOTE: The rule type dropdown is intentionally left unchanged so the + // user's selection persists as they navigate between rows. } @@ -725,6 +726,20 @@ private void RuleTypeChanged(object sender, EventArgs e) // Path // File Attributes // File Hash + RefreshRulePanelForSelectedRow(); + } + + /// + /// Populates the rule details panel for the currently selected row based + /// on the rule type currently chosen in the dropdown. The dropdown + /// selection is intentionally preserved as the user navigates rows. + /// + private void RefreshRulePanelForSelectedRow() + { + if (this.SelectedRow < 0 || this.SelectedRow >= this.CiEvents.Count) + { + return; + } HideAllPanels(); diff --git a/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs index be107674..92bf979c 100644 --- a/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs @@ -290,7 +290,7 @@ private void InitializeComponent() // dataGridView_Exceptions // this.dataGridView_Exceptions.AllowUserToDeleteRows = false; - this.dataGridView_Exceptions.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.dataGridView_Exceptions.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; this.dataGridView_Exceptions.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dataGridView_Exceptions.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.column_Action, @@ -420,6 +420,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScroll = true; this.BackColor = System.Drawing.Color.White; this.Controls.Add(this.panel_ExceptionRule); this.Name = "Exceptions_Control"; diff --git a/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs b/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs index 82872044..108b7630 100644 --- a/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs +++ b/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs @@ -469,6 +469,10 @@ public void AddException() this.displayObjects.Add(displayObject); this.dataGridView_Exceptions.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.dataGridView_Exceptions); + // Scroll to bottom to see new rule added to list this.dataGridView_Exceptions.FirstDisplayedScrollingRowIndex = this.dataGridView_Exceptions.RowCount - 1; this.ExceptionRule = new PolicyCustomRules(); diff --git a/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs b/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs new file mode 100644 index 00000000..266d1b9f --- /dev/null +++ b/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Windows.Forms; + +namespace WDAC_Wizard +{ + /// + /// Helper utilities for DataGridView layout behavior shared across the wizard pages. + /// + internal static class GridLayoutHelper + { + /// + /// Sizes each column to fit its content. The columns remain user-resizable afterwards + /// because the grid's AutoSizeColumnsMode is left as None. + /// The resize is deferred until the grid has a created handle so that text measurement + /// works correctly (calling it too early, e.g. during Load, is a silent no-op). + /// + /// The DataGridView to resize. + public static void AutoFitColumns(DataGridView grid) + { + if (grid == null) + { + return; + } + + void Resize() + { + if (grid.IsDisposed || grid.ColumnCount == 0) + { + return; + } + + grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); + } + + if (grid.IsHandleCreated) + { + // Defer to the message loop so it runs after the current layout pass completes. + grid.BeginInvoke((MethodInvoker)Resize); + } + else + { + // Handle not created yet (e.g. populated during Load). Resize once it is. + void Handler(object sender, System.EventArgs e) + { + grid.HandleCreated -= Handler; + grid.BeginInvoke((MethodInvoker)Resize); + } + + grid.HandleCreated += Handler; + } + } + } +} diff --git a/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs b/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs index 9e654d47..c70f6748 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs @@ -101,6 +101,7 @@ private void InitializeComponent() label_Info.Tag = "IgnoreDarkMode"; label_Info.Text = "Info Text"; label_Info.Visible = false; + label_Info.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; // // backgroundWorker1 // @@ -164,6 +165,7 @@ private void InitializeComponent() control_Panel.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); control_Panel.Name = "control_Panel"; control_Panel.Size = new System.Drawing.Size(150, 700); + control_Panel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; control_Panel.TabIndex = 30; // // workflow_Label @@ -334,6 +336,7 @@ private void InitializeComponent() settings_Button.Name = "settings_Button"; settings_Button.Size = new System.Drawing.Size(129, 45); settings_Button.TabIndex = 31; + settings_Button.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; settings_Button.Text = " Settings"; settings_Button.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; settings_Button.UseVisualStyleBackColor = false; @@ -346,6 +349,7 @@ private void InitializeComponent() button_Next.Name = "button_Next"; button_Next.Size = new System.Drawing.Size(93, 33); button_Next.TabIndex = 31; + button_Next.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Next.Text = "Next"; button_Next.UseVisualStyleBackColor = true; button_Next.Visible = false; @@ -416,10 +420,12 @@ private void InitializeComponent() Controls.Add(button_Edit); Controls.Add(label_Info); Controls.Add(button_New); - FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; HelpButton = true; Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + MaximizeBox = true; + MinimumSize = new System.Drawing.Size(900, 600); Name = "MainWindow"; StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; Text = "App Control Policy Wizard"; diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 6fe50737..81eb63a6 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -83,9 +83,37 @@ public MainWindow() Helper.LicenseCheck(); } - // ############### - // HEADER CONTROLS - // ############### + // ##################### + // DYNAMIC LAYOUT SUPPORT + // ##################### + + /// + /// Hosts a wizard page (UserControl) so it fills the MainWindow client area and tracks the + /// window as it is resized or maximized. Each page now manages its own responsive layout + /// internally (via WinForms layout containers and Dock/Anchor), so the host only needs to + /// dock the page to fill the available space. + /// + private void RegisterPage(Control page) + { + if (page == null) + { + return; + } + + page.Dock = DockStyle.Fill; + } + + /// + /// Automatically docks any wizard page (UserControl) added to the MainWindow so its content + /// expands and contracts with the host window. + /// + private void MainWindow_ControlAdded(object sender, ControlEventArgs e) + { + if (e.Control is UserControl) + { + RegisterPage(e.Control); + } + } /// /// New policy button selected: User can select either base or suppl policy, @@ -2239,7 +2267,10 @@ private void MainWindow_Load(object sender, EventArgs e) SetControlPanelUI(); // Set UI for the 'Next' Button - SetNextButtonUI(); + SetNextButtonUI(); + + // Dock wizard pages so their content fills and tracks the window as it is resized + this.ControlAdded += MainWindow_ControlAdded; } /// diff --git a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs index 30066395..e008ae49 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs @@ -170,6 +170,7 @@ private void InitializeComponent() // AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + AutoScroll = true; Controls.Add(label_Error); Controls.Add(button_RemovePolicy); Controls.Add(button_AddPolicy); diff --git a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs index 89651726..f9810a52 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs @@ -110,6 +110,10 @@ private void Button_AddPolicy_Click(object sender, EventArgs e) this.displayObjects.Add(new DisplayObject(this.nPolicies.ToString(), policyPath)); this.policiesDataGrid.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable + // afterwards since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.policiesDataGrid); + this._MainWindow.Policy.PoliciesToMerge = this.policiesToMerge; if (this.nPolicies >= 2 && !String.IsNullOrEmpty(this.mergePolicyPath)) diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs index 1653d537..21e50982 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs @@ -82,7 +82,7 @@ private void InitializeComponent() // rulesDataGrid // this.rulesDataGrid.AllowUserToDeleteRows = false; - this.rulesDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.rulesDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; @@ -106,13 +106,14 @@ private void InitializeComponent() this.rulesDataGrid.RowTemplate.Height = 24; this.rulesDataGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; this.rulesDataGrid.Size = new System.Drawing.Size(879, 440); + this.rulesDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.rulesDataGrid.TabIndex = 92; this.rulesDataGrid.VirtualMode = true; this.rulesDataGrid.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(this.RulesDataGrid_CellValueNeeded); // // column_Action // - this.column_Action.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_Action.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_Action.HeaderText = "Action"; this.column_Action.MinimumWidth = 6; this.column_Action.Name = "column_Action"; @@ -121,7 +122,7 @@ private void InitializeComponent() // // column_Level // - this.column_Level.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_Level.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_Level.HeaderText = "Level"; this.column_Level.MinimumWidth = 6; this.column_Level.Name = "column_Level"; @@ -130,7 +131,7 @@ private void InitializeComponent() // // Column_Name // - this.Column_Name.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Name.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Name.HeaderText = "Name"; this.Column_Name.MinimumWidth = 6; this.Column_Name.Name = "Column_Name"; @@ -139,7 +140,7 @@ private void InitializeComponent() // // Column_Files // - this.Column_Files.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Files.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Files.HeaderText = "Associated Files"; this.Column_Files.MinimumWidth = 6; this.Column_Files.Name = "Column_Files"; @@ -148,7 +149,7 @@ private void InitializeComponent() // // Column_Exceptions // - this.Column_Exceptions.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Exceptions.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Exceptions.HeaderText = "Exceptions"; this.Column_Exceptions.MinimumWidth = 6; this.Column_Exceptions.Name = "Column_Exceptions"; @@ -157,7 +158,7 @@ private void InitializeComponent() // // column_ID // - this.column_ID.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_ID.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_ID.HeaderText = "Rule ID"; this.column_ID.MinimumWidth = 8; this.column_ID.Name = "column_ID"; @@ -195,6 +196,7 @@ private void InitializeComponent() this.deleteButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.deleteButton.Name = "deleteButton"; this.deleteButton.Size = new System.Drawing.Size(114, 26); + this.deleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.deleteButton.TabIndex = 93; this.deleteButton.Text = "- Remove Rule"; this.deleteButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -211,6 +213,7 @@ private void InitializeComponent() this.label_Error.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.label_Error.Name = "label_Error"; this.label_Error.Size = new System.Drawing.Size(648, 18); + this.label_Error.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label_Error.TabIndex = 96; this.label_Error.Tag = "IgnoreDarkMode"; this.label_Error.Text = "Label_Error: Lorem Ipsum text text text text. Lorum Ipsum text text text text tex" + @@ -224,6 +227,7 @@ private void InitializeComponent() this.addButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.addButton.Name = "addButton"; this.addButton.Size = new System.Drawing.Size(146, 26); + this.addButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.addButton.TabIndex = 97; this.addButton.Text = "+ Add Custom Rule"; this.addButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -236,6 +240,7 @@ private void InitializeComponent() this.checkBox_KernelList.Location = new System.Drawing.Point(163, 634); this.checkBox_KernelList.Name = "checkBox_KernelList"; this.checkBox_KernelList.Size = new System.Drawing.Size(320, 21); + this.checkBox_KernelList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBox_KernelList.TabIndex = 98; this.checkBox_KernelList.Text = "Merge with Recommended Kernel Block Rules"; this.checkBox_KernelList.UseVisualStyleBackColor = true; @@ -248,6 +253,7 @@ private void InitializeComponent() this.checkBox_UserModeList.Location = new System.Drawing.Point(163, 607); this.checkBox_UserModeList.Name = "checkBox_UserModeList"; this.checkBox_UserModeList.Size = new System.Drawing.Size(348, 21); + this.checkBox_UserModeList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBox_UserModeList.TabIndex = 99; this.checkBox_UserModeList.Text = "Merge with Recommended User Mode Block Rules"; this.checkBox_UserModeList.UseVisualStyleBackColor = false; @@ -295,6 +301,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScroll = true; this.BackColor = System.Drawing.Color.White; this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; this.Controls.Add(this.panel_Progress); diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 91a0078b..835e8dfc 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -504,6 +504,10 @@ private void DisplayRules() } } } + + // Size each column to fit its text once all rules are loaded. Columns remain + // user-resizable afterwards since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.rulesDataGrid); } @@ -1213,6 +1217,10 @@ public void AddRuleToTable(string [] displayObjectArray, PolicyCustomRules custo this.displayObjects.Add(new DisplayObject(action, level, name, files, exceptions)); this.rulesDataGrid.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.rulesDataGrid); + // Add custom list to RulesList this.Policy.CustomRules.Add(customRule); From a092ef091be573838c11c8f2aa3e866650ef5e74 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Sun, 31 May 2026 10:31:30 -0400 Subject: [PATCH 7/8] The host was force-docking every UserControl added to the form. I introduced a small marker interface so only intended full-page wizard content is docked --- WDAC-Policy-Wizard/app/src/BuildPage.cs | 2 +- WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs | 2 +- WDAC-Policy-Wizard/app/src/EditWorkflow.cs | 2 +- .../app/src/EventLogRuleConfiguration.cs | 2 +- WDAC-Policy-Wizard/app/src/IWizardPage.cs | 12 ++++++++++++ WDAC-Policy-Wizard/app/src/MainForm.cs | 7 ++++--- WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs | 2 +- WDAC-Policy-Wizard/app/src/PolicyType.cs | 2 +- WDAC-Policy-Wizard/app/src/SettingsPage.cs | 2 +- WDAC-Policy-Wizard/app/src/SigningRules_Control.cs | 2 +- WDAC-Policy-Wizard/app/src/TemplatePage.cs | 2 +- 11 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 WDAC-Policy-Wizard/app/src/IWizardPage.cs diff --git a/WDAC-Policy-Wizard/app/src/BuildPage.cs b/WDAC-Policy-Wizard/app/src/BuildPage.cs index 0b41d04e..d2e9369d 100644 --- a/WDAC-Policy-Wizard/app/src/BuildPage.cs +++ b/WDAC-Policy-Wizard/app/src/BuildPage.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class BuildPage : UserControl + public partial class BuildPage : UserControl, IWizardPage { public string XmlFilePath { get; set; } // File path for the WDAC policy XML file public string BinFilePath { get; set; } // File path for the WDAC policy binary file diff --git a/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs b/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs index 1ed52f4c..e2b8f678 100644 --- a/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs +++ b/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs @@ -14,7 +14,7 @@ namespace WDAC_Wizard { - public partial class ConfigTemplate_Control : UserControl + public partial class ConfigTemplate_Control : UserControl, IWizardPage { public MainWindow _MainWindow; private WDAC_Policy Policy; diff --git a/WDAC-Policy-Wizard/app/src/EditWorkflow.cs b/WDAC-Policy-Wizard/app/src/EditWorkflow.cs index 38c6e9c9..d9fe6063 100644 --- a/WDAC-Policy-Wizard/app/src/EditWorkflow.cs +++ b/WDAC-Policy-Wizard/app/src/EditWorkflow.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard { - public partial class EditWorkflow : UserControl + public partial class EditWorkflow : UserControl, IWizardPage { public string EditPath { get; set; } private int NumberRules; diff --git a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs index 1f4d9f44..c3e9831f 100644 --- a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs +++ b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard { - public partial class EventLogRuleConfiguration : UserControl + public partial class EventLogRuleConfiguration : UserControl, IWizardPage { private List CiEvents; // Declare an ArrayList to serve as the data store. diff --git a/WDAC-Policy-Wizard/app/src/IWizardPage.cs b/WDAC-Policy-Wizard/app/src/IWizardPage.cs new file mode 100644 index 00000000..062e0e9c --- /dev/null +++ b/WDAC-Policy-Wizard/app/src/IWizardPage.cs @@ -0,0 +1,12 @@ +namespace WDAC_Wizard +{ + /// + /// Marker interface implemented by full-page wizard content (UserControls) that the MainWindow + /// hosts and docks to fill its client area. The host uses this marker to dock only intended + /// wizard pages, rather than every UserControl that may be added to the form. Helper or + /// non-page UserControls should NOT implement this interface so they keep their own layout. + /// + public interface IWizardPage + { + } +} diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 81eb63a6..bc1f97af 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -104,12 +104,13 @@ private void RegisterPage(Control page) } /// - /// Automatically docks any wizard page (UserControl) added to the MainWindow so its content - /// expands and contracts with the host window. + /// Automatically docks wizard pages added to the MainWindow so their content expands and + /// contracts with the host window. Only controls marked with are + /// docked, so helper/non-page UserControls keep their own layout and are not forcibly docked. /// private void MainWindow_ControlAdded(object sender, ControlEventArgs e) { - if (e.Control is UserControl) + if (e.Control is IWizardPage) { RegisterPage(e.Control); } diff --git a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs index f9810a52..7e7c2167 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard.src { - public partial class PolicyMerge_Control : UserControl + public partial class PolicyMerge_Control : UserControl, IWizardPage { private int nPolicies; private string mergePolicyPath; diff --git a/WDAC-Policy-Wizard/app/src/PolicyType.cs b/WDAC-Policy-Wizard/app/src/PolicyType.cs index 9f8b4ca0..3e6f2add 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyType.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyType.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class PolicyType : UserControl + public partial class PolicyType : UserControl, IWizardPage { public string BaseToSupplementPath { get; set; } // Path to the supplemental policy on disk diff --git a/WDAC-Policy-Wizard/app/src/SettingsPage.cs b/WDAC-Policy-Wizard/app/src/SettingsPage.cs index d8678559..3a3cdc42 100644 --- a/WDAC-Policy-Wizard/app/src/SettingsPage.cs +++ b/WDAC-Policy-Wizard/app/src/SettingsPage.cs @@ -17,7 +17,7 @@ namespace WDAC_Wizard { - public partial class SettingsPage : UserControl + public partial class SettingsPage : UserControl, IWizardPage { private Dictionary SettingsDict; private MainWindow _MainWindow; diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 835e8dfc..41b1072f 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -16,7 +16,7 @@ namespace WDAC_Wizard { - public partial class SigningRules_Control : UserControl + public partial class SigningRules_Control : UserControl, IWizardPage { // CI Policy objects public WDAC_Policy Policy; diff --git a/WDAC-Policy-Wizard/app/src/TemplatePage.cs b/WDAC-Policy-Wizard/app/src/TemplatePage.cs index 698d0e9b..9ee71f9c 100644 --- a/WDAC-Policy-Wizard/app/src/TemplatePage.cs +++ b/WDAC-Policy-Wizard/app/src/TemplatePage.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class TemplatePage : UserControl + public partial class TemplatePage : UserControl, IWizardPage { // Properties to maintain the policy mode selected private MainWindow _MainWindow; From a478c98a586b19e64234aa56b3f18bc4f8359d40 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Sun, 31 May 2026 10:57:07 -0400 Subject: [PATCH 8/8] =?UTF-8?q?=E2=80=A2=20Added=20RulesDataGrid=5FColumnH?= =?UTF-8?q?eaderMouseClick(object,=20DataGridViewCellMouseEventArgs),=20wh?= =?UTF-8?q?ich=20sorts=20the=20displayObjects=20data=20store=20by=20the=20?= =?UTF-8?q?clicked=20column=20and=20toggles=20ascending/descending=20on=20?= =?UTF-8?q?repeated=20clicks=20(matching=20the=20event=20grid=20behavior).?= =?UTF-8?q?=20=E2=80=A2=20Critically,=20because=20in-session=20custom=20ru?= =?UTF-8?q?les=20are=20linked=20to=20grid=20rows=20via=20RowNumber,=20the?= =?UTF-8?q?=20handler=20remaps=20those=20row=20references=20after=20sortin?= =?UTF-8?q?g=20so=20rule=20deletion/editing=20still=20targets=20the=20corr?= =?UTF-8?q?ect=20rows.=20=E2=80=A2=20Added=20GetDisplayObjectComparison(st?= =?UTF-8?q?ring)=20and=20a=20CompareDisplayStrings(string,=20string)=20hel?= =?UTF-8?q?per=20that=20does=20case-insensitive=20comparison=20and=20pushe?= =?UTF-8?q?s=20empty/null=20values=20to=20the=20bottom=20(same=20conventio?= =?UTF-8?q?n=20as=20the=20event=20grid's=20SortDisplayObjects(int,=20bool)?= =?UTF-8?q?).=20=E2=80=A2=20Added=20a=20sort=20glyph=20(=E2=96=B2/?= =?UTF-8?q?=E2=96=BC)=20on=20the=20active=20column=20header=20for=20visual?= =?UTF-8?q?=20feedback.=20The=20existing=20auto-fit=20sizing,=20virtual-mo?= =?UTF-8?q?de=20binding,=20and=20resize/docking=20behavior=20are=20all=20p?= =?UTF-8?q?reserved.=20You=20can=20now=20click=20any=20column=20header=20i?= =?UTF-8?q?n=20the=20File=20Rules=20grid=20to=20sort,=20and=20click=20agai?= =?UTF-8?q?n=20to=20reverse=20the=20order.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/SigningRules_Control.Designer.cs | 7 + .../app/src/SigningRules_Control.cs | 133 ++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs index 21e50982..bd985246 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs @@ -110,6 +110,7 @@ private void InitializeComponent() this.rulesDataGrid.TabIndex = 92; this.rulesDataGrid.VirtualMode = true; this.rulesDataGrid.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(this.RulesDataGrid_CellValueNeeded); + this.rulesDataGrid.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.RulesDataGrid_ColumnHeaderMouseClick); // // column_Action // @@ -118,6 +119,7 @@ private void InitializeComponent() this.column_Action.MinimumWidth = 6; this.column_Action.Name = "column_Action"; this.column_Action.ReadOnly = true; + this.column_Action.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_Action.Width = 76; // // column_Level @@ -127,6 +129,7 @@ private void InitializeComponent() this.column_Level.MinimumWidth = 6; this.column_Level.Name = "column_Level"; this.column_Level.ReadOnly = true; + this.column_Level.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_Level.Width = 71; // // Column_Name @@ -136,6 +139,7 @@ private void InitializeComponent() this.Column_Name.MinimumWidth = 6; this.Column_Name.Name = "Column_Name"; this.Column_Name.ReadOnly = true; + this.Column_Name.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Name.Width = 74; // // Column_Files @@ -145,6 +149,7 @@ private void InitializeComponent() this.Column_Files.MinimumWidth = 6; this.Column_Files.Name = "Column_Files"; this.Column_Files.ReadOnly = true; + this.Column_Files.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Files.Width = 127; // // Column_Exceptions @@ -154,6 +159,7 @@ private void InitializeComponent() this.Column_Exceptions.MinimumWidth = 6; this.Column_Exceptions.Name = "Column_Exceptions"; this.Column_Exceptions.ReadOnly = true; + this.Column_Exceptions.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Exceptions.Width = 105; // // column_ID @@ -163,6 +169,7 @@ private void InitializeComponent() this.column_ID.MinimumWidth = 8; this.column_ID.Name = "column_ID"; this.column_ID.ReadOnly = true; + this.column_ID.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_ID.Width = 77; // // label8 diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 41b1072f..955f9874 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -1141,6 +1141,139 @@ private void RemoveRuleIdFromFileAttribs(string ruleId) } } + /// + /// Sorts the rules grid by the clicked column header. Mirrors the event grid behavior: + /// the first click on a column sorts ascending and subsequent clicks toggle the direction. + /// The underlying displayObjects data store and the custom rule RowNumber references are + /// kept in sync so deletion and editing continue to map to the correct rows. + /// + /// + /// + private void RulesDataGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) + { + if (e.ColumnIndex < 0 || this.displayObjects.Count == 0) + { + return; + } + + string columnName = this.rulesDataGrid.Columns[e.ColumnIndex].Name; + + // Determine sort direction. If the column is already the sorted column, toggle the + // direction. Otherwise sort ascending and clear the sorted tag on the other columns. + bool ascending = true; + if (this.rulesDataGrid.Columns[e.ColumnIndex].Tag as string == "SortedAsc") + { + ascending = false; + } + + // Project the display objects onto a sortable list paired with their original row index + // so the custom rule RowNumber references can be remapped after sorting. + var indexedObjects = new List>(); + for (int i = 0; i < this.displayObjects.Count; i++) + { + indexedObjects.Add(new KeyValuePair(i, (DisplayObject)this.displayObjects[i])); + } + + Comparison comparison = GetDisplayObjectComparison(columnName); + indexedObjects.Sort((x, y) => + { + int result = comparison(x.Value, y.Value); + return ascending ? result : -result; + }); + + // Map each original row index to its new sorted position. + var oldToNewIndex = new Dictionary(); + for (int newIndex = 0; newIndex < indexedObjects.Count; newIndex++) + { + oldToNewIndex[indexedObjects[newIndex].Key] = newIndex; + } + + // Rebuild the display objects data store in the sorted order. + this.displayObjects.Clear(); + foreach (var pair in indexedObjects) + { + this.displayObjects.Add(pair.Value); + } + + // Remap the in-session custom rule row references so deletion still targets the right row. + if (this.Policy != null && this.Policy.CustomRules != null) + { + foreach (var customRule in this.Policy.CustomRules) + { + if (oldToNewIndex.TryGetValue(customRule.RowNumber, out int newRowNumber)) + { + customRule.RowNumber = newRowNumber; + } + } + } + + // Update the sorted tags so the next click toggles the direction. + foreach (DataGridViewColumn column in this.rulesDataGrid.Columns) + { + column.Tag = null; + column.HeaderCell.SortGlyphDirection = SortOrder.None; + } + + this.rulesDataGrid.Columns[e.ColumnIndex].Tag = ascending ? "SortedAsc" : "SortedDesc"; + this.rulesDataGrid.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = + ascending ? SortOrder.Ascending : SortOrder.Descending; + + // Repaint the virtual-mode grid with the newly ordered data store. + this.rulesDataGrid.Refresh(); + } + + /// + /// Returns the comparison delegate used to sort the display objects for the given column. + /// Empty/null values are sorted to the bottom for ascending sorts. + /// + /// + /// + private Comparison GetDisplayObjectComparison(string columnName) + { + switch (columnName) + { + case "column_Action": + return (x, y) => CompareDisplayStrings(x.Action, y.Action); + + case "column_Level": + return (x, y) => CompareDisplayStrings(x.Level, y.Level); + + case "Column_Name": + return (x, y) => CompareDisplayStrings(x.Name, y.Name); + + case "Column_Files": + return (x, y) => CompareDisplayStrings(x.Files, y.Files); + + case "Column_Exceptions": + return (x, y) => CompareDisplayStrings(x.Exceptions, y.Exceptions); + + case "column_ID": + return (x, y) => CompareDisplayStrings(x.Id, y.Id); + + default: + return (x, y) => 0; + } + } + + /// + /// Case-insensitive string comparison that sorts empty/null values to the bottom. + /// + /// + /// + /// + private static int CompareDisplayStrings(string x, string y) + { + bool xEmpty = string.IsNullOrWhiteSpace(x); + bool yEmpty = string.IsNullOrWhiteSpace(y); + + // Sort empty/null strings to the bottom + if (xEmpty && yEmpty) return 0; + if (xEmpty) return 1; + if (yEmpty) return -1; + + return string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase); + } + /// /// Sets the display object when the DataGridView needed to paint data ///