Invoke-WebRequest остаётся основным способом выйти в сеть прямо из командной строки Windows, когда под рукой нет ни браузера в headless-режиме, ни сторонних утилит, а задача уже горит: проверить жив ли сайт, забрать лог с удалённого сервера, дёрнуть REST API по расписанию или раз в час смотреть страницу инцидентов поставщика. Командлет входит в базовую поставку как Windows PowerShell 5.1, так и PowerShell 7, так что сценарий ниже работает почти на любой машине с Windows без установки чего-либо. Вместе с тем у него хватает подводных камней: алиас curl, который вовсе не настоящий curl, парсер на базе Internet Explorer, прогресс-бар, способный замедлить скачивание в десятки раз, и тихие различия между платформами. Ниже разобран практический материал, набранный из реальных задач автоматизации, чтобы читатель мог сразу переносить приёмы в свои скрипты.
Синтаксис и базовые приёмы работы
Минимальный вызов выглядит просто: Invoke-WebRequest -Uri "https://example.com/status". На выходе получается объект типа BasicHtmlWebResponseObject, а не сырая строка, и это важно для всего последующего анализа. Командлет поддерживает параметры -Method (Get, Post, Put, Delete и остальные), -Body для тела запроса, -Headers для произвольных заголовков, -ContentType, -TimeoutSec и многие другие. Тело можно передавать строкой, хэш-таблицей или XML-документом; при постах форм хэш-таблица автоматически превращается в application/x-www-form-urlencoded.
Пара практических мелочей. Во-первых, параметр -UseBasicParsing в Windows PowerShell 5.1 отключает разбор HTML через движок Internet Explorer: ответ приходит быстрее, не требуется инициализация браузерного COM-компонента, и скрипт работает на серверах, где старый парсер ругался на незапущенный IE. В PowerShell 7 этот параметр оставлен только для совместимости и игнорируется, поскольку базового движка там попросту нет. Во-вторых, стоит явно задавать -TimeoutSec: значение по умолчанию нулевое и означает бесконечное ожидание, что для ночного задания планировщика недопустимо. В-третьих, для заданий в планировщике полезно фиксировать TLS-версию, потому что старый PowerShell по умолчанию мог договориться до TLS 1.0, а современные сайты такое отклоняют: строка [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 решает проблему до первого запроса.
Частый обёртывающий паттерн для опроса выглядит так: результат сохраняется в переменную, проверяется код, и только после успешной проверки разбирается содержимое. Такой минимальный каркас защищает от ситуации, когда скрипт радостно распарсил страницу с ошибкой 503 и принял её за рабочие данные.
Свойства ответа и чем Invoke-WebRequest отличается от Invoke-RestMethod
Объект ответа богат: StatusCode содержит числовой код (200, 404, 503), StatusDescription добавляет текстовое описание, Content хранит тело ответа в виде строки или массива байтов для бинарных данных, Headers даёт хэш-таблицу заголовков ответа, а RawContentStream предоставляет поток. В Windows PowerShell 5.1 без -UseBasicParsing дополнительно появляются удобные коллекции: Links и Images с уже разобранными тегами, InputFields и Forms с полями форм, а также ParsedHtml, представляющий документ как COM-объект mshtml, через который доступны методы DOM вроде getElementById. В PowerShell 7 этой роскоши нет: там Links и Images тоже заполняются, но простым синтаксическим разбором, а полноценного DOM-парсера нет вовсе.
Главный водораздел проходит между Invoke-WebRequest и Invoke-RestMethod. Первый ориентирован на веб-страницы: он возвращает метаданные ответа целиком и предполагает, что тело будет HTML, который читатель разберёт сам. Второй заточен под JSON и XML API: он автоматически десериализует тело в объекты PowerShell, так что из json-ответа получается PSCustomObject с вложенными свойствами, и можно сразу писать $data.items.Count. Правило просто: для HTML-страницы берётся Invoke-WebRequest, для JSON-эндпоинта Invoke-RestMethod. Попытка взять JSON через Invoke-WebRequest заканчивается ручным вызовом ConvertFrom-Json, а попытка спарсить HTML через Invoke-RestMethod иногда «срабатывает» и возвращает строку, иногда ломается на невалидном с точки зрения XML-разборщика документе.
Когда нужен и статус код, и разобранный JSON в PowerShell 7, помогает связка Invoke-RestMethod с параметром -ResponseHeadersVariable и -StatusCodeVariable: они выносят метаданные в отдельные переменные, а основной объект остаётся десериализованным. В сценариях мониторинга разумно ориентироваться на StatusCode и Content одновременно: код 200 вовсе не гарантирует, что страница содержит искомый фрагмент.
Скачивание файлов, прогресс и почему он тормозит
Параметр -OutFile записывает тело ответа напрямую в файл, минуя конвейер: Invoke-WebRequest -Uri $url -OutFile "C:\temp\report.zip". Это правильный способ скачивать бинарные данные, потому что промежуточное чтение Content в строку бьёт кодировку и раздувает память. Для большого архива в несколько гигабайт разница критична.
Здесь кроется знаменитая ловушка старого PowerShell: в Windows PowerShell 5.1 индикатор прогресса отрисовывается на каждую порцию данных, и скачивание с включённым прогрессом может идти в десять-пятнадцать раз медленнее. Стандартный обходной приём, который использовали годами, состоит в отключении прогресс-бара перед запросом: $ProgressPreference = "SilentlyContinue". После строки скорость выравнивается с сетевой. Аккуратная практика сохраняет прежнее значение переменной и возвращает его после скачивания, чтобы скрипт не портил поведение интерактивной сессии пользователя. В PowerShell 7 проблема практически ушла, но привычка ставить SilentlyContinue в служебных скриптах осталась как дешёвая страховка.
Для действительно огромных файлов иногда выгоднее обратиться к более низкоуровневым средствам: Start-BitsTransfer умеет докачку и работу в фоне, а System.Net.Http.HttpClient через потоковое чтение даёт контроль над буфером. Но если речь о десятках мегабайт и ночном задании, Invoke-WebRequest с отключённым прогрессом и -TimeoutSec справляется надёжно. Отдельный совет: после скачивания проверять размер файла и, если сервер отдаёт заголовок Content-Length, сверять значения, потому что обрыв соединения при -OutFile не всегда бросает исключение ожидаемого вида.
Формы, сессии, куки и авторизация
Реальные сайты редко отдают данные анонимно. Механика сессий в Invoke-WebRequest строится на двух параметрах. -SessionVariable принимает имя переменной, в которую командлет сложит объект сессии после первого запроса: Invoke-WebRequest -Uri $loginUrl -Method Post -Body $form -SessionVariable sv. Далее все последующие вызовы выполняются с -WebSession $sv, и куки, выставленные сервером, автоматически подставляются в запросы. Поверх сессии аккуратно ложатся долгоживущие проверки «залогинен ли ещё клиент»: достаточно периодически запрашивать защищённую страницу и следить, не улетел ли ответ на страницу входа.
Типовая последовательность входа через веб-форму выглядит так: сначала GET страницы логина с SessionVariable, затем разбор полей формы из свойства Forms (в старом PowerShell), подстановка логина и пароля, затем POST. Токены защиты от подделки запроса приходится добывать из скрытых полей или мета-тегов и добавлять в тело вручную. Это рутинная, но рабочая схема для интранет-порталов.
Для API чаще нужна не веб-сессия, а заголовочная авторизация. Basic-авторизация собирается вручную: пара "логин:пароль" кодируется в Base64, и в заголовок Authorization пишется строка вида "Basic <base64>". В Windows PowerShell есть и короткий путь через параметр -Credential с объектом PSCredential. Токенные схемы ещё проще: в заголовок помещается "Bearer <token>", и Invoke-RestMethod дальше работает как обычно. Токен хранится не в коде скрипта, а в переменной окружения, секрет-хранилище или защищённом файле, иначе репозиторий с историей коммитов превращается в каталог чужих ключей.
Отдельная тема - User-Agent. По умолчанию Invoke-WebRequest представляется строкой вроде Mozilla/5.0 с пометкой PowerShell, и многие сайты такие запросы отклоняют или отдают урезанную версию. Параметр -UserAgent позволяет подставить нейтральное значение, что честнее, чем маскироваться под конкретный браузер: "CompanyMonitoringBot/1.0" в заголовке читается администратором удалённой стороны куда спокойнее и одновременно снимает технические фильтры, которые режут явно автоматические клиенты с пустым агентом.
Ошибки, повторы и проверка сертификатов
Надёжный сетевой скрипт обязан переживать 500-е коды и таймауты. В Windows PowerShell Invoke-WebRequest на коды 4xx и 5xx бросает исключение, которое ловится через try/catch, а объект исключения содержит свойство Exception.Response с кодом и телом ошибки. В PowerShell 7 поведение то же, но добавился параметр -SkipHttpErrorCheck, после которого ответ возвращается как обычный объект, и код проверяется вручную. Оба стиля рабочие; выбор зависит от того, насколько «тяжёлой» должна быть ветка обработки.
Практичный шаблон повторов с экспоненциальной задержкой строится на цикле: попытка, проверка кода, при отказе ожидание Start-Sleep, удвоение паузы, ограничение максимальной задержки и общего числа попыток. Повторять стоит только идемпотентные операции (GET, HEAD) и при повторяемых ошибках 429, 502, 503, 504 либо сетевых сбоях; повтор POST с оплатой без ключа идемпотентности - прямой путь к двойным списаниям. Для 429 полезно читать заголовок Retry-After и подчиняться ему.
Сертификаты. Если у сервера самоподписанный или сломанный сертификат, Windows PowerShell откажется подключаться, и обход заключается в подмене коллбэка проверки через [Net.ServicePointManager]::ServerCertificateValidationCallback, что глобально и опасно. В PowerShell 7 появился точечный параметр -SkipCertificateCheck, действующий только на текущий вызов - это и есть рекомендуемый способ обращения к тестовым стендам и внутренним устройствам с самоподписанными сертификатами. В боевых сценариях лучше развернуть нормальный внутренний центр сертификации, а имена проблемных хостов фиксировать в исключениях осознанно, журналируя каждый пропуск проверки.
Проверка живости страницы статуса выливается в компактное задание: запрос, сравнение StatusCode с 200, поиск контрольной строки в Content, запись результата в журнал с отметкой времени и отправка уведомления при двух подряд неудачах, чтобы единичный сетевой сбой не будил дежурного.
Не парсить HTML регулярками и прочие житейские ловушки
Соблазн вытащить цену или статус одной регуляркой понятен, но HTML - не регулярный язык: вложенные теги, атрибуты в любом порядке, экранирование и комментарии гарантированно ломают выражение в самый неподходящий момент, обычно сразу после очередного редизайна сайта. Устойчивые варианты таковы: в старом Windows PowerShell использовать ParsedHtml и методы DOM вроде getElementById и getElementsByClassName через COM-объект mshtml; в современных сценариях подключать библиотеку Html Agility Pack, загружаемую через Add-Type, которая даёт XPath-запросы к документу. XPath-выражение к конкретному узлу с устойчивым идентификатором переживает косметические правки вёрстки, чего с regex не бывает. Регулярным выражениям остаётся ниша одноразовых разборов в консоли, когда точность важнее долговечности.
Напоследок - две ловушки окружения. Алиас curl в Windows PowerShell указывает на Invoke-WebRequest, а не на классический curl: привычные флаги вроде -o или -k там не работают или означают иное, поэтому в скриптах следует вызывать командлет полным именем. Начиная с современных версий Windows в системе присутствует и настоящий curl.exe, и если скрипту нужен именно он, положено писать curl.exe с расширением, иначе снова сработает алиас. Вторая ловушка - политика выполнения: на свежей машине скрипты могут блокироваться политикой Restricted или RemoteSigned для файлов из интернета, поэтому дистрибутив сценария должен предусматривать Set-ExecutionPolicy на уровне машины либо запуск через pwsh с параметром -ExecutionPolicy Bypass для конкретного процесса. Понимая эти границы и набор приёмов выше - базовый синтаксис, свойства ответа, OutFile с отключённым прогрессом, сессии и заголовочная авторизация, дисциплинированные повторы с задержкой и разбор HTML через DOM вместо регулярок - практик превращает Invoke-WebRequest из «командки для одного запроса» в полноценный инструмент ежедневной автоматизации.