Qué vas a conseguir
Usar PowerShell remoto como herramienta diaria de operaciones: saber qué tiene que estar en su sitio para que funcione, lanzar un comando contra cien servidores y quedarte con los resultados y con la lista de los que fallaron, trabajar en una sesión interactiva, resolver el "acceso denegado" al recurso compartido desde dentro de la sesión, dar a soporte solo los comandos que necesita y diagnosticar los servidores que responden WinRM cannot complete the operation o Access is denied.
Antes de empezar
- Windows PowerShell 5.1 o PowerShell 7 en tu equipo de administración, en el dominio
contoso.local. - Una cuenta de administración (
CONTOSO\adm-lmartin, no tu cuenta de correo) que sea administrador local en los servidores, normalmente por un grupo comoGG-Admins-Servidores. - El módulo ActiveDirectory de RSAT para consultar equipos y SPN.
- La guía del script de inventario remoto explica el bucle con
try/catch; aquí se pasa a la ejecución en paralelo.
1. WinRM en dos minutos
PowerShell remoto viaja por WinRM, la implementación de Microsoft de WS-Management:
- 5985/TCP para HTTP y 5986/TCP para HTTPS. En el dominio, HTTP no viaja en claro: Kerberos cifra el contenido después de autenticar.
- En Windows Server viene activo de serie. En Windows 11,
Enable-PSRemoting -Forcearranca el servicio, crea el agente de escucha y abre el firewall. Si la red del equipo es Pública, falla salvo con-SkipNetworkProfileCheck, que solo abre a la subred local. - Para muchos equipos, una GPO: Configuración del equipo › Plantillas administrativas › Componentes de Windows › Administración remota de Windows (WinRM) › Servicio WinRM › Permitir la administración remota del servidor a través de WinRM, el servicio WinRM en automático y la regla de firewall Administración remota de Windows (HTTP de entrada) limitada a la red de administración.
Test-NetConnection srv-app07 -Port 5985 | Select-Object ComputerName, TcpTestSucceededComputerName TcpTestSucceeded------------ ----------------srv-app07 TrueTest-WSMan srv-app07wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsdProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsdProductVendor : Microsoft CorporationProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0Test-WSMan contesta sin autenticar: confirma red, firewall y servicio, no permisos.
Fuera del dominio (un servidor en grupo de trabajo, una DMZ, o cuando escribes una IP), Kerberos no sirve y entra NTLM. Entonces hace falta HTTPS o añadir el destino a TrustedHosts en tu equipo, y pasar -Credential siempre:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'srv-dmz01' -Concatenate -ForceGet-Item WSMan:\localhost\Client\TrustedHosts | Select-Object ValueTrustedHosts no hace "de confianza" a nadie: solo calla el aviso de que no se puede comprobar la identidad del servidor. Nunca *, y nunca para equipos del dominio, que deben ir por nombre y Kerberos. Para la DMZ, mejor un agente de escucha HTTPS en 5986 con un certificado de la CA interna.
2. Invoke-Command contra muchos equipos
Si -ComputerName recibe la lista entera, PowerShell abre las conexiones en paralelo, 32 a la vez por defecto (-ThrottleLimit). Cada resultado llega con la propiedad PSComputerName, y los equipos que fallan van a la variable de errores en lugar de parar el comando:
$servidores = (Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -SearchBase 'OU=Servidores,DC=contoso,DC=local').DNSHostName$servicio = 'Spooler'$r = Invoke-Command -ComputerName $servidores -ThrottleLimit 64 -ErrorAction SilentlyContinue -ErrorVariable fallos -ScriptBlock { $s = Get-Service -Name $using:servicio [pscustomobject]@{ Estado = $s.Status; Inicio = $s.StartType; Arranque = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime }}$r | Select-Object PSComputerName, Estado, Inicio, Arranque | Export-Csv C:\Temp\spooler.csv -NoTypeInformation -Delimiter ';' -Encoding UTF8$fallos | ForEach-Object { [pscustomobject]@{ Equipo = $_.TargetObject; Error = $_.FullyQualifiedErrorId } } | Format-Table -AutoSizeEquipo Error------ -----srv-app07.contoso.local WinRMOperationTimeout,PSSessionStateBrokensrv-app09.contoso.local CannotConnect,PSSessionStateBrokensrv-app11.contoso.local AccessDenied,PSSessionStateBroken$using:serviciolleva una variable local al bloque remoto. Sin$using:llega vacía.-ErrorVariable fallos(sin$) guarda los errores de conexión con el equipo enTargetObject. Ese CSV de fallos es tan importante como el de resultados: dice qué equipos no sabes cómo están.- Para trabajos largos,
-AsJobdevuelve el control yReceive-Jobrecoge después. - Si vas a lanzar varios comandos seguidos, abre las sesiones una vez:
$ses = New-PSSession -ComputerName $servidores, despuésInvoke-Command -Session $ses ...y al finalRemove-PSSession $ses.
3. Enter-PSSession: una consola en el servidor
Enter-PSSession -ComputerName srv-app07[srv-app07]: PS C:\Users\adm-lmartin\Documents> Get-Service W3SVC | Restart-Service -PassThruStatus Name DisplayName------ ---- -----------Running W3SVC World Wide Web Publishing Service[srv-app07]: PS C:\Users\adm-lmartin\Documents> exit- Todo corre en el servidor, sin escritorio: nada que abra una ventana ni que pregunte de forma interactiva (instaladores con asistente,
notepad). - Para llevar un archivo, sin carpetas compartidas:
Copy-Item C:\Temp\parche.msu -Destination C:\Temp -ToSession (New-PSSession srv-app07).
4. El segundo salto
Dentro de la sesión en srv-app07, Get-ChildItem \\srv-ficheros\Software$ da Access is denied. No es un permiso que falte: con Kerberos, srv-app07 sabe quién eres pero no tiene tus credenciales para presentarlas a un tercer equipo. Es el segundo salto. Las salidas, de mejor a peor:
- Evitarlo: copiar el archivo al servidor antes con
Copy-Item -ToSession, o lanzar el trabajo desde el equipo que tiene los datos. - Pasar la credencial explícitamente para ese acceso:
Invoke-Command srv-app07 { New-PSDrive -Name S -PSProvider FileSystem -Root \\srv-ficheros\Software$ -Credential $using:cred }, con$cred = Get-Credentialen tu equipo. - Delegación restringida basada en recursos (Kerberos, sin guardar contraseñas), configurada en el destino:
Set-ADComputer srv-ficheros -PrincipalsAllowedToDelegateToAccount (Get-ADComputer srv-app07). Sirve para SMB, no para encadenar otra sesión de WinRM. Si ya se intentó antes,klist purge -li 0x3e7ensrv-app07o esperar 15 minutos. - CredSSP: envía tu credencial al servidor y la deja allí; si ese servidor está comprometido, tu cuenta también. Solo de forma puntual, hacia un servidor concreto (
Enable-WSManCredSSP -Role Client -DelegateComputer srv-app07.contoso.localy-Role Serveren el destino) y deshaciéndolo al terminar conDisable-WSManCredSSP.
La delegación sin restricciones (Confiar en este equipo para la delegación a cualquier servicio) también "funciona" y es la peor opción: cualquier servicio de ese equipo puede usar tu identidad en cualquier sitio.
5. JEA: solo los comandos que hacen falta
Just Enough Administration crea un punto de conexión propio donde un grupo solo ve unos comandos concretos, y ejecuta con una cuenta virtual que es administradora local mientras dura la sesión. Ejemplo: que Soporte N1 pueda reiniciar la cola de impresión e IIS sin ser administrador del servidor.
$mod = 'C:\Program Files\WindowsPowerShell\Modules\JEASoporte'New-Item -ItemType Directory -Path "$mod\RoleCapabilities" -Force | Out-NullNew-ModuleManifest -Path "$mod\JEASoporte.psd1"New-PSRoleCapabilityFile -Path "$mod\RoleCapabilities\ReinicioServicios.psrc" -VisibleCmdlets 'Get-Service', @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler', 'W3SVC' } }New-PSSessionConfigurationFile -Path C:\Temp\Soporte.pssc -SessionType RestrictedRemoteServer -RunAsVirtualAccount -TranscriptDirectory 'C:\JEA\Transcripciones' -RoleDefinitions @{ 'CONTOSO\GG-Soporte-N1' = @{ RoleCapabilities = 'ReinicioServicios' } }Test-PSSessionConfigurationFile C:\Temp\Soporte.psscTrueRegister-PSSessionConfiguration -Name Soporte -Path C:\Temp\Soporte.psscDesde soporte: Enter-PSSession srv-app07 -ConfigurationName Soporte. Get-PSSessionCapability -ConfigurationName Soporte -Username CONTOSO\jperez muestra qué comandos verá cada persona antes de que pruebe. JEA también resuelve el acceso de quien no es administrador, que en el punto de conexión por defecto recibe acceso denegado.
6. Registro: quién hizo qué
- Transcripciones:
Start-Transcript -Path \\srv-gestion\Transcripciones$\$env:USERNAME-$(Get-Date -Format yyyyMMdd-HHmm).txtal empezar un cambio, o la directiva Activar la transcripción de PowerShell con una carpeta central donde los usuarios pueden escribir pero no leer. JEA las guarda enTranscriptDirectory. - Registro de bloques de script: evento 4104 en Microsoft-Windows-PowerShell/Operational del servidor, con el código que se ejecutó en remoto. Se activa por GPO, como en la guía de GPO de seguridad.
- Conexiones: Microsoft-Windows-WinRM/Operational en el destino, y en Seguridad el evento 4624 de tipo de inicio de sesión 3 con tu cuenta de administración.
7. El ticket: "WinRM cannot complete the operation / Access is denied en algunos servidores"
Jueves, 10:20. El script de comprobación del punto 2 funciona en 58 de 63 servidores. Los cinco restantes:
- srv-app07, WinRMOperationTimeout: el mensaje completo dice WinRM cannot complete the operation... a firewall exception for the WinRM service is enabled.... El puerto no responde. Por consola remota o la consola del hipervisor,
Get-NetConnectionProfiledaNetworkCategory : Public: al arrancar no encontró un controlador de dominio y la regla de firewall del perfil Dominio no aplica.Restart-Service NlaSvc -Forcelo devuelve aDomainAuthenticated; para que no se repita, se revisa por qué arranca sin ver el dominio (DNS de la tarjeta, orden de arranque frente a los controladores). - srv-app09, CannotConnect:
sc.exe \\srv-app09 query winrmdaSTATE : 1 STOPPED. Alguien lo paró y quedó en manual.sc.exe \\srv-app09 config winrm start= delayed-autoysc.exe \\srv-app09 start winrm; la GPO del punto 1 evita que vuelva a pasar. - srv-app11, AccessDenied: red y servicio bien, la cuenta no es administradora local. El servidor se movió a una OU donde no llega la GPO que añade
GG-Admins-Servidoresa Administradores. Se corrige la OU o el vínculo; no se añade tu cuenta a mano. - 10.0.30.25, por IP: The WinRM client cannot process the request... the destination machine must be added to the TrustedHosts configuration setting. Con IP no hay Kerberos. Se cambia la lista para usar el nombre DNS (
Resolve-DnsName 10.0.30.25dasrv-app14.contoso.local), no se toca TrustedHosts. - srv-app12, error 0x80090322 con Kerberos:
setspn -Q HTTP/srv-app12*muestra el SPNHTTP/srv-app12.contoso.localregistrado en la cuentasvc-intranet(una aplicación de IIS con autenticación Kerberos), así que el vale se cifra para la cuenta equivocada. Con el responsable de la aplicación, el SPN pasa a un nombre propio de la web (HTTP/intranet.contoso.local) y WinRM vuelve a usar el del equipo.
Un sexto caso que aparece en servidores fuera del dominio: con una cuenta local que es administradora, el acceso se deniega igualmente porque UAC filtra el token de las cuentas locales en conexiones de red. No se resuelve poniendo LocalAccountTokenFilterPolicy a 1 en todos los equipos (la base de seguridad de Microsoft lo mantiene filtrado a propósito para frenar el movimiento lateral): cuenta de dominio, o JEA, o como mucho una excepción documentada en ese servidor.
En el ticket: cada servidor con su error, la causa, el cambio y la repetición del comando con 63 de 63.
Siguientes pasos
- El informe semanal de Active Directory es la otra guía de este tema: se programa con una gMSA y se puede combinar con la consulta remota de este artículo.
- Para que WinRM, el firewall y el grupo de administradores lleguen igual a todos los servidores, la guía de GPO de seguridad explica el despliegue con piloto.
- Las cuentas de servicio sin contraseña que caduca están en la guía de gMSA y tareas programadas.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
What you'll get
Using PowerShell remoting as a daily operations tool: knowing what has to be in place for it to work, running a command against a hundred servers and keeping both the results and the list of those that failed, working in an interactive session, solving the "access denied" to a share from inside the session, giving support only the commands it needs, and diagnosing servers that answer WinRM cannot complete the operation or Access is denied.
Before you start
- Windows PowerShell 5.1 or PowerShell 7 on your admin workstation, in the
contoso.localdomain. - An admin account (
CONTOSO\adm-lmartin, not your mailbox account) that is local administrator on the servers, usually through a group such asGG-Admins-Servidores. - The RSAT ActiveDirectory module to query computers and SPNs.
- The remote inventory script guide explains the loop with
try/catch; here you move on to parallel execution.
1. WinRM in two minutes
PowerShell remoting travels over WinRM, Microsoft's implementation of WS-Management:
- 5985/TCP for HTTP and 5986/TCP for HTTPS. In the domain, HTTP is not sent in clear text: Kerberos encrypts the content after authentication.
- On Windows Server it is on out of the box. On Windows 11,
Enable-PSRemoting -Forcestarts the service, creates the listener and opens the firewall. If the device's network is Public, it fails unless you use-SkipNetworkProfileCheck, which only opens it to the local subnet. - For many devices, a GPO: Computer Configuration › Administrative Templates › Windows Components › Windows Remote Management (WinRM) › WinRM Service › Allow remote server management through WinRM, the WinRM service set to automatic, and the firewall rule Windows Remote Management (HTTP-In) limited to the management network.
Test-NetConnection srv-app07 -Port 5985 | Select-Object ComputerName, TcpTestSucceededComputerName TcpTestSucceeded------------ ----------------srv-app07 TrueTest-WSMan srv-app07wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsdProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsdProductVendor : Microsoft CorporationProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0Test-WSMan answers without authenticating: it confirms network, firewall and service, not permissions.
Outside the domain (a workgroup server, a DMZ, or when you type an IP), Kerberos does not work and NTLM takes over. Then you need HTTPS or the target added to TrustedHosts on your workstation, and -Credential every time:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'srv-dmz01' -Concatenate -ForceGet-Item WSMan:\localhost\Client\TrustedHosts | Select-Object ValueTrustedHosts does not make anyone "trusted": it only silences the warning that the server's identity cannot be verified. Never *, and never for domain computers, which must go by name and Kerberos. For the DMZ, better an HTTPS listener on 5986 with a certificate from the internal CA.
2. Invoke-Command against many computers
If -ComputerName receives the whole list, PowerShell opens the connections in parallel, 32 at a time by default (-ThrottleLimit). Each result arrives with the PSComputerName property, and the computers that fail go into the error variable instead of stopping the command:
$servers = (Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -SearchBase 'OU=Servidores,DC=contoso,DC=local').DNSHostName$service = 'Spooler'$r = Invoke-Command -ComputerName $servers -ThrottleLimit 64 -ErrorAction SilentlyContinue -ErrorVariable failed -ScriptBlock { $s = Get-Service -Name $using:service [pscustomobject]@{ Status = $s.Status; StartType = $s.StartType; LastBoot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime }}$r | Select-Object PSComputerName, Status, StartType, LastBoot | Export-Csv C:\Temp\spooler.csv -NoTypeInformation -Encoding UTF8$failed | ForEach-Object { [pscustomobject]@{ Computer = $_.TargetObject; Error = $_.FullyQualifiedErrorId } } | Format-Table -AutoSizeComputer Error-------- -----srv-app07.contoso.local WinRMOperationTimeout,PSSessionStateBrokensrv-app09.contoso.local CannotConnect,PSSessionStateBrokensrv-app11.contoso.local AccessDenied,PSSessionStateBroken$using:servicecarries a local variable into the remote block. Without$using:it arrives empty.-ErrorVariable failed(without$) keeps the connection errors, with the computer inTargetObject. That failure CSV matters as much as the results one: it tells you which computers you don't know the state of.- For long jobs,
-AsJobhands control back andReceive-Jobcollects later. - If you are going to run several commands in a row, open the sessions once:
$ses = New-PSSession -ComputerName $servers, thenInvoke-Command -Session $ses ...and at the endRemove-PSSession $ses.
3. Enter-PSSession: a console on the server
Enter-PSSession -ComputerName srv-app07[srv-app07]: PS C:\Users\adm-lmartin\Documents> Get-Service W3SVC | Restart-Service -PassThruStatus Name DisplayName------ ---- -----------Running W3SVC World Wide Web Publishing Service[srv-app07]: PS C:\Users\adm-lmartin\Documents> exit- Everything runs on the server, with no desktop: nothing that opens a window or asks interactively (installers with a wizard,
notepad). - To take a file across without shares:
Copy-Item C:\Temp\patch.msu -Destination C:\Temp -ToSession (New-PSSession srv-app07).
4. The second hop
Inside the session on srv-app07, Get-ChildItem \\srv-ficheros\Software$ gives Access is denied. It is not a missing permission: with Kerberos, srv-app07 knows who you are but does not have your credentials to present to a third computer. That is the second hop. The ways out, from best to worst:
- Avoid it: copy the file to the server first with
Copy-Item -ToSession, or run the job from the computer that holds the data. - Pass the credential explicitly for that access:
Invoke-Command srv-app07 { New-PSDrive -Name S -PSProvider FileSystem -Root \\srv-ficheros\Software$ -Credential $using:cred }, with$cred = Get-Credentialon your workstation. - Resource-based constrained delegation (Kerberos, no stored passwords), configured on the target:
Set-ADComputer srv-ficheros -PrincipalsAllowedToDelegateToAccount (Get-ADComputer srv-app07). It works for SMB, not for chaining another WinRM session. If it was attempted before,klist purge -li 0x3e7onsrv-app07or wait 15 minutes. - CredSSP: sends your credential to the server and leaves it there; if that server is compromised, so is your account. Only as a one-off, towards one specific server (
Enable-WSManCredSSP -Role Client -DelegateComputer srv-app07.contoso.localand-Role Serveron the target), undone afterwards withDisable-WSManCredSSP.
Unconstrained delegation (Trust this computer for delegation to any service) also "works" and is the worst option: any service on that computer can use your identity anywhere.
5. JEA: only the commands that are needed
Just Enough Administration creates its own endpoint where a group sees only specific commands, running as a virtual account that is local administrator for the duration of the session. Example: letting Service Desk L1 restart the print spooler and IIS without being server administrators.
$mod = 'C:\Program Files\WindowsPowerShell\Modules\JEASupport'New-Item -ItemType Directory -Path "$mod\RoleCapabilities" -Force | Out-NullNew-ModuleManifest -Path "$mod\JEASupport.psd1"New-PSRoleCapabilityFile -Path "$mod\RoleCapabilities\ServiceRestart.psrc" -VisibleCmdlets 'Get-Service', @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler', 'W3SVC' } }New-PSSessionConfigurationFile -Path C:\Temp\Support.pssc -SessionType RestrictedRemoteServer -RunAsVirtualAccount -TranscriptDirectory 'C:\JEA\Transcripts' -RoleDefinitions @{ 'CONTOSO\GG-Soporte-N1' = @{ RoleCapabilities = 'ServiceRestart' } }Test-PSSessionConfigurationFile C:\Temp\Support.psscTrueRegister-PSSessionConfiguration -Name Support -Path C:\Temp\Support.psscFrom support: Enter-PSSession srv-app07 -ConfigurationName Support. Get-PSSessionCapability -ConfigurationName Support -Username CONTOSO\jperez shows which commands each person will see before they try. JEA also solves access for people who are not administrators, who get access denied on the default endpoint.
6. Logging: who did what
- Transcripts:
Start-Transcript -Path \\srv-gestion\Transcripts$\$env:USERNAME-$(Get-Date -Format yyyyMMdd-HHmm).txtat the start of a change, or the policy Turn on PowerShell Transcription with a central folder where users can write but not read. JEA keeps them inTranscriptDirectory. - Script block logging: event 4104 in Microsoft-Windows-PowerShell/Operational on the server, with the code that was run remotely. It is turned on by GPO, as in the security GPOs guide.
- Connections: Microsoft-Windows-WinRM/Operational on the target, and in Security event 4624 with logon type 3 and your admin account.
7. The ticket: "WinRM cannot complete the operation / Access is denied on some servers"
Thursday, 10:20. The check script from step 2 works on 58 of 63 servers. The other five:
- srv-app07, WinRMOperationTimeout: the full message says WinRM cannot complete the operation... a firewall exception for the WinRM service is enabled.... The port does not answer. Through remote console or the hypervisor console,
Get-NetConnectionProfilegivesNetworkCategory : Public: at boot it did not find a domain controller and the Domain profile firewall rule does not apply.Restart-Service NlaSvc -Forcebrings it back toDomainAuthenticated; so it does not happen again, you check why it boots without seeing the domain (the NIC's DNS, boot order against the domain controllers). - srv-app09, CannotConnect:
sc.exe \\srv-app09 query winrmgivesSTATE : 1 STOPPED. Someone stopped it and left it on manual.sc.exe \\srv-app09 config winrm start= delayed-autoandsc.exe \\srv-app09 start winrm; the GPO from step 1 stops it happening again. - srv-app11, AccessDenied: network and service fine, the account is not a local administrator. The server was moved to an OU that the GPO adding
GG-Admins-Servidoresto Administrators does not reach. You fix the OU or the link; you do not add your account by hand. - 10.0.30.25, by IP: The WinRM client cannot process the request... the destination machine must be added to the TrustedHosts configuration setting. With an IP there is no Kerberos. You change the list to use the DNS name (
Resolve-DnsName 10.0.30.25givessrv-app14.contoso.local), you do not touch TrustedHosts. - srv-app12, error 0x80090322 with Kerberos:
setspn -Q HTTP/srv-app12*shows the SPNHTTP/srv-app12.contoso.localregistered on thesvc-intranetaccount (an IIS application with Kerberos authentication), so the ticket is encrypted for the wrong account. With the application owner, the SPN moves to a name of the website's own (HTTP/intranet.contoso.local) and WinRM goes back to using the computer's.
A sixth case shows up on servers outside the domain: with a local account that is an administrator, access is still denied because UAC filters the token of local accounts on network logons. It is not solved by setting LocalAccountTokenFilterPolicy to 1 on every computer (Microsoft's security baseline keeps it filtered on purpose to slow lateral movement): a domain account, or JEA, or at most a documented exception on that server.
In the ticket: each server with its error, the cause, the change and the command rerun with 63 of 63.
Next steps
- The weekly Active Directory report is the other guide in this topic: it is scheduled with a gMSA and can be combined with the remote query in this article.
- To make WinRM, the firewall and the administrators group reach every server the same way, the security GPOs guide explains the rollout with a pilot.
- Service accounts with no expiring password are in the guide on gMSA and scheduled tasks.
Is something missing, or different in your environment? Get in touch and I'll fix it.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.Found this useful? Every two weeks I send one practical idea for IT professionals: LinkedIn, projects, networking and hiring processes.
Apúntate y llévate las 33 ideasSign up and get the 33 ideas →