VPN-подключение через PowerShell и планировщик задач
Ручное подключение VPN каждый раз, когда ноутбук цепляется к открытому Wi-Fi в кафе или аэропорту, — это лишний клик, который рано или поздно забудут сделать. Через PowerShell VPN-профиль создаётся один раз и навсегда прописывается в системе, а связка с планировщиком задач берёт на себя автоматический старт: по расписанию, при входе в систему или по конкретному событию сети. Ниже — рабочие скрипты для обоих сценариев, без магии и с честными оговорками, где что может пойти не так.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Add-VpnConnection: создаём подключение через PowerShell
Командлет Add-VpnConnection регистрирует VPN-профиль в системном хранилище подключений — то же самое, что вы обычно делаете через «Параметры → Сеть → VPN», только воспроизводимо и без мышки. Базовый вызов для IKEv2:
Add-VpnConnection -Name "WorkVPN" `
-ServerAddress "vpn.example.com" `
-TunnelType Ikev2 `
-AuthenticationMethod EAP `
-EncryptionLevel Required `
-SplitTunneling $false `
-RememberCredential
Для SSTP (он же обычно самый удобный вариант для VPS, потому что ходит через 443 порт и не режется корпоративными фаерволами) параметр TunnelType меняется на Sstp, а сертификат сервера должен быть в доверенных корневых — иначе подключение упадёт с ошибкой проверки цепочки.
Если сервер требует сертификат клиента, а не логин/пароль, тип аутентификации меняется на MachineCertificate:
Add-VpnConnection -Name "WorkVPN" -ServerAddress "vpn.example.com" `
-TunnelType Ikev2 -AuthenticationMethod MachineCertificate
Отдельно для IKEv2 часто нужно донастроить параметры IPsec — таймауты, алгоритмы шифрования, поведение при потере связи:
Set-VpnConnectionIPsecConfiguration -ConnectionName "WorkVPN" `
-AuthenticationTransformConstants GCMAES128 `
-CipherTransformConstants GCMAES128 `
-EncryptionMethod AES256 -IntegrityCheckMethod SHA256 `
-PfsGroup PFS2048 -DHGroup Group14 -Force
Проверить, что профиль создался и с какими параметрами, можно так:
Get-VpnConnection -Name "WorkVPN" | Format-List *
Всё это выполняется от имени администратора один раз при разворачивании машины — дальше профиль просто есть в системе, и его можно вызывать по имени из планировщика.
rasdial: подключение и отключение из скрипта
Add-VpnConnection только создаёт профиль, но не поднимает соединение. Для самого подключения из скрипта проще и надёжнее старая утилита rasdial — она не тянет модуль VpnClient, работает быстро и предсказуемо возвращает код завершения:
rasdial "WorkVPN" username password
Если аутентификация идёт по сертификату или EAP без пароля, поле пароля можно опустить или подставить *, чтобы система запросила его интерактивно (для автоматизации это не подходит — нужен либо сохранённый credential, либо -RememberCredential при создании профиля, либо сертификат):
rasdial "WorkVPN"
Отключение:
rasdial "WorkVPN" /disconnect
Проверка статуса без побочных эффектов:
$status = rasdial
if ($status -match "WorkVPN") { "Подключено" } else { "Отключено" }
Код возврата rasdial доступен через $LASTEXITCODE — 0 означает успех, ненулевые значения соответствуют кодам ошибок RAS (например, 619 — соединение разорвано удалённым узлом, 691 — неверные учётные данные). Это удобно логировать в задаче планировщика, чтобы потом не гадать, почему VPN не поднялся ночью.
Пароль для rasdial лучше не хранить в скрипте открытым текстом. Рабочий вариант — сохранить его один раз через -RememberCredential при создании подключения (Windows сохранит его в Credential Manager под текущим пользователем), и дальше вызывать rasdial "WorkVPN" вообще без пароля.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОпределяем небезопасный Wi-Fi в PowerShell
Условное автоподключение — то есть «включай VPN только на открытых сетях, а дома не трогай» — требует понять, к какой сети сейчас подключена машина и насколько она защищена. Есть два независимых признака.
Первый — категория сети в самой Windows (Public / Private / DomainAuthenticated):
Get-NetConnectionProfile | Select-Object Name, InterfaceAlias, NetworkCategory
Проблема в том, что NetworkCategory — это то, что пользователь (или Windows автоматически) назначил сети, а не показатель реального шифрования. Открытая точка в кафе вполне может быть помечена как Private, если человек когда-то нажал не ту кнопку.
Второй, более честный признак — тип аутентификации самой Wi-Fi сети, который видно через netsh:
$wlan = netsh wlan show interfaces
$auth = ($wlan | Select-String "Authentication").ToString().Split(":")[1].Trim()
if ($auth -eq "Open") {
"Сеть открытая — шифрования на уровне Wi-Fi нет"
} else {
"Сеть защищена: $auth"
}
Значение Open означает, что трафик между устройством и точкой доступа не шифруется вообще — это ровно тот случай, когда VPN нужен уже на уровне транспорта, а не только для обхода блокировок. Значения вида WPA2-Personal или WPA3-Personal говорят о защищённой сети, где решение подключать VPN или нет — уже вопрос доверия к оператору точки, а не к перехвату эфира.
Оба признака стоит комбинировать: если NetworkCategory равен Public ИЛИ Authentication равен Open — поднимаем VPN.
Собираем скрипт условного автоподключения
Соединяем предыдущие блоки в один файл AutoVpn.ps1, который планировщик будет запускать по триггеру:
$vpnName = "WorkVPN"
$logFile = "C:\ProgramData\AutoVpn\autovpn.log"
New-Item -ItemType Directory -Path (Split-Path $logFile) -Force | Out-Null
function Write-Log($msg) {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg" | Out-File $logFile -Append
}
$wlan = netsh wlan show interfaces
$authLine = $wlan | Select-String "Authentication"
$isOpen = $authLine -and $authLine.ToString() -match "Open"
$profile = Get-NetConnectionProfile | Select-Object -First 1
$isPublic = $profile -and $profile.NetworkCategory -eq "Public"
$connected = (rasdial) -match $vpnName
if (($isOpen -or $isPublic) -and -not $connected) {
rasdial $vpnName
Write-Log "Открытая/публичная сеть обнаружена, VPN поднят, код: $LASTEXITCODE"
}
elseif (-not ($isOpen -or $isPublic) -and $connected) {
rasdial $vpnName /disconnect
Write-Log "Сеть доверенная, VPN отключён"
}
else {
Write-Log "Изменений не требуется (открытая=$isOpen, публичная=$isPublic, подключено=$connected)"
}
Скрипт идемпотентен — его можно запускать хоть каждую минуту, он не будет пытаться переподключить уже поднятый VPN.
Регистрация задачи в планировщике
Задачу можно создать двумя путями — через schtasks.exe (проще для одноразового скрипта, работает без прав на модуль ScheduledTasks) или через командлеты *-ScheduledTask (гибче, читаемее, лучше подходит, если задача часть более крупного деплой-скрипта).
Через schtasks, запуск при входе в систему и каждые 5 минут после:
schtasks /create /tn "AutoVPN" ^
/tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\AutoVpn.ps1" ^
/sc onlogon /rl highest /f
schtasks /create /tn "AutoVPN-Poll" ^
/tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\AutoVpn.ps1" ^
/sc minute /mo 5 /rl highest /f
Через PowerShell, тот же результат плюс явный принципал (запуск от SYSTEM, чтобы не зависело от того, разлогинился ли пользователь):
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\AutoVpn.ps1"
$trigger1 = New-ScheduledTaskTrigger -AtLogOn
$trigger2 = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration ([TimeSpan]::MaxValue)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "AutoVPN" -Action $action `
-Trigger @($trigger1, $trigger2) -Principal $principal -Force
Обратите внимание на -ExecutionPolicy Bypass в аргументе — он относится только к этому конкретному вызову powershell.exe и не меняет системную политику выполнения скриптов, поэтому его безопасно использовать в задачах.
Триггер по событию вместо опроса
Опрос раз в 5 минут работает, но реагирует с задержкой и лишний раз будит систему. Точнее — подписаться на событие журнала Microsoft-Windows-NetworkProfile/Operational, которое Windows пишет при каждом изменении сетевого профиля (ID 10000 — сеть определена, 10001 — сеть отменена). Регистрируется такой триггер через CIM:
$class = Get-CimClass -Namespace ROOT\Microsoft\Windows\TaskScheduler -ClassName MSFT_TaskEventTrigger
$trigger = New-CimInstance -CimClass $class -ClientOnly
$trigger.Subscription = @"
<QueryList>
<Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational">
<Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[(EventID=10000)]]</Select>
</Query>
</QueryList>
"@
$trigger.Enabled = $true
Register-ScheduledTask -TaskName "AutoVPN-OnConnect" -Action $action `
-Trigger $trigger -Principal $principal -Force
Такая задача просыпается ровно в момент подключения к новой сети, а не с опозданием в несколько минут. Минус — событие 10000 не всегда успевает нести финальную классификацию сети (Public/Private) к моменту срабатывания триггера, поэтому в скрипте стоит добавить Start-Sleep -Seconds 3 перед проверкой Get-NetConnectionProfile, чтобы дать системе время доопределить профиль.
Диагностика: почему задача не сработала
Три места, куда стоит посмотреть, если VPN не поднялся автоматически:
| Что проверить | Команда |
|---|---|
| Запускалась ли задача вообще | Get-ScheduledTaskInfo -TaskName "AutoVPN" |
| История последних запусков | Журнал событий → Microsoft-Windows-TaskScheduler/Operational |
| Лог самого скрипта | Get-Content C:\ProgramData\AutoVpn\autovpn.log -Tail 30 |
| Код возврата rasdial | Последняя строка лога с "код:" |
Частая причина тишины — задача создана от текущего пользователя, а не от SYSTEM, и после блокировки экрана перестаёт запускаться. Вторая частая причина — политика -ExecutionPolicy Bypass не сработала, потому что путь к скрипту содержит пробелы и не взят в кавычки в аргументе -File. Если задача в планировщике показывает статус «Выполнено (0x1)», это почти всегда синтаксическая ошибка в самом .ps1, а не проблема планировщика — стоит прогнать скрипт вручную с -Verbose.
Для базовой настройки самого сервера, к которому подключается этот скрипт, пригодятся смежные материалы: сертификаты для IKEv2 и SSTP через собственный Windows CA, настройка RRAS VPN IKEv2 с нуля и мониторинг VPN-подключений на Windows Server.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли обойтись без rasdial и поднимать VPN только средствами Add-VpnConnection?
Нет, Add-VpnConnection не подключает — он только регистрирует профиль. Для подключения нужен либо rasdial, либо Connect-VpnConnection (не путать: в Windows PowerShell 5.1 этот командлет часто отсутствует или требует модуль VpnClient, тогда как rasdial доступен всегда, поэтому в скриптах он надёжнее).
Задача в планировщике запускается, но VPN не поднимается — в чём чаще всего дело?
В большинстве случаев это либо отсутствующий сохранённый credential (профиль создан без -RememberCredential, и rasdial без пароля просто зависает или падает по таймауту), либо запуск задачи от пользователя без прав на изменение сетевых подключений — решается через принципал SYSTEM с RunLevel Highest.
Как передать пароль в rasdial безопасно, а не открытым текстом в скрипте?
Никак не передавать — сохранить его один раз через -RememberCredential при Add-VpnConnection (сохранится в Credential Manager текущего пользователя) или, для запуска от SYSTEM, использовать аутентификацию по машинному сертификату вместо логина и пароля.
Событие 10000 срабатывает и на смене профиля внутри одной и той же сети, не только при физическом переключении Wi-Fi — это нормально?
Да, это ожидаемое поведение Windows: профиль может пересчитываться после пробуждения из сна, смены DHCP-аренды или временной потери связи. Скрипт из статьи безопасен к таким повторным срабатываниям, потому что проверяет текущее состояние подключения перед действием.
Работает ли эта схема в Windows 11 так же, как в Windows 10?
Да, Add-VpnConnection, rasdial и планировщик задач не менялись между версиями в части рассмотренных здесь сценариев. Отличия могут быть в интерфейсе «Параметров», но командлеты и утилита rasdial идентичны.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →