Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell Redirection Does Not Always Work

Tags:

powershell

Redirecting the Warning Stream for New-PSSession does not work as I believe it should. I am always getting the following (yellow) warning message to console regardless of what I do to try suppress / redirect it:

WARNING: Using New-PSSession with Basic Authentication is going to be deprecated soon, checkout https://aka.ms/exops-docs for using Exchange Online V2 Module which uses Modern Authentication.

What am I missing about PowerShell redirection / streams that's allowing that message to permeate through to the console / std_out?

What doesn't work

I have tried the following, as per the wisdom of the internet:

New-PSSession *>$null ...
New-PSSession ... | Out-Null
New-PSSession *>$null ... -OutVariable x -ErrorVariable y -WarningVariable z
New-PSSession *>$null ... -WarningAction SilentlyContinue
$WarningPreference = 'SilentlyContinue'
New-PSSession ...

I've even tried taking temporary control of std_out:

$std_out = [System.Console]::Out
$out_writer = New-Object IO.StringWriter
[System.Console]::SetOut($out_writer)

$std_err = [System.Console]::Error
$err_writer = New-Object IO.StringWriter
[System.Console]::SetOut($err_writer)

$sess = New-PSSession ...

[System.Console]::SetOut($std_out)
[System.Console]::SetError($std_err)

Along with every combination of above that I can think of, plus several more methods that I'm forgetting.

What should work

Testing each of those techniques using Invoke-Command works for other warnings, as expected:


$std_out = [System.Console]::Out
$out_writer = New-Object IO.StringWriter
[System.Console]::SetOut($out_writer)

$std_err = [System.Console]::Error
$err_writer = New-Object IO.StringWriter
[System.Console]::SetOut($err_writer)

Invoke-Command -ScriptBlock {

    Write-Error "My Error"
    Write-Warning "My Warning"
    [console]::WriteLine('Directly to std_out!')

} *>$null -OutVariable x -ErrorVariable y -WarningVariable z

[System.Console]::SetOut($std_out)
[System.Console]::SetError($std_err)

But nothing I have tried will suppress or redirect that message from New-PSSession.

To reproduce

param (
    $username,
    $password
)

$secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)
$URL = "https://ps.outlook.com/powershell"
New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $URL -Credential $creds -Authentication Basic -AllowRedirection

Environments Tested

Windows 10:

Name                           Value
----                           -----
PSVersion                      7.1.0-preview.2
PSEdition                      Core
GitCommitId                    7.1.0-preview.2
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Windows 10:

Name                           Value
----                           -----
PSVersion                      5.1.18362.752
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.752
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Server 2019:

Name                           Value
----                           -----
PSVersion                      5.1.17763.1007
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.1007
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

WinRM (Identical for all systems):

Config
    MaxEnvelopeSizekb = 500
    MaxTimeoutms = 60000
    MaxBatchItems = 32000
    MaxProviderRequests = 4294967295
    Client
        NetworkDelayms = 5000
        URLPrefix = wsman
        AllowUnencrypted = false
        Auth
            Basic = true
            Digest = true
            Kerberos = true
            Negotiate = true
            Certificate = true
            CredSSP = false
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        TrustedHosts
    Service
        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
        MaxConcurrentOperations = 4294967295
        MaxConcurrentOperationsPerUser = 1500
        EnumerationTimeoutms = 240000
        MaxConnections = 300
        MaxPacketRetrievalTimeSeconds = 120
        AllowUnencrypted = false
        Auth
            Basic = false
            Kerberos = true
            Negotiate = true
            Certificate = false
            CredSSP = false
            CbtHardeningLevel = Relaxed
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        IPv4Filter = *
        IPv6Filter = *
        EnableCompatibilityHttpListener = false
        EnableCompatibilityHttpsListener = false
        CertificateThumbprint
        AllowRemoteAccess = true
    Winrs
        AllowRemoteShellAccess = true
        IdleTimeout = 7200000
        MaxConcurrentUsers = 2147483647
        MaxShellRunTime = 2147483647
        MaxProcessesPerShell = 2147483647
        MaxMemoryPerShellMB = 2147483647
        MaxShellsPerUser = 2147483647
like image 413
Richard Dunn Avatar asked May 01 '20 20:05

Richard Dunn


2 Answers

Normally, redirecting the output works like you already tried. To be more specific:

New-PSSession -... 3> $null

would redirect your unwanted warning, as 3 describes the Warning Stream since PowerShell 3.0 (see docs).

The fact that it does not work in this particular case seems to be a bug and there has already been opened an issue here a few days ago.


There might be a possible explanation in this article:

This happens because Write-Host is not written to any stream. It's sent to the host program, and the host program decides what to do with it. The Windows PowerShell console and Windows PowerShell ISE display host messages on the console.

Well, it is not correct anymore (since PowerShell 5.0), as Write-Host is now a wrapper for Write-Information and thus writes to the Information Stream (6). But what is interesting, is the fact that there are host messages that are not written to any stream. This might be the case here, where you are not able to suppress a console output by redirecting all streams. So the "warning" is probably forwarded by a host message instead of using any stream.

I saw several attempts to suppress those host messages (here and here), but as far as I can tell, nothing would help in your case.

The cited article further states:

[...] messages to the host program (which can't be suppressed or redirected).

like image 181
stackprotector Avatar answered Sep 27 '22 22:09

stackprotector


Maybe not an answer but too long for a comment. Don't ask me to explain what it does nor how it works. It was found somewhere here or somewhere else. I put it here because it seems to be related to powershell and some kind of thread interactions... I let you take what you need out of it.

Here was our context : We had to run a powershell script from a bat script. The powershell script was launching some system command (zip and so on). We had hard time to collect runtime informations the right way. As a solution we had to pass the ps1 script as a parameter of another ps1 script which we called bug.ps1. Its content is shown below.

Here is the command in the .bat file :

powershell.exe -ExecutionPolicy Bypass -command "C:\path\to\bug.ps1" "my_script.ps1" %arg%

and here is the content of the bug.ps1 file

# <fix>
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$objectRef = $host.GetType().GetField( "externalHostRef", $bindingFlags ).GetValue( $host )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
$consoleHost = $objectRef.GetType().GetProperty( "Value", $bindingFlags ).GetValue( $objectRef, @() )
[void] $consoleHost.GetType().GetProperty( "IsStandardOutputRedirected", $bindingFlags ).GetValue( $consoleHost, @() )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$field = $consoleHost.GetType().GetField( "standardOutputWriter", $bindingFlags )
$field.SetValue( $consoleHost, [Console]::Out )
$field2 = $consoleHost.GetType().GetField( "standardErrorWriter", $bindingFlags )
$field2.SetValue( $consoleHost, [Console]::Out )
# </fix>
invoke-expression "$args"
exit $lastexitcode
like image 38
Tuckbros Avatar answered Sep 28 '22 00:09

Tuckbros