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.


Источники


Теперь у нас есть транспорт, по которому любая команда выполняется на любом сервере домена. Следующий шаг — рабочая станция администратора, на которой эти команды удобно писать: модули ролей, которых на ней нет, оснастки, которые ещё нужны, и инструменты, которые не требуют ничего, кроме браузера.

Дальше — часть 15: управление с рабочей станции. RSAT как компоненты по требованию, модули DnsServer, DhcpServer, GroupPolicy и их -ComputerName и -CimSession, неявный remoting для модулей, которых нет локально, оснастки MMC и их транспорт, Windows Admin Center, и как собрать рабочее место администратора, с которого не нужно заходить на серверы.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.