89

How can I hide the progress display of Invoke-WebRequest? I do a lot of successive requests and have my own Write-Progress display that I use, so I don't need the built-in one popping up underneath it every time.

I use the mshtml results (the IE COM object) that are created from the result of Invoke-WebRequest automatically, so I can't switch to a WebClient or something like that, unless someone provides instructions on how to get an mshtml object from a WebClient request.

qJake
  • 16,821
  • 17
  • 83
  • 135
  • 4
    Note that performance is order of magnitudes better when hiding progressbar. I was waiting for few minutes until I canceled <300MB download. When turned off progress bar, it completed within few seconds. Related: [Progress bar can significantly impact cmdlet performance](https://github.com/PowerShell/PowerShell/issues/2138) – Janis Veinbergs Jun 27 '18 at 06:19

2 Answers2

129

Use the $progressPreference variable. It should have a value of 'Continue' by default unless you've edited it elsewhere, which tells Powershell to display the progress bar. Since you mentioned that you have your own custom progress displays, I would reset it immediately after the cmdlet is executed. For example:

$ProgressPreference = 'SilentlyContinue'    # Subsequent calls do not display UI.
Invoke-WebRequest ...
$ProgressPreference = 'Continue'            # Subsequent calls do display UI.
Write-Progress ...

More info on preference variables at about_preference_variables. Here's the entry for $ProgressPreference:

$ProgressPreference
-------------------
Determines how Windows PowerShell responds to progress updates 
        generated by a script, cmdlet or provider, such as the progress bars
        generated by the Write-Progress cmdlet. The Write-Progress cmdlet 
        creates progress bars that depict the status of a command.

        Valid values:
          Stop:               Does not display the progress bar. Instead,
                                it displays an error message and stops executing.

          Inquire:            Does not display the progress bar. Prompts
                                for permission to continue. If you reply
                                with Y or A, it displays the progress bar.

          Continue:           Displays the progress bar and continues with
          (Default)             execution.

          SilentlyContinue:   Executes the command, but does not display
                                the progress bar.
Joel Verhagen
  • 5,110
  • 4
  • 38
  • 47
Anthony Neace
  • 25,013
  • 7
  • 114
  • 129
  • 26
    In case anyone else has issues with this... you may need to explicitly specify `$global:progressPreference = 'silentlyContinue'` if calling in to other script blocks. – Chris Baxter Apr 02 '18 at 19:18
  • 9
    Using `$oldProgressPreference = $progressPreference; $progressPreference = 'SilentlyContinue';` and later `$progressPreference = $oldProgressPreference` will in addition retain the previous setting which can improve consistency when linking scripts. – Siavas Aug 20 '18 at 09:50
  • 1
    @ChrisBaxter thanks! Without the `$global` bit it was working fine for me when I had the commands written out in my CI config directly, but it stopped working once I moved it into a powershell script. Setting the global variable fixed it right up! – Ben Baron May 04 '20 at 20:06
7

Here is a reusable function to temporarily hide the progress of any script block and automatically restore the progress preference when the script block ends, even if an exception (script-terminating error) is thrown by the script block.

# Create an in-memory module so $ScriptBlock doesn't run in new scope
$null = New-Module {
    function Invoke-WithoutProgress {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory)] [scriptblock] $ScriptBlock
        )

        # Save current progress preference and hide the progress
        $prevProgressPreference = $global:ProgressPreference
        $global:ProgressPreference = 'SilentlyContinue'

        try {
            # Run the script block in the scope of the caller of this module function
            . $ScriptBlock
        }
        finally {
            # Restore the original behavior
            $global:ProgressPreference = $prevProgressPreference
        }
    }
}

Usage example:

Invoke-WithoutProgress {
    # Here $ProgressPreference is set to 'SilentlyContinue'
    Invoke-WebRequest ...
}

# Now $ProgressPreference is restored
Write-Progress ...

Notes:

  • The New-Module call is there so the script block passed to Invoke-WithoutProgress doesn't run in a new scope (allowing it to directly modify surrounding variables, similar to ForEach-Object's script block). See this answer for more information.
zett42
  • 25,437
  • 3
  • 35
  • 72
  • Just wondering, doesn't this technically also swallow exceptions, which may be undesired? I like the approach (have done similar stuff with lambdas in C++ and Python), but swallowing exceptions as a side effect should probably be noted. Thanks for showing the technique, though. – 0xC0000022L Nov 02 '22 at 08:50
  • @0xC0000022L No, it doesn't swallow exceptions as there is no `catch` block in the function. The `finally` block just makes sure that the `$ProgressPreference` variable is restored in any case, but exceptions still bubble up the call stack, after the `finally` block has been run. – zett42 Nov 02 '22 at 09:55
  • 1
    Nice idea, thanks! – Endrju Aug 30 '23 at 12:29