From 162dd8803751610b1c8cedab6bf3abca2dc2e8d8 Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Mon, 4 May 2026 17:44:43 -0400 Subject: [PATCH 1/6] =?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/6] 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/6] 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/6] =?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/6] 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 071754777c7378efa39f7a83474c9a287a1cc58b Mon Sep 17 00:00:00 2001 From: Chris LaPlante Date: Sun, 31 May 2026 11:33:59 -0400 Subject: [PATCH 6/6] 1. Added two layout constants (DefaultSliderBoxX = 139, FileAttributeSliderBoxX = 165) plus a fixed right edge so the boxes stay inside the panel. 2. Added a small helper, OffsetSliderValueBoxes(int xLocation), that shifts the value boxes' left edge and adjusts their width (keeping the right edge fixed). Auto expand when making window bigger. --- .../src/CustomRuleConditionsPanel.Designer.cs | 9 +++ .../app/src/CustomRuleConditionsPanel.cs | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs index 9e3a9c24..47a0e1d4 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.Designer.cs @@ -693,16 +693,19 @@ private void InitializeComponent() panel_Publisher_Scroll.Controls.Add(textBoxSlider_4); panel_Publisher_Scroll.Controls.Add(textBoxSlider_1); panel_Publisher_Scroll.Controls.Add(textBoxSlider_0); + panel_Publisher_Scroll.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; panel_Publisher_Scroll.Location = new System.Drawing.Point(4, 389); panel_Publisher_Scroll.Margin = new System.Windows.Forms.Padding(2); panel_Publisher_Scroll.Name = "panel_Publisher_Scroll"; panel_Publisher_Scroll.Size = new System.Drawing.Size(510, 309); panel_Publisher_Scroll.TabIndex = 103; panel_Publisher_Scroll.Visible = false; + panel_Publisher_Scroll.Resize += Panel_Publisher_Scroll_Resize; // // textBoxSlider_2 // textBoxSlider_2.BackColor = System.Drawing.SystemColors.Control; + textBoxSlider_2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxSlider_2.Enabled = false; textBoxSlider_2.Font = new System.Drawing.Font("Tahoma", 9F); textBoxSlider_2.Location = new System.Drawing.Point(139, 100); @@ -776,6 +779,7 @@ private void InitializeComponent() // textBoxEKU // textBoxEKU.BackColor = System.Drawing.SystemColors.Control; + textBoxEKU.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxEKU.Enabled = false; textBoxEKU.Font = new System.Drawing.Font("Tahoma", 9F); textBoxEKU.Location = new System.Drawing.Point(139, 239); @@ -825,6 +829,7 @@ private void InitializeComponent() // textBox_MaxVersion // textBox_MaxVersion.BackColor = System.Drawing.SystemColors.Control; + textBox_MaxVersion.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBox_MaxVersion.Enabled = false; textBox_MaxVersion.Font = new System.Drawing.Font("Tahoma", 9F); textBox_MaxVersion.ForeColor = System.Drawing.SystemColors.WindowText; @@ -841,6 +846,7 @@ private void InitializeComponent() // textBoxSlider_3 // textBoxSlider_3.BackColor = System.Drawing.SystemColors.Control; + textBoxSlider_3.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxSlider_3.Enabled = false; textBoxSlider_3.Font = new System.Drawing.Font("Tahoma", 9F); textBoxSlider_3.Location = new System.Drawing.Point(139, 142); @@ -854,6 +860,7 @@ private void InitializeComponent() // textBoxSlider_4 // textBoxSlider_4.BackColor = System.Drawing.SystemColors.Control; + textBoxSlider_4.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxSlider_4.Enabled = false; textBoxSlider_4.Font = new System.Drawing.Font("Tahoma", 9F); textBoxSlider_4.Location = new System.Drawing.Point(139, 184); @@ -867,6 +874,7 @@ private void InitializeComponent() // textBoxSlider_1 // textBoxSlider_1.BackColor = System.Drawing.SystemColors.Control; + textBoxSlider_1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxSlider_1.Enabled = false; textBoxSlider_1.Font = new System.Drawing.Font("Tahoma", 9F); textBoxSlider_1.Location = new System.Drawing.Point(139, 58); @@ -880,6 +888,7 @@ private void InitializeComponent() // textBoxSlider_0 // textBoxSlider_0.BackColor = System.Drawing.SystemColors.Control; + textBoxSlider_0.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; textBoxSlider_0.Enabled = false; textBoxSlider_0.Font = new System.Drawing.Font("Tahoma", 9F); textBoxSlider_0.Location = new System.Drawing.Point(139, 16); diff --git a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs index 1d44c930..9403b259 100644 --- a/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs +++ b/WDAC-Policy-Wizard/app/src/CustomRuleConditionsPanel.cs @@ -36,6 +36,24 @@ public partial class CustomRuleConditionsPanel : Form private string PrevComText = String.Empty; private bool IgnoreInput = false; + // X-coordinate of the slider value boxes (textBoxSlider_*) for the different rule types. + // File Attribute labels (e.g. "Original filename:") are wider than the Publisher labels, + // so the value boxes need to be shifted right to avoid overlapping the labels. + private const int DefaultSliderBoxX = 139; + private const int FileAttributeSliderBoxX = 165; + + // Padding kept between the right edge of the value boxes and the panel edge so the + // boxes never run off the right side of the window. + private const int SliderBoxRightPadding = 14; + + // Upper bound on the value box width so the boxes don't become unreasonably long on + // very wide windows. + private const int SliderBoxMaxWidth = 700; + + // Tracks the current left edge of the value boxes so the layout can be recomputed when + // the panel (and therefore the window) is resized. + private int currentSliderBoxX = DefaultSliderBoxX; + private enum UIState { RuleConditions = 0, @@ -1066,6 +1084,50 @@ private void SetFileSignerInfo(string refPath) this.PolicyCustomRule.SupportedCrypto = true; } + /// + /// Moves the slider value boxes (textBoxSlider_*) to the supplied X-coordinate so that wider + /// labels (e.g. File Attribute "Original filename:") do not overlap the value boxes. The right + /// edge is kept fixed so the boxes remain within the panel. + /// + /// The new X-coordinate for the left edge of the value boxes. + private void OffsetSliderValueBoxes(int xLocation) + { + this.currentSliderBoxX = xLocation; + + TextBox[] fullWidthBoxes = { this.textBoxSlider_0, this.textBoxSlider_1, this.textBoxSlider_2, this.textBoxSlider_3 }; + + // Expand the boxes to fill the panel, leaving padding on the right so they never run + // off the edge of the window. Cap the width so the boxes don't get too long. + int availableWidth = this.panel_Publisher_Scroll.ClientSize.Width - xLocation - SliderBoxRightPadding; + int newWidth = Math.Min(availableWidth, SliderBoxMaxWidth); + if (newWidth < 100) + { + newWidth = 100; // sanity floor + } + + foreach (TextBox box in fullWidthBoxes) + { + box.Location = new Point(xLocation, box.Location.Y); + box.Size = new Size(newWidth, box.Size.Height); + } + + // textBoxEKU spans the full width like the attribute boxes + this.textBoxEKU.Location = new Point(xLocation, this.textBoxEKU.Location.Y); + this.textBoxEKU.Size = new Size(newWidth, this.textBoxEKU.Size.Height); + + // textBoxSlider_4 (version box) keeps its own width but follows the same left edge + this.textBoxSlider_4.Location = new Point(xLocation, this.textBoxSlider_4.Location.Y); + } + + /// + /// Recomputes the slider value box layout when the panel is resized so the boxes grow + /// and shrink with the window width. + /// + private void Panel_Publisher_Scroll_Resize(object sender, EventArgs e) + { + OffsetSliderValueBoxes(this.currentSliderBoxX); + } + /// /// Sets the default state of the textboxes and checkboxes based on the rule type /// @@ -1137,6 +1199,9 @@ private void SetDefaultUIState(PolicyCustomRules.RuleType ruleType) this.checkBoxAttribute3.Text = "File name:"; this.checkBoxAttribute4.Text = "Min. Version:"; + // Publisher labels are narrow; restore the value boxes to their default position + OffsetSliderValueBoxes(DefaultSliderBoxX); + // Version textbox should be set to normal size this.textBoxSlider_4.Size = this.textBoxSlider_3.Size; @@ -1218,6 +1283,10 @@ private void SetDefaultUIState(PolicyCustomRules.RuleType ruleType) this.checkBoxAttribute2.Text = "Product name:"; this.checkBoxAttribute3.Text = "Internal name:"; + // The File Attribute labels (e.g. "Original filename:") are wider than the + // Publisher labels, so shift the value boxes right to avoid overlapping the labels + OffsetSliderValueBoxes(FileAttributeSliderBoxX); + // Set checkbox states to all disabled -- allow user to select the ones desired this.checkBoxAttribute0.Checked = false; this.checkBoxAttribute1.Checked = false;