Write-Information does not appear to work in powershell foreach-object -parallel

4.3k views Asked by At

I am new to powershell and just learning it. I have some experience in C#. I am trying to use the foreach-object -Parallel option but cant get all the Write-* functions to work.

function writeTest {
        1..1 | ForEach-Object -Parallel {
            Write-Host "host"
            Write-Output "Output"
            Write-Information "information" -InformationAction Continue
            Write-Verbose "verbose"
            Write-Warning "Warning"
            Write-Error "error"       
        }
}

Function called like: writeTest -verbose

Outputs:

host
Output
WARNING: Warning
Write-Error: error

My question is why doesn't write-verbose and write-information display anything?

Please forgive any ignorance.

1

There are 1 answers

0
mklement0 On BEST ANSWER

You're seeing a bug in ForEach-Object -Parallel / Start-ThreadJob, present up to at least PowerShell Core 7.0:

Your Write-Information output should show, because you've used -InformationAction Continue to turn it on; while your Write-Verbose output not showing is expected, because you didn't turn it on with -Verbose, it also wouldn't show if you did use -Verbose, due to the bug.

See GitHub issue #13816.

The workaround is to also set the preference variable $InformationPreference in order to turn on the information stream, before calling ForEach-Object -Parallel (see below).

Separately, there's a conceptual problem with your code:

  • You're passing the -Verbose common parameter to your write-Test function, yet this function isn't declared as an advanced function (which requires a [CmdletBinding()] attribute above a param(...) block and/or at least one parameter with an [Parameter(...)] attribute - see about_Functions_Advanced), so the parameter will have no effect.

  • Even if it did take effect, which means that PowerShell translates it into a function-local $VerbosePreference variable with value 'Continue', the ForEach-Object -Parallel block would not see that variable, because the $using: scope is needed to reference variable values from the caller's scope; see this answer.

To put it all together.

function Write-Test {

  # Mark the function as an advanced one, so that it accepts 
  # common parameters such as -Verbose
  [CmdletBinding()]
  param()
  
  # WORKAROUND: Turn the information stream on 
  #             via its *preference variable* AS WELL:
  $InformationPreference = 'Continue'
  
  1..1 | ForEach-Object -Parallel {
    Write-Host "host"
    Write-Output "Output"
    Write-Information "information" -InformationAction Continue               
    Write-Verbose "verbose" -Verbose:($using:VerbosePreference -eq 'Continue')
    Write-Warning "Warning"
    Write-Error "error"       
  }

}
  
Write-Test -Verbose

Note the expression passed to the -Verbose switch, of necessity separated from the switch name with : - -Verbose:($using:VerbosePreference -eq 'Continue') - which in effect only turns on verbose output if the $VerbosePreference value in the function's main thread ($using:) is set to 'Continue', which in turn happens when -Verbose is passed to the advanced function from the outside (it would also happen if $VerbosePreference were set to 'Continue' in the caller's scope, due to PowerShell's dynamic scoping; see this answer).


General information about PowerShell output streams:

  • Both Write-Verbose and Write-Information are silent by default, as is Write-Debug.

  • You can make their output show in one of two ways:

    • Per-command, via a common parameter: Add -Verbose to a Write-Verbose call and InformationAction Continue to a Write-Information call (as you've done).

    • Scope-wide, via preference variables: set $VerbosePreference = 'Continue' and $InformationPreference = 'Continue' - but the caveat is that neither functions that live in (other) modules nor code that runs in a different thread or process sees these variables by default:

      • In the module case, you must either use common parameters explicitly or set the preference variable in the global scope, which is not advisable; the problem is discussed in GitHub issue #4568.
      • In the other thread/process case, the $using: scope is required, as shown above.

See about_Redirection, about_CommonParameters, and about_Preference_Variables