Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
326eeacfdf Fix: Prevent backup directory creation during dry runs
Co-authored-by: yeelam-gordon <73506701+yeelam-gordon@users.noreply.github.com>
2025-08-29 05:54:09 +00:00
copilot-swe-agent[bot]
46b81144c5 Initial plan 2025-08-29 05:44:56 +00:00
13 changed files with 85 additions and 559 deletions

View File

@@ -372,17 +372,20 @@ namespace UITests_FancyZones
// launch FancyZones settings page
private void LaunchFancyZones()
{
this.Find<NavigationViewItem>(By.AccessibilityId("WindowingAndLayoutsNavItem")).Click();
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
{
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
}
this.Find<NavigationViewItem>(By.AccessibilityId("FancyZonesNavItem")).Click();
this.Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
this.Find<NavigationViewItem>("FancyZones").Click();
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
this.Session.SetMainWindowSize(WindowSize.Large);
Find<Element>(By.AccessibilityId("HeaderPresenter")).Click();
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
ZoneBehaviourSettings(TestContext.TestName);
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 500, 10000);
this.Session.Attach(PowerToysModule.FancyZone);
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
@@ -400,7 +403,7 @@ namespace UITests_FancyZones
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
this.Session.Attach(PowerToysModule.PowerToysSettings);
SetupCustomLayouts();
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 5000, 5000);
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
this.Session.Attach(PowerToysModule.FancyZone);
this.Find<Microsoft.PowerToys.UITest.Button>("Maximize").Click();

View File

@@ -653,11 +653,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
return (false, "General_SettingsBackupAndRestore_InvalidBackupLocation", "Error", lastBackupExists, "\n" + appBasePath);
}
var dirExists = TryCreateDirectory(settingsBackupAndRestoreDir);
if (!dirExists)
// Only create the backup directory if this is not a dry run
if (!dryRun)
{
Logger.LogError($"Failed to create dir {settingsBackupAndRestoreDir}");
return (false, $"General_SettingsBackupAndRestore_BackupError", "Error", lastBackupExists, "\n" + settingsBackupAndRestoreDir);
var dirExists = TryCreateDirectory(settingsBackupAndRestoreDir);
if (!dirExists)
{
Logger.LogError($"Failed to create dir {settingsBackupAndRestoreDir}");
return (false, $"General_SettingsBackupAndRestore_BackupError", "Error", lastBackupExists, "\n" + settingsBackupAndRestoreDir);
}
}
// get data needed for process
@@ -717,12 +721,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
var relativePath = currentFile.Value.Substring(appBasePath.Length + 1);
var backupFullPath = Path.Combine(fullBackupDir, relativePath);
TryCreateDirectory(fullBackupDir);
TryCreateDirectory(Path.GetDirectoryName(backupFullPath));
Logger.LogInfo($"BackupSettings writing, {backupFullPath}, dryRun:{dryRun}.");
if (!dryRun)
{
TryCreateDirectory(fullBackupDir);
TryCreateDirectory(Path.GetDirectoryName(backupFullPath));
File.WriteAllText(backupFullPath, currentSettingsFileToBackup);
}
}

View File

@@ -6,8 +6,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
@@ -16,7 +14,6 @@ namespace Microsoft.PowerToys.Settings.UI.Services
public class GlobalHotkeyConflictManager
{
private readonly Func<string, int> _sendIPCMessage;
private GeneralSettings _generalSettings;
private static GlobalHotkeyConflictManager _instance;
private AllHotkeyConflictsData _currentConflicts = new AllHotkeyConflictsData();
@@ -37,11 +34,6 @@ namespace Microsoft.PowerToys.Settings.UI.Services
public event EventHandler<AllHotkeyConflictsEventArgs> ConflictsUpdated;
public void UpdateGeneralSettings(GeneralSettings generalSettings)
{
_generalSettings = generalSettings;
}
public void RequestAllConflicts()
{
var requestMessage = "{\"get_all_hotkey_conflicts\":{}}";
@@ -50,76 +42,8 @@ namespace Microsoft.PowerToys.Settings.UI.Services
private void OnAllHotkeyConflictsReceived(object sender, AllHotkeyConflictsEventArgs e)
{
_currentConflicts = FilterConflictsForDisabledModules(e.Conflicts);
ConflictsUpdated?.Invoke(this, new AllHotkeyConflictsEventArgs { Conflicts = _currentConflicts });
}
private AllHotkeyConflictsData FilterConflictsForDisabledModules(AllHotkeyConflictsData conflicts)
{
if (_generalSettings?.Enabled == null)
{
return conflicts;
}
var filteredConflicts = new AllHotkeyConflictsData();
// Filter InAppConflicts
if (conflicts.InAppConflicts != null)
{
filteredConflicts.InAppConflicts = new List<HotkeyConflictGroupData>();
foreach (var conflictGroup in conflicts.InAppConflicts)
{
var enabledModules = conflictGroup.Modules
.Where(module => IsModuleEnabled(module.ModuleType))
.ToList();
// Only include conflict groups that have conflicts between enabled modules
if (enabledModules.Count > 1)
{
filteredConflicts.InAppConflicts.Add(new HotkeyConflictGroupData
{
Hotkey = conflictGroup.Hotkey,
IsSystemConflict = conflictGroup.IsSystemConflict,
Modules = enabledModules
});
}
}
}
// System conflicts should always be included regardless of module state
if (conflicts.SystemConflicts != null)
{
filteredConflicts.SystemConflicts = new List<HotkeyConflictGroupData>();
foreach (var conflictGroup in conflicts.SystemConflicts)
{
var enabledModules = conflictGroup.Modules
.Where(module => IsModuleEnabled(module.ModuleType))
.ToList();
// Include system conflicts if there are any enabled modules involved
if (enabledModules.Count > 0)
{
filteredConflicts.SystemConflicts.Add(new HotkeyConflictGroupData
{
Hotkey = conflictGroup.Hotkey,
IsSystemConflict = conflictGroup.IsSystemConflict,
Modules = enabledModules
});
}
}
}
return filteredConflicts;
}
private bool IsModuleEnabled(ModuleType moduleType)
{
if (_generalSettings?.Enabled == null)
{
return true; // If we can't determine, assume enabled to be safe
}
return ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
_currentConflicts = e.Conflicts;
ConflictsUpdated?.Invoke(this, e);
}
public bool HasConflictForHotkey(HotkeySettings hotkey, string moduleName, int hotkeyID)

View File

@@ -19,11 +19,7 @@
x:Uid="FancyZones_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch
x:Name="EnableFancyZonesToggleSwitch"
x:Uid="ToggleSwitch"
AutomationProperties.AutomationId="EnableFancyZonesToggleSwitch"
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"

View File

@@ -146,17 +146,11 @@
</NavigationView.Resources>
<NavigationView.MenuItems>
<NavigationViewItem
x:Name="DashboardNavigationItem"
x:Uid="Shell_Dashboard"
helpers:NavHelper.NavigateTo="views:DashboardPage"
AutomationProperties.AutomationId="DashboardNavItem"
Icon="{ui:FontIcon Glyph=&#xE80F;}" />
<NavigationViewItem
x:Name="GeneralNavigationItem"
x:Uid="Shell_General"
helpers:NavHelper.NavigateTo="views:GeneralPage"
AutomationProperties.AutomationId="GeneralNavItem">
<NavigationViewItem x:Uid="Shell_General" helpers:NavHelper.NavigateTo="views:GeneralPage">
<NavigationViewItem.Icon>
<AnimatedIcon>
<AnimatedIcon.Source>
@@ -172,220 +166,156 @@
<!-- System Tools -->
<NavigationViewItem
x:Name="SystemToolsNavigationItem"
x:Uid="Shell_TopLevelSystemTools"
AutomationProperties.AutomationId="SystemToolsNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/SystemTools.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="AdvancedPasteNavigationItem"
x:Uid="Shell_AdvancedPaste"
helpers:NavHelper.NavigateTo="views:AdvancedPastePage"
AutomationProperties.AutomationId="AdvancedPasteNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}" />
<NavigationViewItem
x:Name="AwakeNavigationItem"
x:Uid="Shell_Awake"
helpers:NavHelper.NavigateTo="views:AwakePage"
AutomationProperties.AutomationId="AwakeNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}" />
<NavigationViewItem
x:Name="CmdPalNavigationItem"
x:Uid="Shell_CmdPal"
helpers:NavHelper.NavigateTo="views:CmdPalPage"
AutomationProperties.AutomationId="CmdPalNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}" />
<NavigationViewItem
x:Name="ColorPickerNavigationItem"
x:Uid="Shell_ColorPicker"
helpers:NavHelper.NavigateTo="views:ColorPickerPage"
AutomationProperties.AutomationId="ColorPickerNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}" />
<NavigationViewItem
x:Name="PowerLauncherNavigationItem"
x:Uid="Shell_PowerLauncher"
helpers:NavHelper.NavigateTo="views:PowerLauncherPage"
AutomationProperties.AutomationId="PowerLauncherNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerToysRun.png}" />
<NavigationViewItem
x:Name="MeasureToolNavigationItem"
x:Uid="Shell_MeasureTool"
helpers:NavHelper.NavigateTo="views:MeasureToolPage"
AutomationProperties.AutomationId="MeasureToolNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}" />
<NavigationViewItem
x:Name="ShortcutGuideNavigationItem"
x:Uid="Shell_ShortcutGuide"
helpers:NavHelper.NavigateTo="views:ShortcutGuidePage"
AutomationProperties.AutomationId="ShortcutGuideNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}" />
<NavigationViewItem
x:Name="TextExtractorNavigationItem"
x:Uid="Shell_TextExtractor"
helpers:NavHelper.NavigateTo="views:PowerOcrPage"
AutomationProperties.AutomationId="TextExtractorNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/TextExtractor.png}" />
<NavigationViewItem
x:Name="ZoomItNavigationItem"
x:Uid="Shell_ZoomIt"
helpers:NavHelper.NavigateTo="views:ZoomItPage"
AutomationProperties.AutomationId="ZoomItNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ZoomIt.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>
<!-- Windowing & Layouts -->
<NavigationViewItem
x:Name="WindowingAndLayoutsNavigationItem"
x:Uid="Shell_TopLevelWindowsAndLayouts "
AutomationProperties.AutomationId="WindowingAndLayoutsNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/WindowingAndLayouts.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="AlwaysOnTopNavigationItem"
x:Uid="Shell_AlwaysOnTop"
helpers:NavHelper.NavigateTo="views:AlwaysOnTopPage"
AutomationProperties.AutomationId="AlwaysOnTopNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}" />
<NavigationViewItem
x:Name="CropAndLockNavigationItem"
x:Uid="Shell_CropAndLock"
helpers:NavHelper.NavigateTo="views:CropAndLockPage"
AutomationProperties.AutomationId="CropAndLockNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}" />
<NavigationViewItem
x:Name="FancyZonesNavigationItem"
x:Uid="Shell_FancyZones"
helpers:NavHelper.NavigateTo="views:FancyZonesPage"
AutomationProperties.AutomationId="FancyZonesNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}" />
<NavigationViewItem
x:Name="WorkspacesNavigationItem"
x:Uid="Shell_Workspaces"
helpers:NavHelper.NavigateTo="views:WorkspacesPage"
AutomationProperties.AutomationId="WorkspacesNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>
<!-- Input / Output -->
<NavigationViewItem
x:Name="InputOutputNavigationItem"
x:Uid="Shell_TopLevelInputOutput"
AutomationProperties.AutomationId="InputOutputNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="KeyboardManagerNavigationItem"
x:Uid="Shell_KeyboardManager"
helpers:NavHelper.NavigateTo="views:KeyboardManagerPage"
AutomationProperties.AutomationId="KeyboardManagerNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}" />
<!-- Find my mouse -->
<!-- Mouse Highlighter -->
<NavigationViewItem
x:Name="MouseUtilitiesNavigationItem"
x:Uid="Shell_MouseUtilities"
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
<NavigationViewItem
x:Name="MouseWithoutBordersNavigationItem"
x:Uid="Shell_MouseWithoutBorders"
helpers:NavHelper.NavigateTo="views:MouseWithoutBordersPage"
AutomationProperties.AutomationId="MouseWithoutBordersNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}" />
<NavigationViewItem
x:Name="QuickAccentNavigationItem"
x:Uid="Shell_QuickAccent"
helpers:NavHelper.NavigateTo="views:PowerAccentPage"
AutomationProperties.AutomationId="QuickAccentNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/QuickAccent.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>
<!-- File Management -->
<NavigationViewItem
x:Name="FileManagementNavigationItem"
x:Uid="Shell_TopLevelFileManagement"
AutomationProperties.AutomationId="FileManagementNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileManagement.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="PowerPreviewNavigationItem"
x:Uid="Shell_PowerPreview"
helpers:NavHelper.NavigateTo="views:PowerPreviewPage"
AutomationProperties.AutomationId="PowerPreviewNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileExplorerPreview.png}" />
<!-- File Explorer Thumbnails -->
<NavigationViewItem
x:Name="FileLocksmithNavigationItem"
x:Uid="Shell_FileLocksmith"
helpers:NavHelper.NavigateTo="views:FileLocksmithPage"
AutomationProperties.AutomationId="FileLocksmithNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}" />
<NavigationViewItem
x:Name="ImageResizerNavigationItem"
x:Uid="Shell_ImageResizer"
helpers:NavHelper.NavigateTo="views:ImageResizerPage"
AutomationProperties.AutomationId="ImageResizerNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}" />
<NavigationViewItem
x:Name="NewPlusNavigationItem"
x:Uid="NewPlus_Product_Name"
helpers:NavHelper.NavigateTo="views:NewPlusPage"
AutomationProperties.AutomationId="NewPlusNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}" />
<NavigationViewItem
x:Name="PeekNavigationItem"
x:Uid="Shell_Peek"
helpers:NavHelper.NavigateTo="views:PeekPage"
AutomationProperties.AutomationId="PeekNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Peek.png}" />
<NavigationViewItem
x:Name="PowerRenameNavigationItem"
x:Uid="Shell_PowerRename"
helpers:NavHelper.NavigateTo="views:PowerRenamePage"
AutomationProperties.AutomationId="PowerRenameNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>
<!-- Advanced -->
<NavigationViewItem
x:Name="AdvancedNavigationItem"
x:Uid="Shell_TopLevelAdvanced"
AutomationProperties.AutomationId="AdvancedNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Advanced.png}"
SelectsOnInvoked="False">
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="CmdNotFoundNavigationItem"
x:Uid="Shell_CmdNotFound"
helpers:NavHelper.NavigateTo="views:CmdNotFoundPage"
AutomationProperties.AutomationId="CmdNotFoundNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CommandNotFound.png}" />
<NavigationViewItem
x:Name="EnvironmentVariablesNavigationItem"
x:Uid="Shell_EnvironmentVariables"
helpers:NavHelper.NavigateTo="views:EnvironmentVariablesPage"
AutomationProperties.AutomationId="EnvironmentVariablesNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}" />
<NavigationViewItem
x:Name="HostsNavigationItem"
x:Uid="Shell_Hosts"
helpers:NavHelper.NavigateTo="views:HostsPage"
AutomationProperties.AutomationId="HostsNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}" />
<NavigationViewItem
x:Name="RegistryPreviewNavigationItem"
x:Uid="Shell_RegistryPreview"
helpers:NavHelper.NavigateTo="views:RegistryPreviewPage"
AutomationProperties.AutomationId="RegistryPreviewNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/RegistryPreview.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>
@@ -393,27 +323,19 @@
<NavigationView.PaneFooter>
<StackPanel Orientation="Vertical">
<NavigationViewItem
x:Name="OOBENavigationItem"
x:Uid="OOBE_NavViewItem"
AutomationProperties.AutomationId="OOBENavItem"
Icon="{ui:FontIcon Glyph=&#xF133;}"
Tapped="OOBEItem_Tapped" />
<NavigationViewItem
x:Name="WhatIsNewNavigationItem"
x:Uid="WhatIsNew_NavViewItem"
AutomationProperties.AutomationId="WhatIsNewNavItem"
Icon="{ui:FontIcon Glyph=&#xE789;}"
Tapped="WhatIsNewItem_Tapped" />
<NavigationViewItem
x:Name="FeedbackNavigationItem"
x:Uid="Feedback_NavViewItem"
AutomationProperties.AutomationId="FeedbackNavItem"
Icon="{ui:FontIcon Glyph=&#xED15;}"
Tapped="FeedbackItem_Tapped" />
<NavigationViewItem
x:Name="CloseNavigationItem"
x:Uid="Close_NavViewItem"
AutomationProperties.AutomationId="CloseNavItem"
Icon="{ui:FontIcon Glyph=&#xE7E8;}"
Tapped="Close_Tapped"
Visibility="{x:Bind ViewModel.ShowCloseMenu, Mode=OneWay}" />

View File

@@ -76,9 +76,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
// Update the GlobalHotkeyConflictManager with current settings
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
AddDashboardListItem(moduleType);
@@ -129,9 +126,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value);
}
// Update the GlobalHotkeyConflictManager with updated settings
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
// Request updated conflicts after module state change
RequestConflictData();
}
@@ -144,9 +138,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(ShortcutModules));
// Update the GlobalHotkeyConflictManager with updated settings
GlobalHotkeyConflictManager.Instance?.UpdateGeneralSettings(generalSettingsConfig);
// Request updated conflicts after module state change
RequestConflictData();
}

View File

@@ -1,21 +0,0 @@
# Build scripts - quick guideline
As the result of our recent changes, use the following guidance when working in the PowerToys repo:
1. Use `build-essentials.ps1` before any development in general
- Purpose: restore NuGet packages for the full solution and build a small set of essential native projects (runner, settings). This is a fast way to ensure native artifacts required for local development are available.
2. Use `build.ps1` from any folder
- Purpose: lightweight local builder. It auto-discovers the target platform (x64/arm64/x86) and builds projects it finds in the current directory.
- Notes: you can pass additional MSBuild arguments (e.g. `./tools/build/build.ps1 '/p:CIBuild=true'`) — the script will forward them to MSBuild.
- Use `-RestoreOnly` to only restore packages for local projects.
3. Use `build-installer.ps1` to create a local installer (use with caution)
- Purpose: runs the full pipeline that restores, builds the full solution, signs packages, and builds the installer (MSI/bootstrapper).
- Caution: this script performs cleaning (git clean) and installer packaging steps that may remove untracked files under `installer/`.
Additional notes
- Shared helpers live in `build-common.ps1` and are used by the other scripts (`RunMSBuild`, `RestoreThenBuild`, `BuildProjectsInDirectory`, platform auto-detection).
- If you want a different default platform selection, set the `-Platform` parameter explicitly when invoking the scripts.
If you want, I can add this guidance to the repository README instead or add a short one-liner comment to the top of `build-common.ps1` so tools can discover it automatically.

View File

@@ -1,166 +0,0 @@
<#
.SYNOPSIS
Shared build helper functions for PowerToys build scripts.
.DESCRIPTION
This file provides reusable helper functions used by the build scripts:
- Get-BuildPaths: returns ScriptDir, OriginalCwd, RepoRoot (repo root detection)
- RunMSBuild: wrapper around msbuild.exe (accepts optional Platform/Configuration)
- RestoreThenBuild: performs restore and optionally builds the solution/project
- BuildProjectsInDirectory: discovers and builds local .sln/.csproj/.vcxproj files
USAGE
Dot-source this file from a script to load helpers:
. "$PSScriptRoot\build-common.ps1"
.NOTES
Do not execute this file directly; dot-source it from `build.ps1` or `build-installer.ps1` so helpers are available in your script scope.
#>
function RunMSBuild {
param (
[string]$Solution,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration
)
# Prefer the solution's folder for logs; fall back to current directory
$logRoot = Split-Path -Path $Solution
if (-not $logRoot) { $logRoot = '.' }
$cfg = $null
if ($Configuration) { $cfg = $Configuration.ToLower() } else { $cfg = 'unknown' }
$plat = $null
if ($Platform) { $plat = $Platform.ToLower() } else { $plat = 'unknown' }
$allLog = Join-Path $logRoot ("build.{0}.{1}.all.log" -f $cfg, $plat)
$warningLog = Join-Path $logRoot ("build.{0}.{1}.warnings.log" -f $cfg, $plat)
$errorsLog = Join-Path $logRoot ("build.{0}.{1}.errors.log" -f $cfg, $plat)
$binLog = Join-Path $logRoot ("build.{0}.{1}.trace.binlog" -f $cfg, $plat)
$base = @(
$Solution
"/p:Platform=$Platform"
"/p:Configuration=$Configuration"
"/verbosity:normal"
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
"/fileLoggerParameters:LogFile=$allLog;Verbosity=detailed"
"/fileLoggerParameters1:LogFile=$warningLog;WarningsOnly"
"/fileLoggerParameters2:LogFile=$errorsLog;ErrorsOnly"
"/bl:$binLog"
'/nologo'
)
$cmd = $base + ($ExtraArgs -split ' ')
Write-Host (("[MSBUILD] {0}" -f ($cmd -join ' ')))
Push-Location $script:RepoRoot
try {
& msbuild.exe @cmd
if ($LASTEXITCODE -ne 0) {
Write-Error (("Build failed: {0} {1}" -f $Solution, $ExtraArgs))
exit $LASTEXITCODE
}
} finally {
Pop-Location
}
}
function RestoreThenBuild {
param (
[string]$Solution,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration,
[bool]$RestoreOnly=$false
)
$restoreArgs = '/t:restore /p:RestorePackagesConfig=true'
if ($ExtraArgs) { $restoreArgs = "$restoreArgs $ExtraArgs" }
RunMSBuild $Solution $restoreArgs $Platform $Configuration
if (-not $RestoreOnly) {
$buildArgs = '/m'
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
RunMSBuild $Solution $buildArgs $Platform $Configuration
}
}
function BuildProjectsInDirectory {
param(
[string]$DirectoryPath,
[string]$ExtraArgs,
[string]$Platform,
[string]$Configuration,
[switch]$RestoreOnly
)
if (-not (Test-Path $DirectoryPath)) {
return $false
}
$files = @()
try {
$files = Get-ChildItem -Path (Join-Path $DirectoryPath '*') -Include *.sln,*.csproj,*.vcxproj -File -ErrorAction SilentlyContinue
} catch {
$files = @()
}
if (-not $files -or $files.Count -eq 0) {
return $false
}
$names = ($files | ForEach-Object { $_.Name }) -join ', '
Write-Host ("[LOCAL BUILD] Found {0} project(s) in {1}: {2}" -f $files.Count, $DirectoryPath, $names)
$preferredOrder = @('.sln', '.csproj', '.vcxproj')
$files = $files | Sort-Object @{Expression = { [array]::IndexOf($preferredOrder, $_.Extension.ToLower()) }}
foreach ($f in $files) {
Write-Host ("[LOCAL BUILD] Building {0}" -f $f.FullName)
if ($f.Extension -eq '.sln') {
RestoreThenBuild $f.FullName $ExtraArgs $Platform $Configuration $RestoreOnly
} else {
$buildArgs = '/m'
if ($ExtraArgs) { $buildArgs = "$buildArgs $ExtraArgs" }
RunMSBuild $f.FullName $buildArgs $Platform $Configuration
}
}
return $true
}
function Get-DefaultPlatform {
<#
Returns a default target platform string based on the host machine (x64, arm64, x86).
#>
try {
$envArch = $env:PROCESSOR_ARCHITECTURE
if ($envArch) { $envArch = $envArch.ToLower() }
if ($envArch -eq 'amd64' -or $envArch -eq 'x86_64') { return 'x64' }
if ($envArch -match 'arm64') { return 'arm64' }
if ($envArch -eq 'x86') { return 'x86' }
if ($env:PROCESSOR_ARCHITEW6432) {
$envArch2 = $env:PROCESSOR_ARCHITEW6432.ToLower()
if ($envArch2 -eq 'amd64') { return 'x64' }
if ($envArch2 -match 'arm64') { return 'arm64' }
}
try {
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
switch ($osArch.ToString().ToLower()) {
'x64' { return 'x64' }
'arm64' { return 'arm64' }
'x86' { return 'x86' }
}
} catch {
# ignore - RuntimeInformation may not be available
}
} catch {
# ignore any errors and fall back
}
return 'x64'
}

View File

@@ -1,5 +0,0 @@
@echo off
REM Wrapper to run build-essentials.ps1 from cmd.exe
set SCRIPT_DIR=%~dp0
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build-essentials.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -1,71 +1,16 @@
<#
.SYNOPSIS
Build essential native PowerToys projects (runner and settings), restoring NuGet packages first.
cd $PSScriptRoot
cd ..\..
$cwd = Get-Location
$SolutionDir = $cwd,"" -join "\"
cd $SolutionDir
$BuildArgs = "/p:Configuration=Release /p:Platform=x64 /p:BuildProjectReferences=false /p:SolutionDir=$SolutionDir"
.DESCRIPTION
Lightweight script to build a small set of essential C++ projects used by PowerToys' runner and native modules. This script first restores NuGet packages for the full solution (`PowerToys.sln`) and then builds the runner and settings projects. Intended for fast local builds during development.
$ProjectsToBuild =
".\src\runner\runner.vcxproj",
".\src\modules\shortcut_guide\shortcut_guide.vcxproj",
".\src\modules\fancyzones\lib\FancyZonesLib.vcxproj",
".\src\modules\fancyzones\dll\FancyZonesModule.vcxproj"
.PARAMETER Platform
Target platform for the build (for example: 'x64', 'arm64'). If omitted the script will attempt to auto-detect the host platform.
.PARAMETER Configuration
Build configuration (for example: 'Debug' or 'Release'). Default is 'Debug'.
.EXAMPLE
.\tools\build\build-essentials.ps1
Restores packages for the solution and builds the default set of native projects using the auto-detected platform and Debug configuration.
.EXAMPLE
.\tools\build\build-essentials.ps1 -Platform arm64 -Configuration Release
Restores packages and builds the essentials in Release mode for ARM64, even if your machine is running on x64.
.NOTES
- This script dot-sources `build-common.ps1` and uses the shared helper `RunMSBuild`.
- It will call `RestoreThenBuild 'PowerToys.sln'` before building the essential projects to ensure NuGet packages are restored.
- The script attempts to locate the repository root automatically and can be run from any folder inside the repo.
#>
param (
[string]$Platform = '',
[string]$Configuration = 'Debug'
)
# Find repository root starting from the script location
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$repoRoot = $ScriptDir
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
$parent = Split-Path -Parent $repoRoot
if ($parent -eq $repoRoot) {
Write-Error "Could not find PowerToys repository root."
exit 1
}
$repoRoot = $parent
}
# Export script-scope variables used by build-common helpers
Set-Variable -Name RepoRoot -Value $repoRoot -Scope Script -Force
# Load shared helpers
. "$PSScriptRoot\build-common.ps1"
# If platform not provided, auto-detect from host
if (-not $Platform -or $Platform -eq '') {
try {
$Platform = Get-DefaultPlatform
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
} catch {
Write-Warning "Failed to auto-detect platform; defaulting to 'x64'"
$Platform = 'x64'
}
}
# Ensure solution packages are restored
RestoreThenBuild 'PowerToys.sln' '' $Platform $Configuration $true
# Build both runner and settings
$ProjectsToBuild = @(".\src\runner\runner.vcxproj", ".\src\settings-ui\Settings.UI\PowerToys.Settings.csproj")
$ExtraArgs = "/p:SolutionDir=$repoRoot\"
foreach ($proj in $ProjectsToBuild) {
Write-Host ("[BUILD-ESSENTIALS] Building {0}" -f $proj)
RunMSBuild $proj $ExtraArgs $Platform $Configuration
$ProjectsToBuild | % {
Invoke-Expression "msbuild $_ $BuildArgs"
}

View File

@@ -52,26 +52,12 @@ Runs the pipeline for x64 Release with 'vnext' suffix.
#>
param (
[string]$Platform = '',
[string]$Platform = 'x64',
[string]$Configuration = 'Release',
[string]$PerUser = 'true',
[string]$InstallerSuffix = 'wix5'
)
# Ensure helpers are available
. "$PSScriptRoot\build-common.ps1"
# Auto-detect platform when not provided
if (-not $Platform -or $Platform -eq '') {
try {
$Platform = Get-DefaultPlatform
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
} catch {
Write-Warning "Failed to auto-detect platform; defaulting to x64"
$Platform = 'x64'
}
}
# Find the PowerToys repository root automatically
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$repoRoot = $scriptDir
@@ -94,7 +80,50 @@ if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.sln"))) {
}
Write-Host "PowerToys repository root detected: $repoRoot"
function RunMSBuild {
param (
[string]$Solution,
[string]$ExtraArgs
)
$base = @(
$Solution
"/p:Platform=$Platform"
"/p:Configuration=$Configuration"
"/p:CIBuild=true"
'/verbosity:normal'
'/clp:Summary;PerformanceSummary;ErrorsOnly;WarningsOnly'
'/nologo'
)
$cmd = $base + ($ExtraArgs -split ' ')
Write-Host ("[MSBUILD] {0} {1}" -f $Solution, ($cmd -join ' '))
# Run MSBuild from the repository root directory
Push-Location $repoRoot
try {
& msbuild.exe @cmd
if ($LASTEXITCODE -ne 0) {
Write-Error ("Build failed: {0} {1}" -f $Solution, $ExtraArgs)
exit $LASTEXITCODE
}
} finally {
Pop-Location
}
}
function RestoreThenBuild {
param ([string]$Solution)
# 1) restore
RunMSBuild $Solution '/t:restore /p:RestorePackagesConfig=true'
# 2) build -------------------------------------------------
RunMSBuild $Solution '/m'
}
# WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; a separate WiX 3 installation is not required here.
Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser)
Write-Host ''
@@ -105,9 +134,7 @@ if (Test-Path $cmdpalOutputPath) {
Remove-Item $cmdpalOutputPath -Recurse -Force -ErrorAction Ignore
}
$commonArgs = '/p:CIBuild=true'
# No local projects found (or continuing) - build full solution and tools
RestoreThenBuild 'PowerToys.sln' $commonArgs $Platform $Configuration
RestoreThenBuild 'PowerToys.sln'
$msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration"
$msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix |
@@ -121,8 +148,8 @@ else {
Write-Warning "[SIGN] No .msix files found in $msixSearchRoot"
}
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln'
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln'
Write-Host '[CLEAN] installer (keep *.exe)'
Push-Location $repoRoot
@@ -132,10 +159,10 @@ try {
Pop-Location
}
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.sln' '/t:restore /p:RestorePackagesConfig=true'
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstallerVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix"
RunMSBuild 'installer\PowerToysSetup.sln' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix" $Platform $Configuration
RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser /p:InstallerSuffix=$InstallerSuffix"
Write-Host '[PIPELINE] Completed'

View File

@@ -1,5 +0,0 @@
@echo off
REM Wrapper to run the PowerShell build script from cmd.exe
set SCRIPT_DIR=%~dp0
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -1,88 +0,0 @@
<#
.SYNOPSIS
Light-weight wrapper to build local projects (solutions/projects) in the current working directory using helpers in build-common.ps1.
.DESCRIPTION
This script is intended for quick local builds. It dot-sources `build-common.ps1` and calls `BuildProjectsInDirectory` against the current directory. Use `-RestoreOnly` to only restore packages for local projects. If `-Platform` is omitted the script attempts to auto-detect the host platform.
.PARAMETER Platform
Target platform (e.g., 'x64', 'arm64'). If omitted the script will try to detect the host platform automatically.
.PARAMETER Configuration
Build configuration (e.g., 'Debug', 'Release'). Default: 'Debug'.
.PARAMETER RestoreOnly
If specified, only perform package restore for local projects and skip the build steps for a solution file (i.e. .sln).
.PARAMETER ExtraArgs
Any remaining, positional arguments passed to the script are forwarded to MSBuild as additional arguments (e.g., '/p:CIBuild=true').
.EXAMPLE
.\tools\build\build.ps1
Builds any .sln/.csproj/.vcxproj in the current working directory (auto-detects Platform).
.EXAMPLE
.\tools\build\build.ps1 -Platform x64 -Configuration Release
Builds local projects for x64 Release.
.EXAMPLE
.\tools\build\build.ps1 '/p:CIBuild=true' '/p:SomeOther=Value'
Pass additional MSBuild arguments; these are forwarded to the underlying msbuild calls.
.EXAMPLE
.\tools\build\build.ps1 -RestoreOnly '/p:CIBuild=true'
Only restores packages for local projects; ExtraArgs still forwarded to msbuild's restore phase.
.NOTES
- This file expects `build-common.ps1` to be located in the same folder and dot-sources it to load helper functions.
- ExtraArgs are captured using PowerShell's ValueFromRemainingArguments and joined before being passed to the helpers.
#>
param (
[string]$Platform = '',
[string]$Configuration = 'Debug',
[switch]$RestoreOnly,
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$ExtraArgs
)
. "$PSScriptRoot\build-common.ps1"
# If user passed MSBuild-style args (e.g. './build.ps1 /p:CIBuild=true'),
# those will bind to $Platform/$Configuration; detect those and move them to ExtraArgs.
$positionalExtra = @()
if ($Platform -and $Platform -match '^[\/-]') {
$positionalExtra += $Platform
$Platform = ''
}
if ($Configuration -and $Configuration -match '^[\/-]') {
$positionalExtra += $Configuration
$Configuration = 'Debug'
}
if ($positionalExtra.Count -gt 0) {
if (-not $ExtraArgs) { $ExtraArgs = @() }
$ExtraArgs = $positionalExtra + $ExtraArgs
}
# Auto-detect platform when not provided
if (-not $Platform -or $Platform -eq '') {
try {
$Platform = Get-DefaultPlatform
Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
} catch {
Write-Warning "Failed to auto-detect platform; defaulting to x64"
$Platform = 'x64'
}
}
$cwd = (Get-Location).ProviderPath
$extraArgsString = $null
if ($ExtraArgs -and $ExtraArgs.Count -gt 0) { $extraArgsString = ($ExtraArgs -join ' ') }
if (BuildProjectsInDirectory -DirectoryPath $cwd -ExtraArgs $extraArgsString -Platform $Platform -Configuration $Configuration -RestoreOnly:$RestoreOnly) {
Write-Host "[BUILD] Local projects built; exiting."
exit 0
} else {
Write-Host "[BUILD] No local projects found in $cwd"
exit 0
}