Windows Server для самых маленьких. Часть 14: Как работают WinRM и PowerShell Remoting?
Все части серии «Windows Server для самых маленьких»В части 10 мы выполняли Set-DnsClientServerAddress -CimSession dc1 и Install-WindowsFeature -ComputerName dc2 с рабочей станции, и команды просто работали. За ними стоял WinRM — служба, которая с 2012 года включена на каждом Windows Server и через которую проходит почти всё удалённое управление: модули с -ComputerName, Server Manager, Windows Admin Center, System Center и, главное, PowerShell Remoting. Эта часть о том, как он устроен, как аутентифицирует, что шифрует, где его границы и как их обходить правильно.
WS-Management
WinRM — реализация Microsoft открытого стандарта WS-Management (DMTF DSP0226): управление системами через SOAP-сообщения поверх HTTP. Клиент отправляет POST /wsman с XML-телом, в котором операция (Get, Put, Enumerate, Invoke, Create) и адрес ресурса; сервер отвечает XML. Ресурсами бывают классы WMI (так работает Get-CimInstance -ComputerName), и бывает оболочка: PowerShell Remoting — это ресурс http://schemas.microsoft.com/powershell/Microsoft.PowerShell, внутри которого в SOAP-конвертах ездит протокол PSRP (PowerShell Remoting Protocol, MS-PSRP) — сериализованные объекты, команды и потоки вывода.
Служба WinRM слушает через HTTP.sys — тот самый драйвер из части 2, поэтому порты 5985 (HTTP) и 5986 (HTTPS) принадлежат процессу System, а не службе, и на анонимный запрос отвечает сам HTTP.sys с предложением аутентифицироваться:
PS C:\> winrm enumerate winrm/config/listener
Listener
Address = *
Transport = HTTP
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 127.0.0.1, 192.168.1.10, ::1, 2001:db8:1::10, fe80::…
PS C:\> Get-NetTCPConnection -LocalPort 5985 -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess, @{n='Process'; e={(Get-Process -Id $_.OwningProcess).Name}}
LocalAddress LocalPort OwningProcess Process
------------ --------- ------------- -------
:: 5985 4 System
PS C:\> $r = Invoke-WebRequest -Method Post -Uri http://dc1:5985/wsman -ContentType 'application/soap+xml;charset=UTF-8' -Body '<x/>' -SkipHttpErrorCheck
PS C:\> $r.StatusCode; $r.Headers['Server']; $r.Headers['WWW-Authenticate']
401
Microsoft-HTTPAPI/2.0
Negotiate
Kerberos
PS C:\> Get-ChildItem WSMan:\localhost\Service
WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Service
Type Name SourceOfValue Value
---- ---- ------------- -----
System.String RootSDDL O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
System.String MaxConcurrentOperations 4294967295
System.String MaxConcurrentOperationsPerUser 1500
System.String EnumerationTimeoutms 240000
System.String MaxConnections 300
System.String MaxPacketRetrievalTimeSeconds 120
System.String AllowUnencrypted false
Container Auth
Container DefaultPorts
System.String IPv4Filter *
System.String IPv6Filter *
System.String EnableCompatibilityHttpListener false
System.String EnableCompatibilityHttpsListener false
System.String CertificateThumbprint
System.String AllowRemoteAccess true
Вся конфигурация — в провайдере WSMan: (читать и менять её можно только с правами администратора): слушатели, параметры службы, клиента, аутентификации, лимиты оболочек (WSMan:\localhost\Shell: память на оболочку, число оболочек на пользователя, таймаут простоя). Старый интерфейс к тому же — winrm get winrm/config.
Enable-PSRemoting
На Windows Server всё уже включено: служба запущена, слушатель HTTP создан, правила файрвола «Windows Remote Management (HTTP-In)» включены — для профилей Domain и Private без ограничений, для Public только из локальной подсети. На Windows 10 и 11 слушателя по умолчанию нет, и чтобы принимать подключения, выполняется:
PS C:\> Enable-PSRemoting -Force
Что делает команда: запускает службу WinRM и ставит автозапуск; создаёт слушатель HTTP на всех адресах; включает правило файрвола для 5985 (на клиентских Windows — для профилей Domain и Private, а если какой-то сетевой адаптер в профиле Public, команда откажет, пока не добавить -SkipNetworkProfileCheck, и тогда правило для Public разрешит только локальную подсеть); регистрирует конфигурации сеансов и даёт на них права администраторам и группе Remote Management Users; и перезапускает службу WinRM. Старый эквивалент — winrm quickconfig.
Какие конфигурации появятся, зависит от того, откуда команда выполнена. В Windows PowerShell 5.1 это Microsoft.PowerShell, Microsoft.PowerShell.Workflow и на 64-битной системе Microsoft.PowerShell32. В PowerShell 7 — две конечные точки для pwsh, короткая PowerShell.7 и полная, с номером версии, например PowerShell.7.4.6; конечные точки Windows PowerShell она не трогает.
Чтобы подключаться, на клиенте ничего включать не нужно; служба нужна только для хранения настроек клиента, вроде TrustedHosts, о которых ниже.
Отдельная оговорка — локальные учётные записи. Контроль учётных записей (UAC) выдаёт локальному администратору, вошедшему по сети, урезанный токен без прав администратора; доменных администраторов это не касается. Обходится параметром реестра LocalAccountTokenFilterPolicy = 1, который снимает это ограничение сразу для всех пользователей машины, поэтому в домене удалённо входят доменными учётными записями.
Конфигурации сеансов — это конечные точки, к которым подключается клиент. Смотреть их удобнее всего с той стороны, куда подключаются:
PS C:\> Invoke-Command dc1 { Get-PSSessionConfiguration } | Format-Table Name, PSVersion, Permission -AutoSize
Name PSVersion Permission
---- --------- ----------
microsoft.powershell 5.1 NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed
microsoft.powershell.workflow 5.1 BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed
microsoft.powershell32 5.1 NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed
microsoft.windows.servermanagerworkflows 3.0 NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed
Это свежий Windows Server 2025 с установленным PowerShell 7: конечных точек PowerShell.7 нет, пока на нём не выполнен Enable-PSRemoting из pwsh. Обратите внимание, что список вернул сеанс Windows PowerShell 5.1: по умолчанию клиент идёт в microsoft.powershell, с какой бы версии ни подключался, и там выполняется Windows PowerShell. А сам Get-PSSessionConfiguration, запущенный в pwsh на сервере, покажет только конечные точки PowerShell 7 — на этом сервере пустой список. В PowerShell 7 на сервере подключаются с -ConfigurationName PowerShell.7. Своя конечная точка с собственными правами и ограничениями — это JEA, последний раздел части.
Сеансы
Три способа выполнить команду на другом сервере.
Интерактивно, как SSH:
PS C:\> Enter-PSSession -ComputerName dc1
[dc1]: PS C:\Users\alice.admin\Documents> Get-Service NTDS, DNS | Select-Object Name, Status
Name Status
---- ------
DNS Running
NTDS Running
[dc1]: PS C:\Users\alice.admin\Documents> Exit-PSSession
PS C:\>
Приглашение показывает, где вы; все команды выполняются на dc1 в процессе wsmprovhost.exe под вашей учётной записью. Это удобно для разовых действий и бесполезно для скриптов: Enter-PSSession внутри скрипта не переносит в сеанс последующие строки.
Пакетно, на одном или многих:
PS C:\> Invoke-Command -ComputerName dc1, dc2, dc3 -ScriptBlock { Get-Service NTDS | Select-Object Status, @{n='Uptime'; e={(Get-Date) - (Get-Process lsass).StartTime}} }
Status Uptime PSComputerName RunspaceId
------ ------ -------------- ----------
Running 1.10:20:16.4246245 dc3 78d7553c-62c1-4019-a961-cd7e7d3606fb
Running 08:28:38.6201064 dc1 82bba145-11b2-44fa-a802-1b2f161bf6a7
Running 1.10:21:24.7876073 dc2 a9b705ea-cb1f-46c6-bf9b-ec2c7b9b0778
Блок выполняется на всех серверах параллельно (до 32 одновременно, -ThrottleLimit), результаты приходят в порядке готовности, а не в порядке перечисления, и к каждому приклеиваются PSComputerName и RunspaceId. Локальные переменные внутрь блока не видны: передаются через -ArgumentList с param() или через $using:имя:
PS C:\> $days = 7
PS C:\> Invoke-Command -ComputerName dc1, dc2 -ScriptBlock { Get-WinEvent -FilterHashtable @{LogName='System'; Level=2; StartTime=(Get-Date).AddDays(-$using:days)} | Measure-Object | Select-Object Count }
Count PSComputerName RunspaceId
----- -------------- ----------
16 dc2 8582f99b-9b17-41db-b05c-20d261290186
42 dc1 e3c63bd3-ee68-4cad-b5b7-0ed8db7b1a98
Без $using: переменная $days внутри блока была бы пустой. -FilePath .\script.ps1 отправляет и выполняет локальный файл скрипта на каждом сервере; -AsJob возвращает управление сразу, создавая на клиенте задание типа RemoteJob с дочерним заданием на каждый сервер, а результат забирается потом через Receive-Job.
Через постоянный сеанс, когда команд много и состояние между ними нужно сохранить:
PS C:\> $s = New-PSSession -ComputerName dc1, dc2
PS C:\> Invoke-Command -Session $s -ScriptBlock { $before = (Get-Service W32Time).Status }
PS C:\> Invoke-Command -Session $s -ScriptBlock { Restart-Service W32Time; "$before -> $((Get-Service W32Time).Status)" }
Running -> Running
Running -> Running
PS C:\> Copy-Item .\fix-time.ps1 -Destination C:\Temp\ -ToSession $s[0]
PS C:\> Remove-PSSession $s
Переменная $before, созданная в первом вызове, жива во втором: это один процесс на сервере. Copy-Item -ToSession и -FromSession передают файлы по тому же каналу без SMB; папка назначения должна существовать, иначе ответ — «The directory name is invalid.». Сеанс можно отключить и подключиться позже, даже с другой машины: долгая задача на сервере продолжает выполняться, пока клиент закрыт:
PS C:\> $s = New-PSSession -ComputerName dc2 -Name nightly
PS C:\> Disconnect-PSSession $s
Id Name Transport ComputerName ComputerType State ConfigurationName Availability
-- ---- --------- ------------ ------------ ----- ----------------- ------------
1 nightly WSMan dc2 RemoteMachine Disconnected Microsoft.PowerShell None
PS C:\> Get-PSSession -ComputerName dc2 # хоть с другой машины
Id Name Transport ComputerName ComputerType State ConfigurationName Availability
-- ---- --------- ------------ ------------ ----- ----------------- ------------
1 nightly WSMan dc2 RemoteMachine Disconnected Microsoft.PowerShell None
PS C:\> $s = Connect-PSSession -ComputerName dc2 -Name nightly
Отключённые сеансы появились в PowerShell 3.0; отключить можно сеанс из New-PSSession или Invoke-Command -InDisconnectedSession, а интерактивный сеанс Enter-PSSession — нельзя.
Десериализация. Объекты из сеанса приходят не живыми: PSRP сериализует их в XML и восстанавливает на клиенте как объекты типа Deserialized.System.ServiceProcess.ServiceController — со всеми свойствами на момент сериализации, но без методов (у такого объекта остаются только GetType и ToString). Отсюда ловушка: Invoke-Command dc1 { Get-Service W32Time } | Stop-Service не остановит службу на dc1. Stop-Service не узнает в снимке службу, возьмёт из него свойство Name и остановит службу с этим именем на локальной машине — проверить можно с -WhatIf:
PS C:\> Invoke-Command dc1 { Get-Service W32Time } | Stop-Service -WhatIf
What if: Performing the operation "Stop-Service" on target "Windows Time (W32Time)".
Что делать с объектом, делается внутри блока. Второе следствие: Format-Table внутри блока вернёт не данные, а объекты форматирования (FormatStartData, FormatEntryData и т. д.). На экран они ещё выведутся, но отфильтровать, отсортировать или выгрузить в CSV их нельзя — форматировать нужно снаружи.
Аутентификация
По умолчанию — Negotiate: Kerberos, если возможно, иначе NTLM. Для Kerberos нужно то, что разобрано в части 6: клиент и сервер в домене или в доверяющих доменах, обращение по имени, время в пределах пяти минут. Клиент запрашивает билет на SPN HTTP/dc1.corp.example.com; отдельного такого SPN у учётной записи компьютера нет, но KDC сопоставляет HTTP с HOST по атрибуту sPNMappings в разделе конфигурации и выдаёт билет на учётную запись dc1$. В Windows Server 2025 и Windows 11 24H2 у клиента WinRM в WSMan:\localhost\Client есть параметр spn_prefix = HOST, и он сразу просит HOST/dc1.corp.example.com — это и видно в klist после подключения. Служба WinRM регистрирует на учётной записи компьютера ещё и WSMAN/dc1 и WSMAN/dc1.corp.example.com (мы видели их в setspn -L), но клиент PowerShell по умолчанию просит не их. Если HTTP/dc1.corp.example.com явно зарегистрирован на другой учётной записи, например на пуле приложений IIS, Kerberos к WinRM ломается: билет выписывается не на тот ключ; тогда помогает New-PSSessionOption -IncludePortInSPN, с которым SPN — HTTP/dc1.corp.example.com:5985. Когда условия выполнены, всё работает без параметров и без ввода пароля: билет берётся из сеанса входа.
NTLM включается, когда Kerberos невозможен: сервер в рабочей группе, обращение по IP-адресу, клиент не в домене. И здесь клиент WinRM отказывается подключаться:
PS C:\> Enter-PSSession -ComputerName 192.168.1.20 -Credential CORP\alice.admin
Enter-PSSession: Connecting to remote server 192.168.1.20 failed with the following error message : The WinRM client cannot process the request. Default authentication may be used with an IP address under the following conditions: the transport is HTTPS or the destination is in the TrustedHosts list, and explicit credentials are provided. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. For more information on how to set TrustedHosts run the following command: winrm help config. For more information, see the about_Remote_Troubleshooting Help topic.
Причина — взаимная аутентификация. Kerberos подтверждает клиенту, что сервер настоящий (AP-REP из части 6). NTLM этого не делает: клиент отправит ответ на вызов любому, кто назвался 192.168.1.20, и если это подставной сервер, он получит NTLM-ответ, который можно ретранслировать. TrustedHosts — список имён и адресов, для которых администратор клиента принимает этот риск:
PS C:\> Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'srv-wg.example.net,192.168.1.20' -Concatenate
PS C:\> Get-Item WSMan:\localhost\Client\TrustedHosts | Select-Object -ExpandProperty Value
srv-wg.example.net,192.168.1.20
Значение * разрешает NTLM со всеми, и его ставят в лабораториях, не понимая, что это означает «доверяю любому, кто ответит на этом адресе». Правильные альтернативы: ввести сервер в домен; или использовать HTTPS, где подлинность сервера подтверждает сертификат, и тогда TrustedHosts не нужен; или аутентификацию по сертификату клиента.
Другие схемы — -Authentication Basic (имя и пароль открытым текстом; в WSMan:\localhost\Service\Auth\Basic по умолчанию false, и без шифрования служба её не примет, пока не разрешён AllowUnencrypted, поэтому на практике — только по HTTPS), Certificate (клиентский сертификат, сопоставленный с локальной учётной записью на сервере) и CredSSP, о котором в разделе про второй прыжок.
Шифрование. Порт 5985 — HTTP, и это смущает, но содержимое по нему зашифровано: Negotiate даёт не только аутентификацию, но и ключ сеанса, и WinRM шифрует SOAP-тело им (в HTTP это выглядит как Content-Type: multipart/encrypted;protocol="application/HTTP-SPNEGO-session-encrypted"; при явном -Authentication Kerberos — HTTP-Kerberos-session-encrypted, при CredSSP — HTTP-CredSSP-session-encrypted). Незашифрованный обмен служба не принимает (AllowUnencrypted = false). HTTPS нужен для Basic, для доверия к серверу вне домена и для политик, требующих TLS формально.
HTTPS-слушатель
Нужен сертификат в хранилище компьютера с назначением Server Authentication и именем сервера в CN или SAN, не просроченный и не отозванный. В организации его выдаёт удостоверяющий центр из части 25. winrm quickconfig -transport:https подбирает сертификат сам и самоподписанный не возьмёт; слушатель с явно указанным отпечатком создаётся и с самоподписанным, что годится для лаборатории:
PS C:\> $cert = New-SelfSignedCertificate -DnsName dc1.corp.example.com -CertStoreLocation Cert:\LocalMachine\My
PS C:\> New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbprint $cert.Thumbprint -Force
PS C:\> New-NetFirewallRule -DisplayName 'WinRM HTTPS' -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow -Profile Domain, Private
PS C:\> netsh http show sslcert ipport=0.0.0.0:5986
IP:port : 0.0.0.0:5986
Certificate Hash : 3f1a…c9e2
Application ID : {afebb9ad-9b97-4a91-9ab5-daf4d59122f6}
Привязку сертификата к порту в HTTP.sys служба сделала сама — это та самая netsh http sslcert из части 2, а Application ID — идентификатор приложения, создавшего привязку. Правило файрвола для 5986 нужно создавать руками: Enable-PSRemoting открывает только 5985.
Клиент: Enter-PSSession -ComputerName dc1.corp.example.com -UseSSL. С самоподписанным сертификатом он откажет по цепочке доверия; обойти на время — -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck), или положить сертификат в доверенные корневые на клиенте.
Второй прыжок
Подключаемся к srv1 и оттуда пробуем открыть папку на dc1:
PS C:\> Enter-PSSession srv1
[srv1]: PS C:\Users\alice.admin\Documents> Get-ChildItem \\dc1\SYSVOL
Get-ChildItem: Cannot find path '\\dc1\SYSVOL' because it does not exist.
[srv1]: PS C:\Users\alice.admin\Documents> klist
Current LogonId is 0:0xf4cf32
Cached Tickets: (1)
#0> Client: alice.admin @ CORP.EXAMPLE.COM
Server: HOST/srv1.corp.example.com @ CORP.EXAMPLE.COM
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize
…
Cache Flags: 0x8 -> ASC
Путь, конечно, существует, и это не ошибка прав в привычном смысле. В сеансе на srv1 есть только билет, с которым мы на него пришли (флаг ASC — принятый контекст безопасности), и ни одного TGT; чтобы обратиться к cifs/dc1 от нашего имени, srv1 нужен наш TGT или право делегирования, а ни того ни другого у него нет. Аутентифицироваться на dc1 от нашего имени SMB-клиенту на srv1 нечем, и PowerShell сообщает «не существует» — сбивающий с толку текст для этой ситуации. Это проблема второго прыжка из части 6, и у неё четыре решения, от худшего к лучшему.
CredSSP. Клиент отправляет серверу пароль (в зашифрованном виде), и сервер входит с ним куда угодно. Включается на обеих сторонах:
PS C:\> Enable-WSManCredSSP -Role Client -DelegateComputer srv1.corp.example.com -Force # на рабочей станции
PS C:\> Enable-WSManCredSSP -Role Server -Force # на srv1
PS C:\> Enter-PSSession srv1 -Authentication CredSSP -Credential CORP\alice.admin
Работает всегда, в том числе с рабочими группами (кроме членов группы Protected Users), и именно поэтому опасен: скомпрометированный srv1 получает пароль администратора, и администратор, вошедший на него по CredSSP, отдал этот пароль сам. Для серверов, которым не доверяют безусловно, не подходит.
Учётные данные внутрь сеанса. Передать $cred через $using: и подключить ресурс с ними: New-PSDrive -Name S -PSProvider FileSystem -Root \\dc1\SYSVOL -Credential $using:cred. Пароль тоже уезжает на сервер, но только в памяти процесса и только для одного ресурса.
Делегирование Kerberos на основе ресурса. Из части 6: владелец ресурса dc1 разрешает srv1 обращаться к нему от имени пользователей:
PS C:\> Set-ADComputer dc1 -PrincipalsAllowedToDelegateToAccount (Get-ADComputer srv1)
PS C:\> Invoke-Command srv1 { klist purge -li 0x3e7 } # сбросить кеш билетов учётной записи компьютера srv1
PS C:\> Invoke-Command srv1 { Get-ChildItem \\dc1\SYSVOL | Select-Object -First 1 Name }
Name PSComputerName RunspaceId
---- -------------- ----------
corp.example.com srv1 5d0c2a6e-8f41-4b7e-9a3d-1c6f0e2b7a95
klist purge нужен потому, что KDC 15 минут помнит отказы, полученные srv1 до настройки. Пароль никуда не передаётся, srv1 получает через S4U2Proxy билет на cifs/dc1 от имени alice.admin, и это право ограничено одним ресурсом. Правильное решение для серверов домена — с одной оговоркой: вторым прыжком так можно обратиться к файлам, LDAP или SQL Server, но не к WinRM следующего сервера.
Конечная точка с RunAs или gMSA. Конфигурация сеанса на srv1, зарегистрированная с -RunAsCredential или под групповой управляемой учётной записью из части 13: всё внутри сеанса выполняется от имени этой учётной записи, у которой свой TGT и свои права на dc1. Это уже половина JEA.
JEA
Just Enough Administration решает другую задачу: дать сотруднику службы поддержки право перезапустить службу и очистить кеш DNS на контроллере, не делая его администратором контроллера. Три части.
Файл возможностей роли (.psrc) — что видно в сеансе:
PS C:\> New-Item -Path 'C:\Program Files\WindowsPowerShell\Modules\JEA\RoleCapabilities' -ItemType Directory -Force | Out-Null
PS C:\> New-PSRoleCapabilityFile -Path 'C:\Program Files\WindowsPowerShell\Modules\JEA\RoleCapabilities\DnsOperator.psrc' `
-ModulesToImport DnsServer `
-VisibleCmdlets 'Get-DnsServerResourceRecord', 'Clear-DnsServerCache', @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'DNS' } } `
-VisibleFunctions 'Get-DnsHealth' `
-FunctionDefinitions @{ Name = 'Get-DnsHealth'; ScriptBlock = { Get-DnsServerStatistics | Select-Object -ExpandProperty QueryStatistics } }
Три командлета, причём Restart-Service — только со значением DNS, и одна функция. Файл должен лежать в папке RoleCapabilities какого-нибудь модуля из PSModulePath, чтобы JEA его нашёл по имени; с PowerShell 6 файл можно указать и прямо по пути — ключом RoleCapabilityFiles в определении роли вместо RoleCapabilities.
Файл конфигурации сеанса (.pssc) — кто какую роль получает и под кем выполняется:
PS C:\> New-PSSessionConfigurationFile -Path C:\JEA\DnsOps.pssc `
-SessionType RestrictedRemoteServer `
-RunAsVirtualAccount `
-TranscriptDirectory C:\JEA\Transcripts `
-RoleDefinitions @{ 'CORP\Helpdesk' = @{ RoleCapabilities = 'DnsOperator' } }
PS C:\> Test-PSSessionConfigurationFile C:\JEA\DnsOps.pssc
True
RestrictedRemoteServer — сеанс без языка: только вызов разрешённых команд, без переменных, циклов и выражений. RunAsVirtualAccount — команды выполняются под виртуальной учётной записью: временным локальным администратором без пароля, который существует, пока открыт сеанс (вместо неё можно указать gMSA параметром -GroupManagedServiceAccount, когда командам нужен доступ к сети); пользователь из Helpdesk не получает никаких прав на сервере, а права внутри сеанса ограничены списком команд. TranscriptDirectory — полная запись каждого сеанса: кто, когда, что выполнил.
Регистрация и подключение. Register-PSSessionConfiguration перезапускает службу WinRM, и все открытые на сервере сеансы, включая тот, из которого вы её выполняете, обрываются:
PS C:\> Register-PSSessionConfiguration -Name DnsOps -Path C:\JEA\DnsOps.pssc -Force
PS C:\> Get-PSSessionConfiguration DnsOps | Select-Object Name, RunAsVirtualAccount, Permission
Name RunAsVirtualAccount Permission
---- ------------------- ----------
DnsOps true CORP\Helpdesk AccessAllowed
На рабочей станции сотрудника поддержки:
PS C:\> Enter-PSSession dc1 -ConfigurationName DnsOps
[dc1]: PS> Get-Command | Select-Object -ExpandProperty Name
Clear-DnsServerCache
Clear-Host
Exit-PSSession
Get-Command
Get-DnsHealth
Get-DnsServerResourceRecord
Get-FormatData
Get-Help
Measure-Object
Out-Default
Restart-Service
Select-Object
[dc1]: PS> Restart-Service NTDS
Restart-Service: Cannot validate argument on parameter 'Name'. The argument "NTDS" does not belong to the set "DNS" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
[dc1]: PS> Restart-Service DNS
[dc1]: PS> whoami
whoami: The term 'whoami' is not recognized …
Внутри сеанса пользователь — WinRM Virtual Users\WinRM_VA_1_corp_bob с правами администратора, но единственный способ ими воспользоваться — разрешённые команды. Транскрипт в C:\JEA\Transcripts фиксирует и имя настоящего пользователя, и каждую команду. Это тот инструмент, которым делегирование из части 13 дополняется на уровне серверов: там — права на объекты каталога, здесь — права на действия на машине.
Смотрим руками
Процесс на сервере, в котором живёт сеанс:
PS C:\> Invoke-Command dc1 { Get-Process wsmprovhost -IncludeUserName } | Select-Object Id, UserName, StartTime
Id UserName StartTime
-- -------- ---------
3412 CORP\alice.admin 15.10.2026 14:02:17
Один wsmprovhost.exe на сеанс, под учётной записью подключившегося; каждый Invoke-Command без -Session поднимает новый процесс, и в списке будет тот, что выполняет саму эту команду. У JEA с виртуальной учётной записью процесс работает под WinRM_VA_….
Обмен в пакетах: pktmon filter add -p 5985, pktmon start --capture, затем Invoke-Command dc1 { hostname }, pktmon stop и pktmon etl2pcap PktMon.etl для Wireshark:
No. Source Destination Protocol Info
1 192.168.1.100 192.168.1.10 HTTP POST /wsman HTTP/1.1
2 192.168.1.10 192.168.1.100 HTTP HTTP/1.1 401 (WWW-Authenticate: Negotiate, Kerberos)
3 192.168.1.100 192.168.1.10 HTTP POST /wsman HTTP/1.1 (Authorization: Negotiate, Kerberos AP-REQ)
4 192.168.1.10 192.168.1.100 HTTP HTTP/1.1 200 (multipart/encrypted;protocol="application/HTTP-SPNEGO-session-encrypted")
5 192.168.1.100 192.168.1.10 HTTP POST /wsman HTTP/1.1 (multipart/encrypted …)
Первый запрос без аутентификации получает 401 с предложением Negotiate и Kerberos, второй несёт в заголовке Authorization AP-REQ Kerberos на HTTP/dc1.corp.example.com (с клиента на Windows Server 2025 — на HOST/dc1.corp.example.com), и дальше все тела зашифрованы ключом сеанса — Wireshark видит только заголовки HTTP и длину. Если бы в третьем пакете был NTLMSSP_NEGOTIATE, это означало бы откат на NTLM: обращение по IP или сервер вне домена.
Журналы. Get-WinEvent -ComputerName ходит не через WinRM, а по RPC службы журнала событий, и на сервере с правилами файрвола по умолчанию отвечает «The RPC server is unavailable.» (группа правил Remote Event Log Management выключена) — журналы удобнее читать через тот же Remoting. На сервере в Microsoft-Windows-WinRM/Operational событие 91 — создание оболочки, с именем пользователя и адресом клиента; на клиенте в журнале с тем же именем при неудаче — 142 («WSMan operation CreateShell failed, error code …») и 161 с тем же текстом ошибки, что показал PowerShell. В Microsoft-Windows-PowerShell/Operational на сервере событие 4104 при включённой политике записи блоков скриптов (Turn on PowerShell Script Block Logging) содержит текст каждого выполненного блока; сеанс в конечной точке microsoft.powershell — это Windows PowerShell, поэтому пишет он именно сюда, а PowerShell 7 ведёт свой журнал PowerShellCore/Operational со своей политикой:
PS C:\> Invoke-Command dc1 { Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-WinRM/Operational'; Id=91} -MaxEvents 1 | Select-Object -ExpandProperty Message }
Creating WSMan shell on server with ResourceUri: http://schemas.microsoft.com/powershell/Microsoft.PowerShell (CORP\alice.admin clientIP: 192.168.1.100)
PS C:\> Invoke-Command dc1 { Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} -MaxEvents 1 | Select-Object -ExpandProperty Message }
Creating Scriptblock text (1 of 1):
hostname
В журнале Security тот же сеанс — событие 4624 с типом входа 3 (сетевой) и пакетом аутентификации Kerberos; при CredSSP — тип 8 (NetworkCleartext): так CredSSP и опознают в аудите, и этот тип означает, что учётные данные пользователя остались на сервере.
И проверка связи без сеанса:
PS C:\> Test-WSMan dc1 | Select-Object ProductVersion
ProductVersion
--------------
OS: 0.0.0 SP: 0.0 Stack: 3.0
PS C:\> Test-WSMan dc1 -Authentication Default | Select-Object ProductVersion
ProductVersion
--------------
OS: 10.0.26100 SP: 0.0 Stack: 3.0
Без -Authentication запрос идёт анонимно и проверяет только, что слушатель отвечает, поэтому версия ОС приходит нулевой; с ним — что аутентификация проходит. Стек 3.0 — версия WinRM, та же, что в Windows 8 и Windows Server 2012.
Источники
- WS-Management Specification (DSP0226) — стандарт DMTF: SOAP поверх HTTP, операции и адреса ресурсов;
- MS-WSMV: Web Services Management Protocol Extensions, Encrypted Message Types и SPNEGO-based encryption — расширения Microsoft, шифрование тела ключом сеанса и типы
multipart/encrypted; - MS-PSRP: PowerShell Remoting Protocol — протокол PowerShell внутри WS-Management, сериализация объектов;
- Windows Remote Management и Installation and configuration for Windows Remote Management — служба WinRM, слушатели, порты 5985 и 5986, параметры службы и клиента;
- HTTP Server API и netsh http — HTTP.sys и привязки сертификатов;
- about_WSMan_Provider — провайдер
WSMan:и создание слушателя черезNew-Item; - about_Remote, about_Remote_Requirements и about_Remote_Troubleshooting — устройство Remoting, требования, TrustedHosts,
LocalAccountTokenFilterPolicy, типичные ошибки; - Enable-PSRemoting — что меняет команда, конечные точки PowerShell 7, правила файрвола на серверных и клиентских Windows;
- about_Session_Configurations, Get-PSSessionConfiguration и Register-PSSessionConfiguration — конечные точки и их регистрация;
- Enter-PSSession, Invoke-Command, New-PSSession и about_Remote_Variables — три способа выполнения,
-ThrottleLimit,-AsJob,-FilePath,$using:; - Copy-Item —
-ToSessionи-FromSession; - about_Remote_Disconnected_Sessions и Disconnect-PSSession — отключённые сеансы;
- about_Remote_Output — десериализованные объекты без методов;
- Security considerations for PowerShell Remoting using WinRM — аутентификация, шифрование по HTTP, TrustedHosts;
- Error 0x80090322 when connecting PowerShell to a remote server via WinRM, New-PSSessionOption и SPN-Mappings — SPN
HTTP/<FQDN>, параметрspn_prefix,-IncludePortInSPNи сопоставление классов служб сhost; - Configure WinRM for HTTPS — требования к сертификату и слушатель HTTPS;
- Making the second hop in PowerShell Remoting, Kerberos constrained delegation overview и Enable-WSManCredSSP — второй прыжок, CredSSP, делегирование на основе ресурса и
klist purge -li 0x3e7; - Administrative tools and logon types — типы входа WinRM (3) и WinRM с CredSSP (8) и где остаются учётные данные;
- JEA overview, JEA Role Capabilities, JEA Session Configurations, Registering JEA Configurations и JEA Security Considerations — роли, команды по умолчанию в
RestrictedRemoteServer, виртуальные учётные записи, перезапуск WinRM при регистрации; - New-PSRoleCapabilityFile и New-PSSessionConfigurationFile — файлы
.psrcи.pssc; - about_Logging_Windows — событие 4104, запись блоков скриптов и журнал PowerShell 7;
- Get-WinEvent —
-ComputerNameбез Remoting и требование к файрволу; - Test-WSMan — анонимная и аутентифицированная проверка, нулевая версия ОС;
- Packet Monitor (Pktmon) — захват пакетов встроенными средствами.
Теперь у нас есть транспорт, по которому любая команда выполняется на любом сервере домена. Следующий шаг — рабочая станция администратора, на которой эти команды удобно писать: модули ролей, которых на ней нет, оснастки, которые ещё нужны, и инструменты, которые не требуют ничего, кроме браузера.
Дальше — часть 15: управление с рабочей станции. RSAT как компоненты по требованию, модули DnsServer, DhcpServer, GroupPolicy и их -ComputerName и -CimSession, неявный remoting для модулей, которых нет локально, оснастки MMC и их транспорт, Windows Admin Center, и как собрать рабочее место администратора, с которого не нужно заходить на серверы.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.