From c2f07544d4493d442fd795647fa8dbe110122d80 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 23 Sep 2025 13:04:39 -0700 Subject: [PATCH 1/3] Allow opt-out of the named-pipe listener using an environment variable --- .../engine/Utils.cs | 67 +++++++++++++++++- .../remoting/common/RemoteSessionNamedPipe.cs | 14 ++-- .../utils/Telemetry.cs | 69 +------------------ 3 files changed, 74 insertions(+), 76 deletions(-) diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index ba43bc521ce..0de9fe0d5cc 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -24,7 +24,6 @@ using System.Threading; using Microsoft.PowerShell.Commands; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; using TypeTable = System.Management.Automation.Runspaces.TypeTable; @@ -1548,6 +1547,72 @@ internal static bool IsSessionRestricted(ExecutionContext context) } return true; } + + /// + /// Determine whether the environment variable is set and how. + /// + /// The name of the environment variable. + /// If the environment variable is not set, use this as the default value. + /// A boolean representing the value of the environment variable. + internal static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) + { + var str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + var boolStr = str.AsSpan(); + + if (boolStr.Length == 1) + { + if (boolStr[0] == '1') + { + return true; + } + + if (boolStr[0] == '0') + { + return false; + } + } + + if (boolStr.Length == 3 && + (boolStr[0] == 'y' || boolStr[0] == 'Y') && + (boolStr[1] == 'e' || boolStr[1] == 'E') && + (boolStr[2] == 's' || boolStr[2] == 'S')) + { + return true; + } + + if (boolStr.Length == 2 && + (boolStr[0] == 'n' || boolStr[0] == 'N') && + (boolStr[1] == 'o' || boolStr[1] == 'O')) + { + return false; + } + + if (boolStr.Length == 4 && + (boolStr[0] == 't' || boolStr[0] == 'T') && + (boolStr[1] == 'r' || boolStr[1] == 'R') && + (boolStr[2] == 'u' || boolStr[2] == 'U') && + (boolStr[3] == 'e' || boolStr[3] == 'E')) + { + return true; + } + + if (boolStr.Length == 5 && + (boolStr[0] == 'f' || boolStr[0] == 'F') && + (boolStr[1] == 'a' || boolStr[1] == 'A') && + (boolStr[2] == 'l' || boolStr[2] == 'L') && + (boolStr[3] == 's' || boolStr[3] == 'S') && + (boolStr[4] == 'e' || boolStr[4] == 'E')) + { + return false; + } + + return defaultValue; + } } } diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 4810d1b37ea..198338e918c 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -12,7 +12,6 @@ using System.Security.AccessControl; using System.Security.Principal; using System.Threading; -using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -500,13 +499,14 @@ static RemoteSessionNamedPipeServer() { s_syncObject = new object(); - // All PowerShell instances will start with the named pipe - // and listener created and running. - IPCNamedPipeServerEnabled = true; - - CreateIPCNamedPipeServerSingleton(); + // Unless opt-out, all PowerShell instances will start with the named-pipe listener created and running. + IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_Diagnostics_OPTOUT", defaultValue: false); - CreateProcessExitHandler(); + if (IPCNamedPipeServerEnabled) + { + CreateIPCNamedPipeServerSingleton(); + CreateProcessExitHandler(); + } } #endregion diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index 13a8d7d5ec5..5cd728e6c55 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; -using System.Security.AccessControl; using System.Threading; using Microsoft.ApplicationInsights; @@ -177,7 +176,7 @@ public static class ApplicationInsightsTelemetry static ApplicationInsightsTelemetry() { // If we can't send telemetry, there's no reason to do any of this - CanSendTelemetry = !GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false); + CanSendTelemetry = !Utils.GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false); if (CanSendTelemetry) { s_sessionId = Guid.NewGuid().ToString(); @@ -643,72 +642,6 @@ static ApplicationInsightsTelemetry() } } - /// - /// Determine whether the environment variable is set and how. - /// - /// The name of the environment variable. - /// If the environment variable is not set, use this as the default value. - /// A boolean representing the value of the environment variable. - private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) - { - var str = Environment.GetEnvironmentVariable(name); - if (string.IsNullOrEmpty(str)) - { - return defaultValue; - } - - var boolStr = str.AsSpan(); - - if (boolStr.Length == 1) - { - if (boolStr[0] == '1') - { - return true; - } - - if (boolStr[0] == '0') - { - return false; - } - } - - if (boolStr.Length == 3 && - (boolStr[0] == 'y' || boolStr[0] == 'Y') && - (boolStr[1] == 'e' || boolStr[1] == 'E') && - (boolStr[2] == 's' || boolStr[2] == 'S')) - { - return true; - } - - if (boolStr.Length == 2 && - (boolStr[0] == 'n' || boolStr[0] == 'N') && - (boolStr[1] == 'o' || boolStr[1] == 'O')) - { - return false; - } - - if (boolStr.Length == 4 && - (boolStr[0] == 't' || boolStr[0] == 'T') && - (boolStr[1] == 'r' || boolStr[1] == 'R') && - (boolStr[2] == 'u' || boolStr[2] == 'U') && - (boolStr[3] == 'e' || boolStr[3] == 'E')) - { - return true; - } - - if (boolStr.Length == 5 && - (boolStr[0] == 'f' || boolStr[0] == 'F') && - (boolStr[1] == 'a' || boolStr[1] == 'A') && - (boolStr[2] == 'l' || boolStr[2] == 'L') && - (boolStr[3] == 's' || boolStr[3] == 'S') && - (boolStr[4] == 'e' || boolStr[4] == 'E')) - { - return false; - } - - return defaultValue; - } - /// /// Send module load telemetry as a metric. /// For modules we send the module name (if allowed), and the version. From 700ed27ca0eaa2705fdb8376f140ac86d086e9bb Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 23 Sep 2025 13:29:13 -0700 Subject: [PATCH 2/3] Update message --- .../resources/RemotingErrorIdStrings.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index a9dc46bdec5..819270da259 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1411,7 +1411,7 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro Multiple processes were found with this name {0}. Use the process Id to specify a single process to enter. - Cannot enter process with Id '{0}' because it has not loaded the PowerShell engine. + Cannot enter process with Id '{0}' because it has not loaded the PowerShell engine or the named-pipe listener was disabled. No process was found with Id: {0}. From 142cd55586bf7e26be36fa8e9e3dc4a7ee39768e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 30 Sep 2025 09:41:42 -0700 Subject: [PATCH 3/3] Use upper case for the env variable --- .../engine/remoting/common/RemoteSessionNamedPipe.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 198338e918c..e21608a378f 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -500,7 +500,7 @@ static RemoteSessionNamedPipeServer() s_syncObject = new object(); // Unless opt-out, all PowerShell instances will start with the named-pipe listener created and running. - IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_Diagnostics_OPTOUT", defaultValue: false); + IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_DIAGNOSTICS_OPTOUT", defaultValue: false); if (IPCNamedPipeServerEnabled) {