Таймаут Invoke-WebRequest и WebClient в PowerShell

У System.Net.WebClient нет свойства Timeout. Варианта три: параметр -TimeoutSec у Invoke-WebRequest и Invoke-RestMethod, свойство Timeout у HttpClient или свой наследник WebClient.

# Веб-командлеты: секунды, 0 (по умолчанию) = без таймаута
Invoke-WebRequest -Uri 'https://example.com/' -UseBasicParsing -TimeoutSec 30
Invoke-RestMethod -Uri 'https://example.com/' -TimeoutSec 30

# HttpClient: Timeout меняют только до первого запроса
Add-Type -AssemblyName System.Net.Http
$http = [System.Net.Http.HttpClient]::new()
$http.Timeout = [TimeSpan]::FromSeconds(30)
$html = $http.GetStringAsync('https://example.com/').GetAwaiter().GetResult()

Таймаут WebClient: наследник через Add-Type

Переопределяем GetWebRequest и ставим таймаут каждому запросу:

Add-Type -IgnoreWarnings -TypeDefinition @"
using System;
using System.Net;

public class TimeoutWebClient : WebClient
{
    public int TimeoutMs = 30000;

    protected override WebRequest GetWebRequest(Uri address)
    {
        WebRequest request = base.GetWebRequest(address);
        if (request != null)
        {
            request.Timeout = TimeoutMs;
            HttpWebRequest http = request as HttpWebRequest;
            if (http != null) { http.ReadWriteTimeout = TimeoutMs; }
        }
        return request;
    }
}
"@

$wc = New-Object TimeoutWebClient
$wc.TimeoutMs = 10000   # миллисекунды
$wc.DownloadString('https://example.com/')

Почему код такой:

  • Timeout — сколько ждать ответа (по умолчанию 100 секунд), ReadWriteTimeout — сколько ждать каждой порции тела (по умолчанию 5 минут). В моём тесте сервер отдал заголовки и замолчал на 8 секунд: ReadWriteTimeout на 3 секунды сработал, Timeout — нет.
  • В PowerShell 7 нужен ключ -IgnoreWarnings. WebClient устарел в .NET 6 (SYSLIB0014), а Add-Type считает предупреждения ошибками. Без ключа получите Cannot add type. Compilation errors occurred.
  • Код на C# обходится без нового синтаксиса, чтобы тот же блок собрался и в Windows PowerShell 5.1 со старым компилятором CodeDom. Тип в сессии не переопределить: поменяли класс — переименуйте его или перезапустите PowerShell.

Invoke-WebRequest -TimeoutSec: что он ограничивает

-TimeoutSec действует, только пока не пришли заголовки ответа: сюда входят DNS, подключение и время, пока сервер думает. На скачивание тела он не влияет. С -TimeoutSec 3 ответ, который сразу отдал заголовки и потом шёл 8 секунд, скачался без ошибок.

В PowerShell 7.4 параметр переименовали в -ConnectionTimeoutSeconds (-TimeoutSec остался псевдонимом) и добавили -OperationTimeoutSeconds — максимальную паузу между порциями данных. Когда качаете файлы, ставьте оба:

Invoke-WebRequest -Uri $url -OutFile $file -ConnectionTimeoutSeconds 15 -OperationTimeoutSeconds 60

Общее время не ограничивает ни один из них. Жёсткий лимит даёт HttpClient.Timeout: он действует на весь вызов GetStringAsync вместе с телом.

Ещё две детали:

  • -MaximumRetryCount не повторяет запрос после таймаута: с -TimeoutSec 2 -MaximumRetryCount 2 вызов упал через 2 секунды.
  • В Windows PowerShell 5.1 есть только -TimeoutSec, и медленный DNS-запрос может оттянуть таймаут до 15 секунд и дольше (документация Microsoft). В PowerShell 7 такого нет. Добавляйте в 5.1 -UseBasicParsing: после обновления безопасности от декабря 2025 года Invoke-WebRequest без него останавливается и спрашивает подтверждение.

«The operation has timed out» и DownloadDataInternal

Так выглядит таймаут в Windows PowerShell 5.1 или .NET Framework:

Exception calling "DownloadString" with "1" argument(s): "The operation has timed out"

System.Net.WebException: The operation has timed out
   at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
   at System.Net.WebClient.DownloadString(Uri address)

PowerShell 7.5 ставит в конце сообщения точку и добавляет сверху ещё три строки: HttpWebRequest.GetResponse(), GetWebResponse и DownloadBits.

В .NET Framework DownloadDataInternal открывает трассировку любой ошибки DownloadString и DownloadData (throw e; сбрасывает стек), так что смотрите на текст сообщения:

  • «The operation has timed out»: сервер не ответил за HttpWebRequest.Timeout (по умолчанию 100 секунд). Поменяйте значение через наследника выше или перейдите на командлеты. Для ASP.NET за корпоративным прокси проверьте настройки прокси в web.config.
  • «(407) Proxy Authentication Required»: задайте $wc.Proxy.Credentials = [Net.CredentialCache]::DefaultNetworkCredentials или useDefaultCredentials="true" у <defaultProxy> в web.config.
  • «Could not create SSL/TLS secure channel»: перед запросом выполните [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 'Tls12'.

Таймаут ловите по типу исключения и смотрите на Status:

try {
    $wc.DownloadString($url)
}
catch [System.Net.WebException] {
    if ($_.Exception.Status -eq [System.Net.WebExceptionStatus]::Timeout) {
        Write-Warning "Таймаут: $url"
    }
    else { throw }
}

Если зависло тело ответа (сработал ReadWriteTimeout), PowerShell 7 пишет другое: «An exception occurred during a WebClient request.», Status равен UnknownError, внутри лежит IOException. Проверка Status такой случай пропустит, поэтому допишите в условие -or $_.Exception.InnerException -is [System.IO.IOException].

Invoke-WebRequest в PowerShell 7 по таймауту подключения бросает TaskCanceledException («The request was canceled due to the configured HttpClient.Timeout of 30 seconds elapsing.»), а по таймауту операции — TimeoutException (с -OutFile он приходит внутри AggregateException):

try {
    Invoke-RestMethod -Uri $url -TimeoutSec 30
}
catch [System.Threading.Tasks.TaskCanceledException] {
    Write-Warning "Нет ответа за 30 секунд: $url"
}

Windows PowerShell 5.1 здесь бросает WebException (документация Microsoft), поэтому там подойдёт вариант для WebClient.

Асинхронные методы WebClient игнорируют Timeout в Windows PowerShell 5.1

В .NET Framework HttpWebRequest.Timeout не действует на асинхронные запросы, а DownloadStringAsync, DownloadFileAsync и их версии TaskAsync работают через BeginGetResponse. В PowerShell 7 DownloadStringTaskAsync в моём тесте оборвался по таймауту через 3 секунды. В 5.1 берите синхронные методы или HttpClient.

Таймаут WebClient в C#

Для .NET 6+ Microsoft советует HttpClient:

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
try
{
    Console.WriteLine(await http.GetStringAsync(url));
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    Console.WriteLine("Таймаут");
}

Если старый код завязан на WebClient:

class TimeoutWebClient : WebClient
{
    public int TimeoutMs { get; set; } = 30_000;

    protected override WebRequest GetWebRequest(Uri address)
    {
        var request = base.GetWebRequest(address);
        request.Timeout = TimeoutMs;
        if (request is HttpWebRequest http) http.ReadWriteTimeout = TimeoutMs;
        return request;
    }
}

В .NET 6+ добавьте #pragma warning disable SYSLIB0014.

Консультирую по продукту, собираю MVP и учу делать продукты самостоятельно. Поработать со мной →