From 2db145c56a7f6ca6f431feb66c7c9aff57dd4cad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:49:43 +0000 Subject: [PATCH 1/3] Initial plan From c93ecb49c5e301ea112a9c8906aef5ad66503168 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:03:00 +0000 Subject: [PATCH 2/3] Fix UriFormatException in ImageLoader when PowerToys is run from extended-length UNC path When PowerToys is installed on or accessed from a network share (e.g. via Remote Desktop), the assembly location reported by the runtime uses the Windows extended-length UNC path prefix (\\?\UNC\server\share\...). The System.Uri class cannot parse this prefix and throws UriFormatException with 'The hostname could not be parsed' in ImageLoader.Initialize(). Add GetNormalizedPath() helper that strips \\?\UNC\ (converts to \\) or \\?\ prefix before creating Uri objects, and apply it in Initialize() and LoadFullImage(). Also add unit tests for the new helper. Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/ea947576-4e79-40ac-bf65-d6b35bc75ad7 Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- .../Wox.Infrastructure/Image/ImageLoader.cs | 22 +++++++++- .../launcher/Wox.Test/ImageLoaderTest.cs | 41 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/modules/launcher/Wox.Test/ImageLoaderTest.cs diff --git a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs index 4d1f75707997..aace1c3a1b7b 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs @@ -55,13 +55,31 @@ public static bool IsValidPngSignature(string filePath) return fs.Read(buffer, 0, buffer.Length) == buffer.Length && pngSignature.SequenceEqual(buffer); } + // Strips the Windows extended-length path prefix (\\?\UNC\ or \\?\) so that + // System.Uri can parse the resulting path. The prefix is used by Windows for + // paths longer than MAX_PATH but is not understood by System.Uri. + internal static string GetNormalizedPath(string path) + { + if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase)) + { + return @"\\" + path.Substring(8); + } + + if (path.StartsWith(@"\\?\", StringComparison.OrdinalIgnoreCase)) + { + return path.Substring(4); + } + + return path; + } + public static void Initialize() { _hashGenerator = new ImageHashGenerator(); foreach (var icon in new[] { Constant.ErrorIcon, Constant.LightThemedErrorIcon }) { - var uri = new Uri(icon); + var uri = new Uri(GetNormalizedPath(icon)); try { @@ -298,7 +316,7 @@ private static BitmapImage LoadFullImage(string path) BitmapImage image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; - image.UriSource = new Uri(path); + image.UriSource = new Uri(GetNormalizedPath(path)); image.EndInit(); return image; } diff --git a/src/modules/launcher/Wox.Test/ImageLoaderTest.cs b/src/modules/launcher/Wox.Test/ImageLoaderTest.cs new file mode 100644 index 000000000000..9c53ea2ad557 --- /dev/null +++ b/src/modules/launcher/Wox.Test/ImageLoaderTest.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Wox.Infrastructure.Image; + +namespace Wox.Test +{ + [TestClass] + public class ImageLoaderTest + { + [DataTestMethod] + + // Regular Windows paths should be returned unchanged + [DataRow(@"C:\path\to\file.png", @"C:\path\to\file.png")] + [DataRow(@"C:\Program Files\PowerToys\Assets\PowerLauncher\app_error.dark.png", @"C:\Program Files\PowerToys\Assets\PowerLauncher\app_error.dark.png")] + + // UNC paths should be returned unchanged + [DataRow(@"\\server\share\path\file.png", @"\\server\share\path\file.png")] + + // Extended-length local paths (\\?\C:\...) should have the \\?\ prefix stripped + [DataRow(@"\\?\C:\path\to\file.png", @"C:\path\to\file.png")] + [DataRow(@"\\?\C:\Program Files\PowerToys\Assets\app_error.dark.png", @"C:\Program Files\PowerToys\Assets\app_error.dark.png")] + + // Extended-length UNC paths (\\?\UNC\server\...) should be converted to \\server\... + [DataRow(@"\\?\UNC\server\share\path\file.png", @"\\server\share\path\file.png")] + [DataRow(@"\\?\UNC\TH50\TH50_c\Program Files\PowerToys\Assets\PowerLauncher\app_error.dark.png", @"\\TH50\TH50_c\Program Files\PowerToys\Assets\PowerLauncher\app_error.dark.png")] + + // Case-insensitive matching for the prefix + [DataRow(@"\\?\unc\server\share\path\file.png", @"\\server\share\path\file.png")] + public void GetNormalizedPath_ShouldStripExtendedLengthPrefix(string input, string expected) + { + // Act + string result = ImageLoader.GetNormalizedPath(input); + + // Assert + Assert.AreEqual(expected, result); + } + } +} From bb4356bdeea8a42dd12f2292681ab7c299504bf9 Mon Sep 17 00:00:00 2001 From: "Muyuan Li (from Dev Box)" Date: Thu, 14 May 2026 15:55:43 +0800 Subject: [PATCH 3/3] Address review: use prefix constants instead of magic numbers, normalize paths at source Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Wox.Infrastructure/Image/ImageLoader.cs | 15 +--------- src/modules/launcher/Wox.Plugin/Constant.cs | 4 +-- .../launcher/Wox.Plugin/PathNormalization.cs | 29 +++++++++++++++++++ .../launcher/Wox.Test/ImageLoaderTest.cs | 4 +-- 4 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 src/modules/launcher/Wox.Plugin/PathNormalization.cs diff --git a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs index aace1c3a1b7b..0da3de80e7a6 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs @@ -55,22 +55,9 @@ public static bool IsValidPngSignature(string filePath) return fs.Read(buffer, 0, buffer.Length) == buffer.Length && pngSignature.SequenceEqual(buffer); } - // Strips the Windows extended-length path prefix (\\?\UNC\ or \\?\) so that - // System.Uri can parse the resulting path. The prefix is used by Windows for - // paths longer than MAX_PATH but is not understood by System.Uri. internal static string GetNormalizedPath(string path) { - if (path.StartsWith(@"\\?\UNC\", StringComparison.OrdinalIgnoreCase)) - { - return @"\\" + path.Substring(8); - } - - if (path.StartsWith(@"\\?\", StringComparison.OrdinalIgnoreCase)) - { - return path.Substring(4); - } - - return path; + return PathNormalization.NormalizePath(path); } public static void Initialize() diff --git a/src/modules/launcher/Wox.Plugin/Constant.cs b/src/modules/launcher/Wox.Plugin/Constant.cs index 18245bb6c2f4..9866fab6f32c 100644 --- a/src/modules/launcher/Wox.Plugin/Constant.cs +++ b/src/modules/launcher/Wox.Plugin/Constant.cs @@ -64,7 +64,7 @@ public static string DetermineDataDirectory() public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion; public static readonly int ThumbnailSize = 64; - public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Assets", "PowerLauncher", "app_error.dark.png"); - public static readonly string LightThemedErrorIcon = Path.Combine(ProgramDirectory, "Assets", "PowerLauncher", "app_error.light.png"); + public static readonly string ErrorIcon = PathNormalization.NormalizePath(Path.Combine(ProgramDirectory, "Assets", "PowerLauncher", "app_error.dark.png")); + public static readonly string LightThemedErrorIcon = PathNormalization.NormalizePath(Path.Combine(ProgramDirectory, "Assets", "PowerLauncher", "app_error.light.png")); } } diff --git a/src/modules/launcher/Wox.Plugin/PathNormalization.cs b/src/modules/launcher/Wox.Plugin/PathNormalization.cs new file mode 100644 index 000000000000..6396e72d6353 --- /dev/null +++ b/src/modules/launcher/Wox.Plugin/PathNormalization.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Wox.Plugin +{ + public static class PathNormalization + { + private const string UncPrefix = @"\\?\UNC\"; + private const string ExtendedPrefix = @"\\?\"; + + public static string NormalizePath(string path) + { + if (path.StartsWith(UncPrefix, StringComparison.OrdinalIgnoreCase)) + { + return @"\\" + path.Substring(UncPrefix.Length); + } + + if (path.StartsWith(ExtendedPrefix, StringComparison.OrdinalIgnoreCase)) + { + return path.Substring(ExtendedPrefix.Length); + } + + return path; + } + } +} diff --git a/src/modules/launcher/Wox.Test/ImageLoaderTest.cs b/src/modules/launcher/Wox.Test/ImageLoaderTest.cs index 9c53ea2ad557..e568974e9402 100644 --- a/src/modules/launcher/Wox.Test/ImageLoaderTest.cs +++ b/src/modules/launcher/Wox.Test/ImageLoaderTest.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; -using Wox.Infrastructure.Image; +using Wox.Plugin; namespace Wox.Test { @@ -32,7 +32,7 @@ public class ImageLoaderTest public void GetNormalizedPath_ShouldStripExtendedLengthPrefix(string input, string expected) { // Act - string result = ImageLoader.GetNormalizedPath(input); + string result = PathNormalization.NormalizePath(input); // Assert Assert.AreEqual(expected, result);