PowerShell 7 ejecuta el mismo pipeline de objetos en Windows, Linux y macOS, por lo que una sola guía cubre los tres sistemas.
Aunque los comandos no siempre se comportan igual: Get-Service no existe en Linux, ls no es un alias allí, e Invoke-Command -ComputerName te conecta a Windows PowerShell 5.1 a menos que pidas explícitamente la versión 7.
1. Referencia rápida
Cada tabla agrupa los comandos por tarea. Lo marcado como “solo Windows” requiere APIs de Windows y falla en Linux/macOS. El resto es multiplataforma.
1.1 Iniciar PowerShell y obtener ayuda
| Comando | Qué hace |
|---|---|
pwsh | Inicia PowerShell 7 (Windows PowerShell 5.1 se llama powershell.exe) |
pwsh -NoProfile -c 'Get-Date' | Ejecuta un comando sin cargar el perfil |
$PSVersionTable | Muestra versión, edición (Core o Desktop) y SO |
Get-Command -Noun Process | Busca cmdlets por sustantivo |
Get-Command *-Service | Busca cmdlets por comodín |
Get-Help Get-Process -Examples | Muestra ejemplos de uso |
Update-Help -UICulture es-ES | Descarga los archivos de ayuda completos en español |
Get-Process | Get-Member | Propiedades y métodos de los objetos de salida |
Get-Alias -Definition Get-ChildItem | Alias que apuntan a un cmdlet |
1.2 Pipeline y objetos
| Comando | Qué hace |
|---|---|
Get-Process | Where-Object CPU -gt 1 | Filtra objetos por una propiedad |
Select-Object -First 5 Name, Id | Conserva ciertas propiedades y cierta cantidad de objetos |
Sort-Object WorkingSet64 -Descending | Ordena por una propiedad |
Group-Object Extension | Cuenta objetos por valor |
Measure-Object Length -Sum -Average | Cuenta, suma, promedio, máximo |
ForEach-Object { $_.Name } | Ejecuta un bloque por cada objeto |
ForEach-Object -Parallel { ... } -ThrottleLimit 5 | Ejecuta bloques en runspaces paralelos |
Format-Table Name, Id -AutoSize | Vista de tabla (siempre al final del pipeline) |
Format-List * | Todas las propiedades, una por línea |
@{n='MB';e={$_.WorkingSet64/1MB}} | Propiedad calculada para Select-Object |
1.3 Archivos y texto
| Comando | Qué hace |
|---|---|
Get-ChildItem -Recurse -Filter *.conf | Busca archivos recursivamente (alias gci, dir) |
Get-Content app.log -Tail 20 | Últimas líneas de un archivo (-Wait lo sigue en vivo) |
Set-Content f.txt 'texto' | Escribe un archivo (sobrescribe) |
Add-Content f.txt 'más' | Añade contenido a un archivo |
New-Item -ItemType Directory logs | Crea una carpeta |
Copy-Item src dst -Recurse | Copia archivos o carpetas |
Move-Item draft.txt final.txt | Mueve o renombra |
Remove-Item old.bak -WhatIf | Previsualiza un borrado antes de ejecutarlo |
Test-Path /etc/hosts | True si la ruta existe |
Select-String -Path *.log -Pattern 'error' | El “grep” de PowerShell |
1.4 Procesos, servicios y trabajos
| Comando | Qué hace |
|---|---|
Get-Process -Id $PID | El proceso actual de PowerShell |
Stop-Process -Name sleep | Mata procesos por nombre |
Start-Job { Start-Sleep 2; 'done' } | Trabajo en segundo plano en un proceso nuevo |
Receive-Job -Id 1 -Wait -AutoRemoveJob | Recoge la salida del trabajo y lo limpia |
Start-ThreadJob { ... } | Trabajo más ligero en un hilo |
Get-Service | Where-Object Status -eq Running | Servicios en ejecución (solo Windows) |
Restart-Service Spooler | Reinicia un servicio (solo Windows) |
1.5 Variables y operadores
| Comando | Qué hace |
|---|---|
-eq -ne -gt -lt -ge -le | Comparación (insensible a mayúsculas por defecto) |
'pwsh' -like 'p*' | Coincidencia con comodines |
'error 404' -match '\d+' | Coincidencia regex, resultado en $Matches[0] |
1,2,3 -contains 2 | ¿Está el valor en la colección? |
'a,b,c' -split ',' | Divide una cadena (-join hace lo inverso) |
$null ?? 'default' | Coalescencia de nulos |
$x -gt 3 ? 'grande' : 'pequeño' | Operador ternario |
Test-Path f.txt && 'ok' | Ejecuta el segundo comando solo si el primero tuvo éxito (|| en caso de fallo) |
Get-ChildItem @params | Splatting de una tabla hash de parámetros |
[pscustomobject]@{Name='web01'} | Crea tu propio objeto |
1.6 JSON, CSV y web
| Comando | Qué hace |
|---|---|
ConvertTo-Json -Depth 5 | Objetos a JSON (la profundidad por defecto es 2) |
Get-Content f.json | ConvertFrom-Json | JSON a objetos |
Export-Csv procs.csv | Objetos a CSV (sin línea #TYPE en 7) |
Import-Csv procs.csv | Filas CSV a objetos |
Invoke-RestMethod https://api.github.com/... | Llama a una API; el JSON se parsea por ti |
Invoke-WebRequest $url -Method Head | Petición HTTP cruda con código de estado |
1.7 Errores y depuración
| Comando | Qué hace |
|---|---|
try { ... -ErrorAction Stop } catch { $_ } | Captura un error de un cmdlet |
$ErrorActionPreference = 'Stop' | Convierte todos los errores en terminantes |
-ErrorAction SilentlyContinue | Ignora un error para una llamada |
$Error[0].Exception.Message | Mensaje del último error |
Get-Error | Detalle completo del último error |
Measure-Command { ... } | Mide el tiempo de un bloque |
1.8 Remoting por SSH y WinRM
| Comando | Qué hace |
|---|---|
Enter-PSSession -HostName web01 -UserName admin | Shell interactiva por SSH |
Invoke-Command -HostName web01,db01 -UserName admin { hostname } | Ejecuta un bloque en varios hosts por SSH |
$s = New-PSSession -HostName web01 -UserName admin | Sesión reutilizable |
Get-PSSession | Remove-PSSession | Lista y cierra sesiones |
Invoke-Command -ComputerName srv01 -ConfigurationName PowerShell.7 { ... } | WinRM hacia PowerShell 7 (solo Windows) |
Enable-PSRemoting -Force | Registra los endpoints de WinRM (Windows, admin) |
Test-WSMan srv01 | ¿Está WinRM respondiendo? |
1.9 Módulos con PSResourceGet
| Comando | Qué hace |
|---|---|
Find-PSResource -Name PSScriptAnalyzer | Busca en la Galería de PowerShell |
Install-PSResource Pester -Scope CurrentUser -TrustRepository | Instala sin el prompt de confianza |
Get-InstalledPSResource | Módulos instalados desde un repositorio |
Update-PSResource -Name Pester | Actualiza un módulo |
Uninstall-PSResource -Name Pester | Elimina un módulo |
Set-PSResourceRepository -Name PSGallery -Trusted | Confía permanentemente en la galería |
Get-Module -ListAvailable | Todos los módulos en el module path |
Import-Module PSScriptAnalyzer | Carga un módulo en la sesión |
1.10 Información del sistema Windows
| Comando | Qué hace |
|---|---|
Get-CimInstance Win32_OperatingSystem | Versión del SO, último arranque, memoria libre |
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | Discos locales y espacio libre |
Get-WinEvent -LogName System -MaxEvents 5 | Entradas más recientes del registro de eventos |
Get-NetIPAddress -AddressFamily IPv4 | Direcciones IP por interfaz |
Test-NetConnection 1.1.1.1 -Port 443 | Comprobación de puerto TCP |
Get-HotFix | Actualizaciones instaladas |
Import-Module ServerManager -UseWindowsPowerShell | Carga un módulo exclusivo de 5.1 a través de una sesión proxy |
Get-ExecutionPolicy -List | Política de scripts por ámbito |
1.11 Perfil y configuración del shell
| Comando | Qué hace |
|---|---|
$PROFILE | Select-Object * | Las cuatro rutas de perfil |
New-Item -ItemType File $PROFILE -Force | Crea tu script de perfil |
Get-PSReadLineOption | Modo de edición, predicción e historial |
Get-PSReadLineKeyHandler -Chord Ctrl+r | Qué hace una tecla (búsqueda inversa del historial) |
$env:PSModulePath -split [IO.Path]::PathSeparator | De dónde se cargan los módulos |
2. Instalación de PowerShell 7
2.1 Windows
Instala el MSI desde la página de releases de GitHub. Los parámetros provienen de la página oficial de instalación de Microsoft; ENABLE_PSREMOTING=1 es clave porque registra el endpoint de WinRM para PowerShell 7:
$ver = (Invoke-RestMethod https://api.github.com/repos/PowerShell/PowerShell/releases/latest).tag_name.TrimStart('v')
Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/v$ver/PowerShell-$ver-win-x64.msi" `
-OutFile "C:\lab\PowerShell-$ver-win-x64.msi"
Start-Process msiexec.exe -Wait -ArgumentList "/package C:\lab\PowerShell-$ver-win-x64.msi /quiet ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1 USE_MU=1 ENABLE_MU=1 ADD_PATH=1"
⚠️ Nota de actualización: La página de Microsoft indica que desde la versión 7.6,
wingetinstala el paquete MSIX por defecto, y a partir de 7.7 no se distribuye MSI. Las instalaciones por script con MSI (como la anterior) tienen vida limitada. En Windows 11 o Server con Desktop Experience, usa:winget install --id Microsoft.PowerShell --source winget
El MSI instala en C:\Program Files\PowerShell\7 y deja Windows PowerShell 5.1 en su sitio, por lo que ambas shells coexisten en el PATH.
2.2 Ubuntu 26.04 (y derivados)
En Ubuntu 26.04 el repositorio apt oficial de Microsoft no incluía el paquete estable en el momento de la prueba (solo powershell-preview). El método que funciona es instalar el .deb universal desde GitHub:
VER=$(curl -s https://api.github.com/repos/PowerShell/PowerShell/releases/latest \
| grep '"tag_name"' | cut -d '"' -f 4 | sed 's/^v//')
echo "$VER"
wget https://github.com/PowerShell/PowerShell/releases/download/v${VER}/powershell_${VER}-1.deb_amd64.deb
sudo apt-get install -y ./powershell_${VER}-1.deb_amd64.deb
Una instalación sana reporta PSEdition: Core y el nombre de la distro:
pwsh -NoProfile -c '$PSVersionTable'
Salida esperada en Ubuntu 26.04:
Name Value
---- -----
PSVersion 7.6.9
PSEdition Core
GitCommitId 7.6.9
OS Ubuntu 26.04.1 LTS
Platform Unix
PSRemotingProtocolVersion 2.4
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
💡 Tip de actualización: En Ubuntu 24.04 LTS el repositorio de Microsoft sí trae el paquete estable (
sudo apt-get install -y powershell). Para RHEL 10 / Rocky Linux 10 y macOS existen guías oficiales equivalentes.
2.3 Tres comprobaciones rápidas tras instalar
which pwsh # Linux: /usr/bin/pwsh | Windows: C:\Program Files\PowerShell\7
pwsh --version # Debe coincidir con la versión de la API de GitHub
$PSVersionTable.PSEdition # Debe leer "Core", no "Desktop"
2.4 Comparativa PowerShell 7 vs Windows PowerShell 5.1
Mediciones reales en una VM Windows Server 2025 (tres ejecuciones de -NoProfile -c exit):
| Comprobación | PowerShell 7 (pwsh) | Windows PowerShell 5.1 |
|---|---|---|
| Arranque (3 ejecuciones) | 198–248 ms | 540–662 ms |
(Get-Command).Count | 2052 | 2014 |
curl | No es alias | Alias de Invoke-WebRequest |
&&, ??, ternario | Funcionan | No disponibles |
ForEach-Object -Parallel | Funciona | No disponible |
| Endpoint WinRM por defecto | 5.1 salvo -ConfigurationName PowerShell.7 | 5.1 |
En Ubuntu 26.04 sobre una VM de 2 vCPU, pwsh -NoProfile -c exit tardó 391–428 ms.
💡 La diferencia de arranque se nota: una tarea programada que ejecuta un script corto cada minuto ahorra ~⅓ de segundo por ejecución bajo
pwsh.
3. Encontrar el cmdlet correcto
Usa Get-Command para localizar el cmdlet, Get-Help para conocer sus parámetros y Get-Member para ver qué objetos devuelve. Los cmdlets siguen el patrón Verbo-Sustantivo, así que una búsqueda por sustantivo suele listar toda la familia:
Get-Command -Noun Service
En Ubuntu no imprime nada — primer indicio de que los cmdlets de servicio son exclusivos de Windows. En Linux, (Get-Command).Count devolvió 298 comandos frente a 2052 en Windows Server, porque la mayoría de módulos de Windows (redes, almacenamiento, CIM, registros de eventos) no tienen build para Linux.
3.1 La ayuda no viene instalada
PowerShell 7 se distribuye sin los archivos de ayuda completos. La primera llamada a Get-Help avisa y solo muestra la sintaxis:
Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help.
Ejecuta una vez por usuario (45 segundos en la VM de prueba):
Update-Help -UICulture es-ES
# o para todos los usuarios (requiere shell elevada / sudo):
Update-Help -UICulture es-ES -Scope AllUsers
3.2 Los alias son la trampa multiplataforma
En Windows, ls y cat son alias de Get-ChildItem y Get-Content. En Linux PowerShell elimina esos alias para que los binarios reales sigan funcionando:
Get-Alias: This command cannot find a matching alias because an alias with the name 'ls' does not exist.
Por eso ls | Select-Object Name funciona en Windows y no devuelve nada útil en Linux: /usr/bin/ls emite texto, no objetos.
⚠️ Escribe siempre el nombre completo del cmdlet en scripts que puedan correr en ambos sistemas.
Y ojo con alias inesperados: una función llamada
Rnunca se ejecutó porqueres alias integrado deInvoke-History— los alias tienen prioridad sobre las funciones. Comprueba cualquier nombre corto conGet-Aliasantes de usarlo.
4. Pipeline y objetos
El pipeline de PowerShell pasa objetos .NET, no texto, así que filtras y ordenas por propiedades con nombre en lugar de cortar columnas con awk. Este comando selecciona los cinco procesos que más memoria usan y añade una columna calculada en MB:
Get-Process |
Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 Name, Id, @{n='MB';e={[math]::Round($_.WorkingSet64/1MB,1)}}
Salida real en la VM Ubuntu:
Name Id MB
---- -- --
pwsh 33466 110.50
snapd 32569 43.10
unattended-upgrade-shutdown 1534 32.00
networkd-dispatcher 1441 29.20
packagekitd 32450 22.20
💡
pwshfue el proceso más grande (~110 MB). Importa si planeas correr pwsh en un contenedor pequeño o en una VM de 512 MB: la shell sola consume una quinta parte de la memoria antes de cargar nada.
4.1 Agrupar y medir
Group-Object y Measure-Object responden a las preguntas de “cuántos” que en Bash requerirían sort | uniq -c:
Get-ChildItem /usr/bin | Measure-Object -Property Length -Sum -Average -Maximum
El resultado es a su vez un objeto, así que puedes encadenarlo o leer una propiedad con .Sum:
Count : 1107
Average : 143424.717253839
Sum : 158771162
Maximum : 25214552
4.2 Paralelismo con ForEach-Object -Parallel
Es la característica de PowerShell 7 que más tiempo ahorra en scripts de administración, porque cada bloque corre en su propio runspace. Medición con cinco Start-Sleep 1:
(Measure-Command { 1..5 | ForEach-Object { Start-Sleep 1 } }).TotalSeconds
(Measure-Command { 1..5 | ForEach-Object -Parallel { Start-Sleep 1 } -ThrottleLimit 5 }).TotalSeconds
- Secuencial: 5.01 s
- Paralelo: 1.33 s (Ubuntu) / 1.08 s (Windows Server)
- Con
-ThrottleLimit 2y cuatro ítems en Windows: 2.06 s (el límite funciona)
⚠️ Dos límites críticos:
- Las variables del ámbito externo no son visibles dentro del bloque paralelo. Pásalas con
$using:nombre.- El coste de los runspaces puede hacer que bloques pequeños y rápidos sean más lentos que un bucle normal. Úsalo para trabajo lento e independiente (llamadas de red, comprobaciones remotas), no para bucles triviales.
4.3 Regla de oro con Format-*
Mantén Format-Table y Format-List al final absoluto del pipeline. Convierten objetos en registros de visualización, así que cualquier cosa después (Export-Csv, ConvertTo-Json) recibe datos de formato en lugar de tus propiedades.
5. Operadores y variables
Los operadores de comparación son palabras (-eq, -gt, -like, -match) porque > y < son redirección. Son insensibles a mayúsculas salvo que uses la forma c (-ceq). PowerShell 7 añade atajos que 5.1 no tiene:
'error 404' -match '\d+'; $Matches[0]
$null ?? 'default'
$x = 5; $x -gt 3 ? 'big' : 'small'
Test-Path /etc/hosts && 'exists'
Get-Item /nope 2>$null || 'missing'
Salida (cada línea imprime lo esperado):
True
404
default
big
True
exists
missing
💡 Fíjate en que
Test-Pathimprime suTrueantes deexists, porque&&no se traga la salida del primer comando.
5.1 Splatting
Mantén legibles los comandos largos. Pon los parámetros en una tabla hash y pásalos con @ en vez de $:
$p = @{Path='/etc'; Filter='*.conf'}
(Get-ChildItem @p).Count # 31
5.2 Objetos personalizados
Cuando necesites tus propios registros, créalos con [pscustomobject]:
[pscustomobject]@{Name='web01';Port=443}
A diferencia de una tabla hash plana, el resultado se imprime como tabla y funciona con Export-Csv y Sort-Object como cualquier salida de cmdlet.
6. JSON, CSV y APIs REST
6.1 Invoke-RestMethod: JSON a objetos en un paso
Invoke-RestMethod https://api.github.com/repos/PowerShell/PowerShell/releases/latest |
Select-Object tag_name, published_at
tag_name published_at
-------- ------------
v7.6.9 10/25/2026 17:26:05
Sin jq, sin parseo de cadenas. Por eso el script de instalación en Windows lee la última versión en una sola línea.
6.2 La trampa de ConvertTo-Json
Se detiene en profundidad 2 y aplana todo lo más profundo en un nombre de tipo:
@{a=@{b=@{c=@{d=1}}}} | ConvertTo-Json
Resulting JSON is truncated as serialization has exceeded the set depth of 2.
{
"a": {
"b": {
"c": "System.Collections.Hashtable"
}
}
}
⚠️ Los datos desaparecen y solo queda el nombre de la clase. Añade
-Depth 5o más en cadaConvertTo-Jsonque escriba un archivo de configuración o el cuerpo de una petición. Un payload truncado suele fallar lejos de la línea que lo causó.
6.3 CSV
Export-Csv en PowerShell 7 ya no escribe la línea #TYPE que añade 5.1, así que el archivo empieza directamente con los nombres de columna y otras herramientas lo leen sin problemas:
Get-Process | Select-Object -First 3 Name, Id | Export-Csv procs.csv -NoTypeInformation
Import-Csv procs.csv
7. Manejo de errores
Envuelve la llamada en try/catch y añade -ErrorAction Stop, porque la mayoría de errores de cmdlet son no terminantes y de otro modo se saltan el catch:
try { Get-Item /nope -ErrorAction Stop } catch { "Caught: $($_.Exception.Message)" }
Caught: Cannot find path '/nope' because it does not exist.
Sin -ErrorAction Stop, la misma línea imprime el error en rojo y el catch nunca se ejecuta.
7.1 Buenas prácticas
- Para todo un script, pon
$ErrorActionPreference = 'Stop'al inicio en lugar de añadir el parámetro a cada llamada. - Dentro del
catch,$_.FullyQualifiedErrorIdda un ID estable (p. ej.PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand), más seguro de comprobar que el texto del mensaje. - Cuando necesites el cuadro completo tras un fallo,
Get-Errorimprime el tipo de excepción, el objeto destino, la línea del script y el stack trace.
8. Cmdlets exclusivos de Windows
Todo lo que habla con un subsistema de Windows solo funciona en Windows, porque los cmdlets son envoltorios finos sobre APIs sin equivalente en Linux. Antes de compartir un script con usuarios de Linux, asegúrate de que evita estas familias o las protege con if ($IsWindows) { ... }:
- ❌ Sin cmdlets de servicio (
Get-Service,Restart-Service) fuera del guard - ❌ Sin consultas CIM/WMI (
Get-CimInstance) fuera del guard - ❌ Sin lectura del registro de eventos (
Get-WinEvent) ni rutas de registro (HKLM:) fuera del guard - ❌ Sin cmdlets de red (
Get-NetIPAddress,Test-NetConnection) niGet-HotFixfuera del guard
En Linux fallan antes de ejecutarse, con el mismo error que da una errata:
The term 'Get-Service' is not recognized as a name of a cmdlet, function, script file, or executable program.
8.1 Ejemplo real en Windows Server 2025
Devuelve SO, último arranque y memoria libre en un solo objeto:
Get-CimInstance Win32_OperatingSystem |
Select-Object Caption, Version, LastBootUpTime,
@{n='FreeGB';e={[math]::Round($_.FreePhysicalMemory/1MB,2)}}
Caption Version LastBootUpTime FreeGB
------- ------- -------------- ------
Microsoft Windows Server 2025 Datacenter 10.0.26100 10/29/2026 8:59:25 AM 5.05
💡
FreePhysicalMemoryviene en kilobytes; dividir por1MBlo convierte a gigabytes. Ese tipo de detalle de unidades queda oculto por la propiedad calculada.
8.2 Módulos que solo cargan en 5.1
Algunos módulos de Windows aún solo cargan en Windows PowerShell 5.1. ServerManager es uno; Get-Module -ListAvailable ServerManager no devuelve nada en pwsh. Cárgalo con -UseWindowsPowerShell y PowerShell 7 lo ejecuta en un proceso 5.1 oculto:
Import-Module ServerManager -UseWindowsPowerShell
Get-WindowsFeature | Where-Object Installed | Select-Object -First 5 Name
La importación avisa del compromiso: los objetos devueltos son copias deserializadas, así que sus métodos desaparecen y solo quedan las propiedades.
💡 Sorpresa:
Get-WmiObjectfue eliminado de PowerShell 7, pero en Windows Server seguía respondiendo porqueGet-Commandlo reveló como una función proxy que reenvía a la sesiónWinPSCompatSession. Los scripts que dependen de él corren más lentos y reciben objetos deserializados. Migra aGet-CimInstance, nativo en 7 y con soporte remoto.
8.3 Política de ejecución
Get-ExecutionPolicy -List
En Server 2025 mostró RemoteSigned en LocalMachine para ambas shells. En Linux la política es fija Unrestricted y Set-ExecutionPolicy RemoteSigned falla con Operation is not supported on this platform.
9. Remoting por SSH y WinRM
PowerShell 7 hace remoting por SSH en todas las plataformas con -HostName, y por WinRM en Windows con -ComputerName. SSH es el que cruza sistemas operativos.
9.1 Requisitos en cada destino (SSH)
- Un servidor SSH corriendo y accesible en el puerto 22.
- Una línea
Subsystem powershellen la config desshdque arranquepwshen modo servidor. - Login por clave para el usuario remoto, para que los scripts nunca esperen un prompt de contraseña.
9.2 Configurar el subsistema en Ubuntu (drop-in)
Ubuntu lee archivos drop-in de /etc/ssh/sshd_config.d/, así que la línea puede vivir en su propio archivo y sobrevivir a actualizaciones:
echo 'Subsystem powershell /usr/bin/pwsh -sshs -NoLogo' | sudo tee /etc/ssh/sshd_config.d/50-powershell.conf
sudo sshd -t
sudo systemctl restart ssh
sshd -t no devuelve nada = sintaxis válida. Verifica con:
sudo sshd -T | grep -i subsystem
⚠️ Sin esa línea, el cliente conecta y luego falla con un error que nunca menciona la config (ver sección 12).
9.3 Configurar el subsistema en Windows Server 2025
Abre C:\ProgramData\ssh\sshd_config y añade la línea junto a la de sftp. Microsoft usa la ruta corta 8.3 porque sshd no maneja el espacio en “Program Files”:
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
9.4 Preparar las claves
En el lado Linux:
ssh-keygen -t ed25519
ssh-keyscan 10.0.1.51 >> ~/.ssh/known_hosts
Pega la clave pública en C:\ProgramData\ssh\administrators_authorized_keys y bloquea el archivo a Administrators y SYSTEM (Microsoft lo exige; el login por clave falla si otras cuentas pueden leerlo):
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
Restart-Service sshd
9.5 Lanzar comandos contra ambos sistemas
Invoke-Command -HostName web01,db01 -UserName admin { hostname }
La columna PSComputerName indica de qué host viene cada resultado — crucial cuando un bloque corre en veinte servidores.
💡 Aviso de criptografía post-cuántica: Ubuntu 26.04 avisa porque Windows Server trae OpenSSH 9.5, que carece del intercambio de claves más nuevo. Es solo un aviso, la sesión funciona; la solución definitiva es un OpenSSH más reciente en Windows.
9.6 WinRM: el detalle que atrapa a todos
Desde pwsh, Invoke-Command -ComputerName sin -ConfigurationName conecta al endpoint Microsoft.PowerShell, que es Windows PowerShell 5.1. Comparación real:
Invoke-Command -ComputerName localhost -Credential $cred -ScriptBlock { $PSVersionTable.PSVersion.ToString() }
Invoke-Command -ComputerName localhost -Credential $cred -ConfigurationName PowerShell.7 -ScriptBlock { $PSVersionTable.PSVersion.ToString() }
5.1.26100.7462
7.6.9
⚠️ Un script que use
??o-Paralleldentro del bloque remoto fallará en el endpoint por defecto aunque lo lances desdepwsh. Pasa-ConfigurationName PowerShell.7o define$PSSessionConfigurationName = 'PowerShell.7'en tu perfil.
El endpoint PowerShell.7 existe porque el MSI se ejecutó con ENABLE_PSREMOTING=1. Si se instaló sin él:
Enable-PSRemoting -Force
Linux no puede usar -ComputerName en absoluto: Invoke-Command -ComputerName localhost en Ubuntu falla con el error de WSMan de la sección 12. Para flotas mixtas, estandariza en SSH y deja WinRM para Windows-a-Windows.
10. Módulos con PSResourceGet
Usa Install-PSResource con -TrustRepository en scripts, o confía en la Galería una vez con Set-PSResourceRepository. PowerShell 7 incluye dos gestores: Microsoft.PowerShell.PSResourceGet (los cmdlets *-PSResource) y el antiguo PowerShellGet con Install-Module. Instalan en las mismas carpetas, así que un módulo instalado con uno aparece en ambos.
10.1 La trampa de la galería no confiable
Buscar funciona, pero una instalación sin -TrustRepository muestra un prompt de confirmación y en una sesión no interactiva falla con un error que no menciona nada de confianza:
Untrusted repository
You are installing the modules from an untrusted repository. If you trust this
repository, change its Trusted value by running the Set-PSResourceRepository
cmdlet. Are you sure you want to install the PSResource from 'PSGallery'?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "N"):Object reference not set to an instance of an object.
⚠️ Añadir
-Confirm:$falseno ayuda — el prompt y el fallo vuelven igual.
10.2 La solución
Install-PSResource -Name PSScriptAnalyzer -Scope CurrentUser -TrustRepository
Get-InstalledPSResource
Name Version Prerelease Repository Description
---- ------- ---------- ---------- -----------
PSScriptAnalyzer 1.25.0 PSGallery PSScriptAnalyzer provides script analysis...
10.3 Estrategias para CI/CD (agentes de build)
Elige una de estas para que ninguna instalación se detenga en un prompt:
- Cada llamada a
Install-PSResourcelleva-TrustRepository. - La imagen ejecuta una vez
Set-PSResourceRepository -Name PSGallery -Trusted(verificable conGet-PSResourceRepository→Trusted: True). - Los builds internos apuntan a tu propio repositorio, registrado con
Register-PSResourceRepositoryy marcado como confiable.
💡
PSScriptAnalyzeres especialmente útil para el problema de alias de la sección 3: apuntado afunction f { gci }, marcaPSAvoidUsingCmdletAliasescon el mensaje'gci' is an alias of 'Get-ChildItem'.
10.4 Rutas de módulos por plataforma
| Ámbito | Ubuntu 26.04 | Windows Server 2025 |
|---|---|---|
| Usuario actual | ~/.local/share/powershell/Modules | Documents\PowerShell\Modules |
| Todos los usuarios | /usr/local/share/powershell/Modules | C:\Program Files\PowerShell\Modules |
| Incluidos con pwsh | /opt/microsoft/powershell/7/Modules | C:\Program Files\PowerShell\7\Modules |
| Rutas de 5.1 | — | También en el path, después de las de pwsh |
Windows pwsh también busca en las carpetas de módulos de 5.1 después de las suyas. Los que necesitan el .NET Framework completo cargan a través de la sesión de compatibilidad (como ServerManager).
11. Perfil y configuración del shell
$PROFILE contiene la ruta del perfil de usuario actual y host actual; tiene tres hermanos para los otros ámbitos. En Ubuntu el archivo no existe hasta que lo creas:
$PROFILE | Select-Object *
New-Item -ItemType File -Path $PROFILE -Force
Linux sigue el esquema XDG:
AllUsersAllHosts : /opt/microsoft/powershell/7/profile.ps1
AllUsersCurrentHost : /opt/microsoft/powershell/7/Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : /home/usuario/.config/powershell/profile.ps1
CurrentUserCurrentHost : /home/usuario/.config/powershell/Microsoft.PowerShell_profile.ps1
💡 En Windows el perfil vive en
Documents\PowerShell, carpeta distinta deDocuments\WindowsPowerShellde 5.1: tu perfil antiguo no te sigue apwsh. Copia lo que aún necesites.
11.1 Mantén el perfil ligero
Se ejecuta en cada shell interactiva nueva y en cada llamada programada a pwsh sin -NoProfile, así que una importación lenta se paga muchas veces al día. Un perfil vacío no costó nada medible (418–440 ms frente a 391–428 ms sin él), pero cada módulo importado suma.
Antes de versionar tu perfil, compruébalo contra esta lista:
- ✅
pwsh -c exitarranca en unos cientos de ms depwsh -NoProfile -c exit - ✅ El perfil solo importa módulos que usas en la mayoría de sesiones
- ✅ Las líneas exclusivas de Windows van dentro de
if ($IsWindows) { }si el perfil se sincroniza a Linux/macOS
11.2 PSReadLine
Por defecto corre en modo Emacs, con predicciones HistoryAndPlugin en línea, Ctrl+r para búsqueda inversa y el historial en ~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt.
⚠️ Ese historial guarda cada comando que escribes, incluidos secretos pegados en la línea de comandos. Bórralo tras un error.
Get-PSReadLineOption
Get-PSReadLineKeyHandler -Chord Ctrl+r
12. Solución de errores comunes
Estos son los errores reales encontrados al construir el laboratorio, con causa y solución:
❌ “subsystem request failed on channel 0”
- Causa: el
sshddestino no tiene la líneaSubsystem powershell. SSH conectó, pero el subsistema falló (el mensaje completo dice The SSH transport process has abruptly terminated…). - Solución: añade la línea de la sección 9, ejecuta
sshd -ty reiniciasshd.
❌ “This parameter set requires WSMan, and no supported WSMan client library was found”
- Causa: usaste
-ComputerNameen Linux/macOS (eso implica WinRM, ypwshallí no tiene cliente WSMan). - Solución: cambia a
-HostNamey-UserNamepara que la sesión vaya por SSH.
❌ “Connecting to remote server localhost failed … Access is denied”
- Causa:
Invoke-Command -ComputerName localhostejecutado como la cuenta SYSTEM, que no tiene credencial de red para WinRM. - Solución: una sesión de admin normal o un
-Credentialexplícito de administrador local.
❌ “Object reference not set to an instance of an object” en Install-PSResource
- Causa: el prompt de confianza de PSGallery no tenía a quién preguntar.
- Solución: añade
-TrustRepository, o ejecutaSet-PSResourceRepository -Name PSGallery -Trusteden las máquinas de build.
❌ “The term ‘Get-Service’ is not recognized”
- Causa: el cmdlet solo existe en Windows.
- Solución: en Linux, llama directamente a
systemctldesdepwsh(p. ej.systemctl is-active ssh), y envuelve los bloques Windows-only enif ($IsWindows) { ... }.
❌ “Operation is not supported on this platform” en Set-ExecutionPolicy
- Causa: la política de ejecución es una característica de Windows.
- Solución: Linux/macOS siempre corren como
Unrestricted. Elimina la llamada de los scripts multiplataforma o protégela con$IsWindows.
❌ “E: Unable to locate package powershell” en Ubuntu 26.04
- Causa: el feed apt de 26.04 solo tenía
powershell-previewen la prueba. - Solución: instala el
.debde GitHub (sección 2) y revisa más tarde conapt-cache policy powershell.
13. Checklist de configuración
Recorre esta lista en cada equipo o servidor nuevo. Todo se verifica desde la shell en segundos:
-
pwsh --versionmuestra la versión esperada y$PSVersionTable.PSEditionleeCore. -
Update-Help -UICulture es-ESse ejecutó para tu cuenta (o una vez con-Scope AllUsers), asíGet-Helpmuestra ejemplos completos. -
Test-Path $PROFILEdevuelveTrue, y el perfil solo carga módulos de uso diario. -
Get-PSResourceRepositorymuestra PSGallery confiable, o tus scripts pasan-TrustRepository. -
sshd -Tlista el subsistemapowershellen cada host que planees alcanzar con-HostName. - En Windows,
Get-PSSessionConfigurationlistaPowerShell.7, y tus llamadas WinRM pasan-ConfigurationName PowerShell.7. - Los scripts compartidos usan nombres completos de cmdlet, e
Invoke-ScriptAnalyzerno reporta avisosPSAvoidUsingCmdletAliases.
🎁 Bonus: actualizaciones y mejoras sugeridas
Añadidas a esta versión en español, fruto del análisis del contenido original:
Update-Help -UICulture es-ESen lugar deen-US— la ayuda oficial en español existe y ahorra fricción a lectores hispanohablantes.- Advertencia sobre el fin del MSI: desde 7.7 no se distribuye MSI; el script con
msiexecdel original tiene fecha de caducidad. Migra awinget/MSIX. - Nota sobre
-ErrorAction SilentlyContinuevsStopen un script completo: prefiere$ErrorActionPreference = 'Stop'para detectar errores temprano ySilentlyContinuesolo donde realmente esperas un fallo benigno. Set-StrictMode -Version Latest(sugerencia añadida): colócalo en tus scripts serios para que PowerShell falle ante variables no inicializadas o propiedades inexistentes — la mayoría de bugs silenciosos vienen de ahí.PSScriptAnalyzercomo pre-commit: intégralo en un hook o en CI (Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning,Error) para que los alias y errores comunes no lleguen a producción.- Rutas de módulos en macOS (añadido):
~/.local/share/powershell/Modulespara usuario y/usr/local/share/powershell/Modulespara todos — igual que Linux. - Nota sobre
$using:enForEach-Object -Parallel: también admite pasar variables del ámbito externo; úsalo siempre que el bloque necesite datos externos.
Resumen final: PowerShell 7 es un shell multiplataforma real, pero el comportamiento de los cmdlets, los alias, el remoting y los módulos depende del sistema operativo. Escribe siempre nombres completos de cmdlet, protege los bloques Windows-only con
$IsWindows, estandariza en SSH para flotas mixtas y confía explícitamente en PSGallery (-TrustRepository) en cualquier script no interactivo. Con estas tres reglas evitas el 90 % de los errores recogidos en esta guía.
Descubre más desde CIBERED
Suscríbete y recibe las últimas entradas en tu correo electrónico.
