From 009ee75de0d8209b6205665c4d755d3574f20461 Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Sun, 22 Feb 2026 19:14:19 -0800 Subject: [PATCH] CmdPal: Fix RDP extension rejecting host:port connections (#45740) ## Summary Fixes #45100 Uri.CheckHostName does not accept host:port strings (e.g. localhost:3389), returning UriHostNameType.Unknown. This causes the RDP extension to show an invalid hostname error when connecting to a local forwarded port. ## Changes - OpenRemoteDesktopCommand.cs - Strip port suffix before Uri.CheckHostName validation. The full host:port is still passed to mstsc /v:. - FallbackRemoteDesktopItem.cs - Same port-aware validation so the fallback correctly recognizes host:port queries and displays them in the title. - FallbackRemoteDesktopItemTests.cs - Added tests for localhost:3389 and 192.168.1.100:3390 inputs. ## Validation Port detection uses LastIndexOf(':') + ushort.TryParse to safely identify a trailing port number without affecting IPv6 addresses or plain hostnames. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FallbackRemoteDesktopItemTests.cs | 42 +++++++++++++++++++ .../Commands/FallbackRemoteDesktopItem.cs | 36 +++++++++++----- .../Commands/OpenRemoteDesktopCommand.cs | 15 ++++++- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests/FallbackRemoteDesktopItemTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests/FallbackRemoteDesktopItemTests.cs index f42520e6de..28f410c6ac 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests/FallbackRemoteDesktopItemTests.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests/FallbackRemoteDesktopItemTests.cs @@ -98,6 +98,48 @@ public class FallbackRemoteDesktopItemTests Assert.IsNull(command); } + [TestMethod] + public void UpdateQuery_WhenQueryIsHostnameWithPort_UsesFullHostPort() + { + // Arrange + var setup = CreateFallback(); + var fallback = setup.Fallback; + const string hostPort = "localhost:3389"; + + // Act + fallback.UpdateQuery(hostPort); + + // Assert + var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostPort); + Assert.AreEqual(expectedTitle, fallback.Title); + Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle); + + var command = fallback.Command as OpenRemoteDesktopCommand; + Assert.IsNotNull(command); + Assert.AreEqual(hostPort, GetCommandHost(command)); + } + + [TestMethod] + public void UpdateQuery_WhenQueryIsIPWithPort_UsesFullHostPort() + { + // Arrange + var setup = CreateFallback(); + var fallback = setup.Fallback; + const string hostPort = "192.168.1.100:3390"; + + // Act + fallback.UpdateQuery(hostPort); + + // Assert + var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostPort); + Assert.AreEqual(expectedTitle, fallback.Title); + Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle); + + var command = fallback.Command as OpenRemoteDesktopCommand; + Assert.IsNotNull(command); + Assert.AreEqual(hostPort, GetCommandHost(command)); + } + private static string GetCommandHost(OpenRemoteDesktopCommand command) { var field = typeof(OpenRemoteDesktopCommand).GetField("_rdpHost", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/FallbackRemoteDesktopItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/FallbackRemoteDesktopItem.cs index 287a697c31..8319d5c2c2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/FallbackRemoteDesktopItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/FallbackRemoteDesktopItem.cs @@ -60,18 +60,34 @@ internal sealed partial class FallbackRemoteDesktopItem : FallbackCommandItem Title = connectionName; Subtitle = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName); } - else if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(query))) - { - var connectionName = query.Trim(); - Command = new OpenRemoteDesktopCommand(connectionName); - Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName); - Subtitle = Resources.remotedesktop_title; - } else { - Title = string.Empty; - Subtitle = string.Empty; - Command = _emptyCommand; + // Strip port suffix (e.g. "localhost:3389") before validation, + // since Uri.CheckHostName does not accept host:port strings. + var hostForValidation = query.Trim(); + var lastColon = hostForValidation.LastIndexOf(':'); + if (lastColon > 0 && lastColon < hostForValidation.Length - 1) + { + var portPart = hostForValidation.Substring(lastColon + 1); + if (ushort.TryParse(portPart, out _)) + { + hostForValidation = hostForValidation.Substring(0, lastColon); + } + } + + if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(hostForValidation))) + { + var connectionName = query.Trim(); + Command = new OpenRemoteDesktopCommand(connectionName); + Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName); + Subtitle = Resources.remotedesktop_title; + } + else + { + Title = string.Empty; + Subtitle = string.Empty; + Command = _emptyCommand; + } } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/OpenRemoteDesktopCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/OpenRemoteDesktopCommand.cs index 39c53fe9d2..614bebb95a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/OpenRemoteDesktopCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Commands/OpenRemoteDesktopCommand.cs @@ -46,7 +46,20 @@ internal sealed partial class OpenRemoteDesktopCommand : BaseObservable, IInvoka if (!string.IsNullOrWhiteSpace(_rdpHost)) { // validate that _rdpHost is a proper hostname or IP address - if (Uri.CheckHostName(_rdpHost) == UriHostNameType.Unknown) + // Strip port suffix (e.g. "localhost:3389") before validation, + // since Uri.CheckHostName does not accept host:port strings. + var hostForValidation = _rdpHost; + var lastColon = _rdpHost.LastIndexOf(':'); + if (lastColon > 0 && lastColon < _rdpHost.Length - 1) + { + var portPart = _rdpHost.Substring(lastColon + 1); + if (ushort.TryParse(portPart, out _)) + { + hostForValidation = _rdpHost.Substring(0, lastColon); + } + } + + if (Uri.CheckHostName(hostForValidation) == UriHostNameType.Unknown) { return CommandResult.ShowToast(new ToastArgs() {