PowerShell запрос через прокси: параметры командлетов, WebProxy и планировщик заданий
- Что понадобится
- Параметры Invoke-WebRequest и Invoke-RestMethod для работы через посредник
- Логин и пароль объектом учётных данных
- Класс System.Net.WebProxy, когда нужен точный контроль
- Переменные окружения и их видимость внутри сеанса
- Повторы и таймауты в длинном прогоне
- Запуск скрипта в планировщике заданий Windows
- Служебная учётная запись, привязка адреса и журнал отказов
- Проверка после настройки
- Разбор ошибок
PowerShell отправляет исходящие запросы командлетами Invoke-WebRequest и Invoke-RestMethod поверх сетевого стека .NET, и адрес посредника попадает в них тремя путями: параметром самого вызова, объектом прокси внутри сеанса либо переменной окружения. Страница разбирает все три пути, показывает передачу логина с паролем объектом учётных данных и переносит готовый файл сценария в задание планировщика Windows.
Я держу на рабочем сервере три сценария выгрузки. Первый снимает позиции каждые 20 минут, второй раз в сутки забирает отчёт с партнёрского API, третий обходит список из 340 адресов и складывает коды ответов в таблицу. Все три ходят наружу через посредник. Из моей консоли они отрабатывали ровно, а после переноса в планировщик начали возвращать отказы на первом же запросе, и разбираться пришлось долго. Ниже собран порядок, который эту разницу убрал.
1. Что понадобится
Подойдёт Windows PowerShell 5.1 из поставки системы либо PowerShell 7. Версию печатает одна строка:
$PSVersionTable.PSVersion
Разница между ветками сидит ровно в тех местах, про которые эта страница. В 5.1 командлеты построены на классе HttpWebRequest: переменные окружения они пропускают и смотрят на системную настройку WinINET. В 7 под капотом HttpClient, который читает HTTP_PROXY и HTTPS_PROXY сам. Держите в голове, какая версия запускает ваш файл: планировщик вызывает powershell.exe для 5.1 и pwsh.exe для 7, и подмена одного другим меняет поведение сети целиком.
Строка подключения нужна в одном из двух видов: IP:PORT при авторизации по привязанному адресу и IP:PORT:LOGIN:PASS при доступе по паре логина и пароля. Примеры ниже написаны на адресе 203.0.113.31 и порте 8000, подставляйте свои значения без изменения структуры строк. Список у меня выдаётся ссылкой сразу в обоих форматах, и я беру нужную форму без ручной разборки; под сценарии я использую приватные серверные адреса для скриптов с двумя привязками в пакете и сменой привязки в кабинете.
Для раздела про расписание понадобятся права на создание заданий и модуль ScheduledTasks, он входит в систему. Отдельно приготовьте служебную учётную запись, если сценарий будет ходить наружу ночью. Включение пакета занимает около 5 минут, поэтому отлаживать вызовы раньше, чем адрес начал отвечать, смысла нет. Бесплатный тест до 2 часов покрывает и проверку в консоли, и первый прогон по расписанию.
2. Параметры Invoke-WebRequest и Invoke-RestMethod для работы через посредник
Оба командлета делят один набор сетевых параметров. Invoke-RestMethod дополнительно разбирает ответ JSON в объект, Invoke-WebRequest отдаёт сырой ответ с заголовками и кодом. Всё, что написано про посредника, работает у обоих одинаково.
| Параметр | Что делает | Где доступен | Замечание |
|---|---|---|---|
-Proxy | адрес посредника целиком, со схемой и портом | 5.1 и 7 | принимает тип Uri, строка без http:// отвергается |
-ProxyCredential | пара логина и пароля для посредника | 5.1 и 7 | ждёт объект PSCredential, обычную строку не примет |
-ProxyUseDefaultCredentials | подставляет учётную запись текущего сеанса Windows | 5.1 и 7 | рассчитан на корпоративный шлюз с NTLM |
-NoProxy | отключает посредника для одного вызова | только 7 | перебивает и переменные окружения, и настройку сеанса |
-TimeoutSec | верхняя граница ожидания ответа | 5.1 и 7 | значение 0 снимает ограничение сверху |
-ConnectionTimeoutSec | отдельная граница на установку соединения | 7.4 и новее | отделяет закрытый порт от медленного сайта |
-MaximumRetryCount | число автоматических повторов | только 7 | повторяет коды 5xx и 408, обрывы соединения пропускает |
-RetryIntervalSec | пауза между повторами | только 7 | пауза постоянная, роста интервала нет |
-UseBasicParsing | разбор ответа без движка Internet Explorer | 5.1 | в 7 подразумевается всегда |
-SkipCertificateCheck | пропуск проверки сертификата | только 7 | для посредника с подменой цепочки |
Схема в адресе посредника всегда http://, включая случай, когда сам запрос уходит по HTTPS. Схема описывает канал до посредника, целевой протокол задаётся адресом в параметре -Uri. Строка вида https://203.0.113.31:8000 порту не понравится, и клиент вернёт отказ на согласовании шифрования.
Повторять три одинаковых параметра в каждом вызове скучно, поэтому я собираю их в таблицу и подставляю оператором splatting:
$common = @{
Proxy = 'http://203.0.113.31:8000'
TimeoutSec = 40
UserAgent = 'ops-fetch/1.4'
Headers = @{ 'Accept-Language' = 'ru-RU,ru;q=0.9' }
}
$data = Invoke-RestMethod -Uri 'https://api.example.net/v1/rows' @common
$page = Invoke-WebRequest -Uri 'https://example.net/catalog' @common -UseBasicParsing
$page.StatusCode
Такой набор правится в одном месте, и адрес посредника перестаёт расползаться по файлу копиями. Когда я перевожу сценарий с прямого канала на посредника, правка занимает одну строку.
Полезная мелочь для отладки: в PowerShell 7 добавьте к вызову ключ -SkipHttpErrorCheck, и командлет вернёт ответ с кодом 403 либо 407 обычным объектом, без исключения. Разбирать содержимое ответа посредника так удобнее.
3. Логин и пароль объектом учётных данных
Параметр -ProxyCredential принимает только объект PSCredential. Строка u7391:Kf3zR8pQ в него не пройдёт, и попытка передать её заканчивается ошибкой приведения типа. Интерактивно объект собирает Get-Credential, но в сценарии по расписанию окно запроса пароля неуместно, поэтому объект собирается кодом:
$user = 'u7391'
$pass = ConvertTo-SecureString 'Kf3zR8pQ' -AsPlainText -Force
$cred = [pscredential]::new($user, $pass)
Invoke-RestMethod -Uri 'https://api.ipify.org' `
-Proxy 'http://203.0.113.31:8000' `
-ProxyCredential $cred
Пароль открытой строкой в файле меня устраивает ровно до первого ревью. Дальше я перекладываю его в зашифрованный файл. PowerShell шифрует SecureString механизмом DPAPI, и ключ привязан к учётной записи Windows и к машине:
Read-Host -Prompt 'пароль посредника' -AsSecureString |
ConvertFrom-SecureString |
Set-Content -Path 'C:\ops\secure\proxy.txt' -Encoding ASCII
$pass = Get-Content 'C:\ops\secure\proxy.txt' | ConvertTo-SecureString
$cred = [pscredential]::new('u7391', $pass)
Запомните привязку ключа, к ней я вернусь в разделе про планировщик. Файл, созданный под моей учётной записью, служебная учётка прочитать не сможет: расшифровка завершится сообщением про недопустимое состояние ключа. Создавать такой файл нужно от имени той учётки, которая будет его читать.
Параметр -ProxyUseDefaultCredentials делает другое. Он отдаёт посреднику текущую учётную запись Windows по протоколу NTLM либо Kerberos, что подходит внутреннему шлюзу предприятия. Для пары логина и пароля от поставщика этот ключ бесполезен, здесь нужен именно объект учётных данных.
Есть путь короче. Привязка внешнего адреса машины в кабинете снимает вопрос о пароле целиком: порт пускает вас по адресу источника, и в файле сценария не остаётся ни одной строки с секретом. Для машины со стабильным внешним адресом я выбираю именно так, привязок в пакете две и меняются они в кабинете свободно. Ночное задание при этом стартует молча, без диалогов и без файла с ключом на диске. Так же устроены прогоны по готовым шаблонам, адреса под них я разбирал на странице про пул под запуск шаблонов ZennoPoster.
4. Класс System.Net.WebProxy, когда нужен точный контроль
Параметр -Proxy задаёт посредника на один вызов. Когда в файле сорок вызовов, а часть адресов должна идти напрямую, удобнее собрать объект прокси и повесить его на весь сеанс:
$proxy = [System.Net.WebProxy]::new('http://203.0.113.31:8000', $true)
$proxy.BypassList = @(
'localhost',
'127\.0\.0\.1',
'.*\.corp\.local$',
'^10\.'
)
$proxy.Credentials = $cred
[System.Net.WebRequest]::DefaultWebProxy = $proxy
Первая тонкость сидит в списке исключений. Записи в BypassList разбираются как регулярные выражения, звёздочка из системного поля Windows здесь работать не будет. Точку нужно экранировать, иначе она совпадёт с любым символом, и запись 10.0.0.5 неожиданно накроет 10a0b0c5. Я пишу список сразу в форме регулярных выражений и проверяю каждую запись отдельно.
Вторая тонкость в аргументе конструктора. Логическая истина вторым параметром включает обход посредника для локальных имён без точки, тот же смысл, что у флажка в графическом окне системы.
Проверить попадание конкретного адреса под исключения можно на месте:
$proxy.IsBypassed([uri]'https://api.example.net/') # False
$proxy.IsBypassed([uri]'http://srv1.corp.local/') # True
$proxy.GetProxy([uri]'https://api.example.net/')
Третья тонкость касается версий. Свойство DefaultWebProxy класса WebRequest управляет старым стеком, на котором работают Windows PowerShell 5.1, WebClient и HttpWebRequest. В PowerShell 7 командлеты собирают свой обработчик HttpClient, и умолчание для него живёт в другом месте:
[System.Net.Http.HttpClient]::DefaultProxy = $proxy
Обе строки в сценарии не мешают друг другу, и я ставлю их рядом в шапке файла. Тогда один и тот же текст отрабатывает и под powershell.exe, и под pwsh.exe, что для машины с двумя ветками избавляет меня от отдельных версий сценария.
Учётные данные, положенные в свойство Credentials объекта, распространяются на все вызовы сеанса. Отдельный вызов при этом можно увести мимо: явный -Proxy в командлете перебивает умолчание, а ключ -NoProxy в семёрке отправляет запрос напрямую.
5. Переменные окружения и их видимость внутри сеанса
PowerShell 7 читает HTTP_PROXY, HTTPS_PROXY, ALL_PROXY и NO_PROXY при первом обращении к умолчанию HttpClient. Значение берётся один раз за процесс, поэтому правка переменной посреди сеанса на уже выполненные вызовы никак не влияет. Windows PowerShell 5.1 эти имена пропускает полностью и смотрит на системную настройку.
Задать переменную можно четырьмя способами, и у каждого своя область видимости. Путаница между ними даёт самый частый сюжет: в консоли всё работает, из планировщика запросы уходят напрямую.
| Способ задать | Область | Видит текущая консоль | Видит задание планировщика | Переживает перезагрузку |
|---|---|---|---|---|
присваивание через префикс env: | текущий процесс | да | нет | нет |
SetEnvironmentVariable с областью Process | текущий процесс | да | нет | нет |
SetEnvironmentVariable с областью User | профиль учётной записи | нет, до перезапуска консоли | да, если задание идёт от той же учётки | да |
SetEnvironmentVariable с областью Machine | вся машина | нет, до перезапуска консоли | да, включая сеанс SYSTEM | да |
| строка в файле профиля | сеансы этой учётки | да, после перезапуска | нет при ключе -NoProfile | да |
| присваивание в самом файле сценария | процесс задания | да | да | да |
Постоянные значения ставятся так:
[Environment]::SetEnvironmentVariable('HTTPS_PROXY','http://203.0.113.31:8000','Machine')
[Environment]::SetEnvironmentVariable('HTTP_PROXY','http://203.0.113.31:8000','Machine')
[Environment]::SetEnvironmentVariable('NO_PROXY','localhost,127.0.0.1,.corp.local','Machine')
Открытая консоль новых значений не увидит. Область Machine пишется в реестр, а окружение процесса копируется в момент его старта, поэтому проверять правку нужно в заново открытом окне. Я на этом потерял вечер, пока не завёл привычку закрывать консоль после каждой правки области Machine.
Разделитель в списке исключений здесь запятая, без пробелов. Ведущая точка работает маской поддоменов, звёздочка в реализации .NET понимается не везде, поэтому я пишу и короткое имя, и форму с точкой подряд. Строка no_proxy в нижнем регистре читается тоже, и на машинах со смешанным набором утилит я задаю оба написания.
Проверить, что видит именно текущий процесс, помогает пара строк:
Get-ChildItem env: | Where-Object Name -match 'proxy'
[System.Net.Http.HttpClient]::DefaultProxy.GetProxy([uri]'https://example.net/')
Вторая строка печатает адрес, на который уйдёт запрос. Пустой ответ означает прямой канал. Для машин, где переменные раздаются групповой политикой на весь парк, я держу отдельный порт под консольные утилиты и беру HTTP-прокси для запросов из консоли: одна короткая строка ложится и в переменную окружения, и в параметр командлета без переделки.
6. Повторы и таймауты в длинном прогоне
Значение -TimeoutSec по умолчанию равно нулю, и ожидание сверху не ограничено. Для интерактивного вызова это терпимо. Для задания по расписанию это прямой путь к процессу, который висит сутки и мешает следующему запуску. Я ставлю таймаут всегда.
В PowerShell 7.4 появилась пара более точных параметров. -ConnectionTimeoutSec ограничивает установку соединения с посредником, -OperationTimeoutSec ограничивает чтение ответа. Разведя их, вы отличаете закрытый порт от медленного целевого сайта прямо по тексту ошибки, и это экономит половину времени разбора.
Встроенные повторы в семёрке срабатывают на кодах 5xx и 408. Обрыв соединения, отказ по времени и код 407 они пропускают, поэтому свой цикл всё равно нужен:
function Invoke-ViaProxy {
param(
[hashtable] $Call,
[int] $Tries = 4
)
for ($n = 1; $n -le $Tries; $n++) {
try {
return Invoke-RestMethod @Call
}
catch {
$pause = [math]::Min(45, 3 * $n * $n)
Write-Warning ("попытка {0} из {1}: {2}. Пауза {3} c" -f `
$n, $Tries, $_.Exception.Message, $pause)
Start-Sleep -Seconds $pause
}
}
throw ("адрес {0} молчит после {1} попыток" -f $Call.Uri, $Tries)
}
Invoke-ViaProxy -Call @{
Uri = 'https://api.example.net/v1/rows'
Proxy = 'http://203.0.113.31:8000'
TimeoutSec = 30
}
Пауза растёт квадратично и упирается в 45 секунд. Постоянный интервал в 5 секунд у меня вёл к тому, что все четыре попытки укладывались в одну минуту и целевой сайт отдавал 429 на каждой.
Отдельная строка нужна на Windows PowerShell 5.1, где число одновременных соединений к одному хосту по умолчанию равно двум. Параллельный обход списка из 340 адресов упирается в это ограничение раньше, чем в порт посредника:
[System.Net.ServicePointManager]::DefaultConnectionLimit = 64
[System.Net.ServicePointManager]::Expect100Continue = $false
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Третья строка спасает от отказов на согласовании шифрования: сборки 5.1 по умолчанию предлагают устаревший набор протоколов, и часть сайтов такое соединение закрывает молча. Ночные прогоны с сотнями страниц я гоняю на пакете, где обмен не считают, и потолок упирается только в число потоков. Ту же арифметику по потокам на массовом сборе я расписывал на странице про прокси для многопоточного сбора A-Parser.
Про потоки помните отдельно. Обычный пакет даёт 1000, корпоративный до 3000, пакеты по потокам не складываются. При двух привязанных адресах лимит делится между ними пополам, и параллельный обход на 700 потоков со второй активной привязкой начнёт получать отказы на установке соединения. Считайте параллелизм по фактической половине.
7. Запуск скрипта в планировщике заданий Windows
Задание регистрируется четырьмя объектами: действие, триггер, набор настроек и сама регистрация под учётной записью.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument @'
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\ops\fetch.ps1"
'@
$trigger = New-ScheduledTaskTrigger -Daily -At 03:20
$set = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-StartWhenAvailable `
-RestartCount 2 -RestartInterval (New-TimeSpan -Minutes 10)
Register-ScheduledTask -TaskName 'ops-fetch' -Action $action -Trigger $trigger `
-Settings $set -User 'CORP\svc-ops' -Password 'подставьте свой' -RunLevel Limited
Кавычки вокруг пути обязательны, если в нём есть пробел. Ключ -File передаёт файл целиком, -Command передаёт строку кода, и смешение этих двух форм даёт код последнего запуска 0x1 без единой подсказки в журнале.
Теперь главное про перенос. Задание стартует свежим процессом от указанной учётной записи, и почти ничего из вашей рабочей обстановки в него не попадает.
| Что настроено в вашей консоли | Видит ли задание | Почему |
|---|---|---|
системная страница «Параметры», ветка HKCU | нет, при запуске от другой учётки | настройка лежит в профиле конкретного пользователя |
netsh winhttp set proxy, ветка HKLM | да | машинное хранилище WinHTTP общее для всех сеансов |
переменная окружения области User | только при той же учётной записи | значение читается из профиля владельца |
переменная окружения области Machine | да | значение лежит в реестре машины |
| строки в файле профиля PowerShell | нет при ключе -NoProfile | профиль сознательно пропускается |
| файл с паролем, зашифрованный DPAPI | нет | ключ привязан к учётной записи создателя |
| сопоставленные сетевые диски | нет | буквы дисков создаются при интерактивном входе |
| текущий рабочий каталог | нет | процесс стартует в C:\Windows\System32 |
Отсюда простое правило, к которому я пришёл: настройка посредника живёт внутри файла сценария. Никаких переменных из профиля, никакой опоры на страницу «Параметры». Шапка файла задаёт адрес, список исключений и учётные данные явно, и тогда результат прогона в консоли совпадает с результатом по расписанию.
Второе правило про относительные пути. Строка .\out\rows.csv в задании превратится в C:\Windows\System32\out\rows.csv, куда служебная учётка писать не имеет права. Я задаю рабочий каталог первой строкой файла через Set-Location либо собираю пути от корня.
Флажок «Выполнять вне зависимости от регистрации пользователя» переводит задание в фоновый режим без загруженного профиля. Обращения к ветке HKCU в таком сеансе ведут себя непредсказуемо, и ещё одна причина держать настройку в самом файле именно здесь.
8. Служебная учётная запись, привязка адреса и журнал отказов
Служебной учётной записи нужно право «Вход в качестве пакетного задания», иначе планировщик откажет ещё до старта процесса. Право выдаётся в локальной политике безопасности, раздел «Назначение прав пользователя». Без него в журнале появится код 0x80070569, а в тексте сообщения будет сказано про несостоявшийся вход.
Дальше про адрес. Посредник видит внешний адрес машины, и смена учётной записи внутри Windows на него никак не влияет. Привязывать в кабинете нужно именно внешний адрес сервера, каким его видит внешняя сторона:
Invoke-RestMethod -Uri 'https://api.ipify.org' -NoProxy
Get-NetIPAddress -AddressFamily IPv4 -PrefixOrigin Dhcp, Manual |
Select-Object IPAddress, InterfaceAlias
Первая строка печатает адрес, который увидит порт посредника. Вторая перечисляет локальные интерфейсы, они в кабинет не вносятся. У сервера за NAT эти значения различаются, и путаница между ними даёт ровный поток отказов 403 на каждом ночном прогоне.
Накопление отказов по расписанию выглядит коварно именно потому, что днём никто на него не смотрит. Задание раз в 20 минут даёт 72 прогона за ночь. Если каждый оставляет после себя незакрытое соединение или упирается в отказ авторизации, к утру в журнале лежит длинный хвост, а таблица с результатами пустая. Я закрываю это тремя приёмами.
Первый: настройка -MultipleInstances IgnoreNew запрещает наложение экземпляров. Второй: -ExecutionTimeLimit снимает зависший прогон принудительно. Третий: каждый прогон пишет строку в общий файл журнала, и утренний взгляд на этот файл занимает 10 секунд.
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$code = 0
$seen = ''
try {
$r = Invoke-WebRequest -Uri 'https://api.ipify.org' @common -UseBasicParsing
$code = $r.StatusCode
$seen = $r.Content.Trim()
}
catch {
$code = -1
$seen = $_.Exception.Message
}
[pscustomobject]@{
Метка = Get-Date -Format 'MM-dd HH:mm:ss'
Код = $code
Адрес = $seen
Секунды = [math]::Round($sw.Elapsed.TotalSeconds, 2)
} | Export-Csv -Path 'C:\ops\log\fetch.csv' -Append -NoTypeInformation -Encoding UTF8
Такая таблица показывает две вещи сразу: сколько прогонов прошло и какой внешний адрес видел каждый. Значения в колонке адреса будут разными от прогона к прогону, потому что ротация внутри пула идёт автоматически и конкретный узел на выходе меняется сам. Пул примерно на 12 000 адресов делает эту смену незаметной для сценария, пока строка подключения одна.
Полную картину по самому заданию отдаёт планировщик:
Get-ScheduledTaskInfo -TaskName 'ops-fetch' |
Select-Object LastRunTime, LastTaskResult, NumberOfMissedRuns, NextRunTime
Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 40 |
Where-Object Message -match 'ops-fetch' |
Select-Object TimeCreated, Id, LevelDisplayName, Message |
Format-List
Поле NumberOfMissedRuns показывает пропуски, поле LastTaskResult печатает код завершения. Коды разобраны в таблице ошибок ниже. Для серверов, где такие задания крутятся месяцами без правок, я беру серверный доступ к пулу под задания планировщика: строка подключения не меняется, привязка правится в кабинете, и файл сценария остаётся нетронутым.
9. Проверка после настройки
Первым делом смотрим, что порт вообще отвечает с этой машины. Отказ на этом шаге снимает половину дальнейших вопросов:
Test-NetConnection -ComputerName 203.0.113.31 -Port 8000 -InformationLevel Detailed
Дальше сверяем два адреса подряд, прямой и через посредника:
$direct = Invoke-RestMethod -Uri 'https://api.ipify.org' -NoProxy
$viaP = Invoke-RestMethod -Uri 'https://api.ipify.org' -Proxy 'http://203.0.113.31:8000'
"прямо: $direct ; через посредника: $viaP"
Значения обязаны различаться. Совпадение говорит, что запрос ушёл мимо посредника, и причину ищем в списке исключений либо в версии PowerShell. Повторите вызов несколько раз: второе значение будет меняться, ротация внутри пула работает без вашего участия.
Третья проверка касается заголовков ответа. Она показывает, дошёл ли запрос до цели через туннель:
$r = Invoke-WebRequest -Uri 'https://example.net/' @common -UseBasicParsing
$r.StatusCode
$r.BaseResponse.RequestMessage.RequestUri
$r.Headers['Via']
Самая полезная проверка идёт последней, и делать её нужно от имени служебной учётной записи. Заведите разовое задание, которое печатает окружение и внешний адрес в файл, запустите его руками и прочитайте результат:
@'
"учётка: $(whoami)" | Set-Content C:\ops\probe.txt
Get-ChildItem env: | Where-Object Name -match 'proxy' |
ForEach-Object { "$($_.Name)=$($_.Value)" } | Add-Content C:\ops\probe.txt
(Invoke-RestMethod -Uri 'https://api.ipify.org' -Proxy 'http://203.0.113.31:8000') |
Add-Content C:\ops\probe.txt
'@ | Set-Content C:\ops\probe.ps1 -Encoding UTF8
Start-ScheduledTask -TaskName 'ops-probe'
Start-Sleep -Seconds 15
Get-Content C:\ops\probe.txt
Файл покажет три строки: под какой учёткой шёл прогон, какие переменные окружения видел процесс и какой внешний адрес вернул порт. Я прогоняю эту пробу после каждой правки задания, привычка стоит 15 секунд и закрывает почти весь класс расхождений между консолью и расписанием. Для стендов, которые живут дольше пары суток, я перевожу пробу на месячный срок доступа под стенд, чтобы срок доступа перекрывал весь цикл наблюдений.
10. Разбор ошибок
| Сообщение | Причина | Что делать |
|---|---|---|
Cannot convert value "203.0.113.31:8000" to type "System.Uri" | в -Proxy передана строка без схемы | написать http:// перед адресом |
Unable to connect to the remote server | порт посредника закрыт исходящим фильтром либо в адресе опечатка | прогнать Test-NetConnection до порта |
The remote server returned an error: (407) Proxy Authentication Required. | пароль не доехал до вызова | добавить -ProxyCredential либо перейти на привязку адреса |
The proxy tunnel request to proxy 'http://203.0.113.31:8000' failed with status code '407' | то же самое в PowerShell 7, где туннель строит HttpClient | пересобрать объект учётных данных, проверить логин |
The remote server returned an error: (403) Forbidden. | внешний адрес машины в кабинете отсутствует | внести внешний адрес сервера и повторить прогон |
Key not valid for use in specified state. | файл с паролем зашифрован под другой учётной записью | пересоздать файл от имени служебной учётки |
The underlying connection was closed: An unexpected error occurred on a send. | в 5.1 согласование шифрования упирается в устаревший протокол | выставить SecurityProtocol в Tls12 в шапке файла |
The operation has timed out. | ответ не пришёл за отведённое время | поднять -TimeoutSec, развести подключение и чтение в 7.4 |
Response status code does not indicate success: 429 (Too Many Requests) | темп запросов выше того, что принимает целевой сайт | увеличить паузу в цикле повторов, снизить параллелизм |
The SSL connection could not be established, see inner exception. | цепочка сертификатов неполная либо посредник подменяет её | добавить корневой сертификат в хранилище машины |
Cannot bind argument to parameter 'ProxyCredential' because it is null | файл с паролем пуст либо путь к нему разобран от System32 | задать полный путь, проверить содержимое файла |
Код последнего запуска 0x1, из консоли всё работает | в задании стоит -NoProfile, настройка посредника лежала в профиле | перенести адрес и учётные данные в сам файл сценария |
Код последнего запуска 0x41301 держится часами | запрос завис без верхней границы ожидания | задать -TimeoutSec и ExecutionTimeLimit |
Код последнего запуска 0x41303 | задание ни разу не запускалось | включить триггер, проверить условие питания в настройках |
Код 0x80070569 в журнале планировщика | у служебной учётки нет права входа как пакетное задание | выдать право «Вход в качестве пакетного задания» |
Код 0x80070005 при записи файла | служебной учётке закрыт каталог выгрузки | выдать права на запись в каталог журнала и выгрузок |
Две строки заслуживают комментария. Сообщение про недопустимое состояние ключа я получал трижды, и каждый раз причина была одна: файл с паролем я готовил под своей учётной записью, а задание шло от служебной. Порядок правильный такой: войти под служебной учёткой либо запустить консоль от её имени, там создать файл, там же проверить расшифровку.
Вторая строка про код 0x1 при рабочем ручном запуске. Отладка тут короткая: добавьте в начало файла запись окружения в текстовый файл, запустите задание и сравните вывод с тем, что печатает ваша консоль. Расхождение будет видно в первой же строке, и дальше остаётся перенести недостающие значения внутрь сценария.
Смежные сценарии серверной стороны разобраны на соседних страницах справочника: контейнеры и общая сборка описаны в материале про прокси в Docker и docker-compose, обмен с внешними сервисами из учётной системы в статье про настройку посредника в 1С, выгрузка результатов наружу в инструкции про Google Таблицы и Apps Script. Если тот же сервер должен отдавать посредника всем программам сразу, начните с раздела про системные сетевые параметры Windows 11 и команду netsh winhttp.