PowerShell 7: Guía Completa de Cmdlets, Remoting y Módulos

Tutoriales de Windows Cibered

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.

Índice

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

ComandoQué hace
pwshInicia PowerShell 7 (Windows PowerShell 5.1 se llama powershell.exe)
pwsh -NoProfile -c 'Get-Date'Ejecuta un comando sin cargar el perfil
$PSVersionTableMuestra versión, edición (Core o Desktop) y SO
Get-Command -Noun ProcessBusca cmdlets por sustantivo
Get-Command *-ServiceBusca cmdlets por comodín
Get-Help Get-Process -ExamplesMuestra ejemplos de uso
Update-Help -UICulture es-ESDescarga los archivos de ayuda completos en español
Get-Process | Get-MemberPropiedades y métodos de los objetos de salida
Get-Alias -Definition Get-ChildItemAlias que apuntan a un cmdlet

1.2 Pipeline y objetos

ComandoQué hace
Get-Process | Where-Object CPU -gt 1Filtra objetos por una propiedad
Select-Object -First 5 Name, IdConserva ciertas propiedades y cierta cantidad de objetos
Sort-Object WorkingSet64 -DescendingOrdena por una propiedad
Group-Object ExtensionCuenta objetos por valor
Measure-Object Length -Sum -AverageCuenta, suma, promedio, máximo
ForEach-Object { $_.Name }Ejecuta un bloque por cada objeto
ForEach-Object -Parallel { ... } -ThrottleLimit 5Ejecuta bloques en runspaces paralelos
Format-Table Name, Id -AutoSizeVista 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

ComandoQué hace
Get-ChildItem -Recurse -Filter *.confBusca 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 logsCrea una carpeta
Copy-Item src dst -RecurseCopia archivos o carpetas
Move-Item draft.txt final.txtMueve o renombra
Remove-Item old.bak -WhatIfPrevisualiza un borrado antes de ejecutarlo
Test-Path /etc/hostsTrue si la ruta existe
Select-String -Path *.log -Pattern 'error'El “grep” de PowerShell

1.4 Procesos, servicios y trabajos

ComandoQué hace
Get-Process -Id $PIDEl proceso actual de PowerShell
Stop-Process -Name sleepMata procesos por nombre
Start-Job { Start-Sleep 2; 'done' }Trabajo en segundo plano en un proceso nuevo
Receive-Job -Id 1 -Wait -AutoRemoveJobRecoge la salida del trabajo y lo limpia
Start-ThreadJob { ... }Trabajo más ligero en un hilo
Get-Service | Where-Object Status -eq RunningServicios en ejecución (solo Windows)
Restart-Service SpoolerReinicia un servicio (solo Windows)

1.5 Variables y operadores

ComandoQué hace
-eq -ne -gt -lt -ge -leComparació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 @paramsSplatting de una tabla hash de parámetros
[pscustomobject]@{Name='web01'}Crea tu propio objeto

1.6 JSON, CSV y web

ComandoQué hace
ConvertTo-Json -Depth 5Objetos a JSON (la profundidad por defecto es 2)
Get-Content f.json | ConvertFrom-JsonJSON a objetos
Export-Csv procs.csvObjetos a CSV (sin línea #TYPE en 7)
Import-Csv procs.csvFilas CSV a objetos
Invoke-RestMethod https://api.github.com/...Llama a una API; el JSON se parsea por ti
Invoke-WebRequest $url -Method HeadPetición HTTP cruda con código de estado

1.7 Errores y depuración

ComandoQué hace
try { ... -ErrorAction Stop } catch { $_ }Captura un error de un cmdlet
$ErrorActionPreference = 'Stop'Convierte todos los errores en terminantes
-ErrorAction SilentlyContinueIgnora un error para una llamada
$Error[0].Exception.MessageMensaje del último error
Get-ErrorDetalle completo del último error
Measure-Command { ... }Mide el tiempo de un bloque

1.8 Remoting por SSH y WinRM

ComandoQué hace
Enter-PSSession -HostName web01 -UserName adminShell 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 adminSesión reutilizable
Get-PSSession | Remove-PSSessionLista y cierra sesiones
Invoke-Command -ComputerName srv01 -ConfigurationName PowerShell.7 { ... }WinRM hacia PowerShell 7 (solo Windows)
Enable-PSRemoting -ForceRegistra los endpoints de WinRM (Windows, admin)
Test-WSMan srv01¿Está WinRM respondiendo?

1.9 Módulos con PSResourceGet

ComandoQué hace
Find-PSResource -Name PSScriptAnalyzerBusca en la Galería de PowerShell
Install-PSResource Pester -Scope CurrentUser -TrustRepositoryInstala sin el prompt de confianza
Get-InstalledPSResourceMódulos instalados desde un repositorio
Update-PSResource -Name PesterActualiza un módulo
Uninstall-PSResource -Name PesterElimina un módulo
Set-PSResourceRepository -Name PSGallery -TrustedConfía permanentemente en la galería
Get-Module -ListAvailableTodos los módulos en el module path
Import-Module PSScriptAnalyzerCarga un módulo en la sesión

1.10 Información del sistema Windows

ComandoQué hace
Get-CimInstance Win32_OperatingSystemVersión del SO, último arranque, memoria libre
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3'Discos locales y espacio libre
Get-WinEvent -LogName System -MaxEvents 5Entradas más recientes del registro de eventos
Get-NetIPAddress -AddressFamily IPv4Direcciones IP por interfaz
Test-NetConnection 1.1.1.1 -Port 443Comprobación de puerto TCP
Get-HotFixActualizaciones instaladas
Import-Module ServerManager -UseWindowsPowerShellCarga un módulo exclusivo de 5.1 a través de una sesión proxy
Get-ExecutionPolicy -ListPolítica de scripts por ámbito

1.11 Perfil y configuración del shell

ComandoQué hace
$PROFILE | Select-Object *Las cuatro rutas de perfil
New-Item -ItemType File $PROFILE -ForceCrea tu script de perfil
Get-PSReadLineOptionModo de edición, predicción e historial
Get-PSReadLineKeyHandler -Chord Ctrl+rQué hace una tecla (búsqueda inversa del historial)
$env:PSModulePath -split [IO.Path]::PathSeparatorDe 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, winget instala 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ónPowerShell 7 (pwsh)Windows PowerShell 5.1
Arranque (3 ejecuciones)198–248 ms540–662 ms
(Get-Command).Count20522014
curlNo es aliasAlias de Invoke-WebRequest
&&, ??, ternarioFuncionanNo disponibles
ForEach-Object -ParallelFuncionaNo disponible
Endpoint WinRM por defecto5.1 salvo -ConfigurationName PowerShell.75.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 R nunca se ejecutó porque r es alias integrado de Invoke-History — los alias tienen prioridad sobre las funciones. Comprueba cualquier nombre corto con Get-Alias antes 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

💡 pwsh fue 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 2 y cuatro ítems en Windows: 2.06 s (el límite funciona)

⚠️ Dos límites críticos:

  1. Las variables del ámbito externo no son visibles dentro del bloque paralelo. Pásalas con $using:nombre.
  2. 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-Path imprime su True antes de exists, 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 5 o más en cada ConvertTo-Json que 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, $_.FullyQualifiedErrorId da 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-Error imprime 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) ni Get-HotFix fuera 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

💡 FreePhysicalMemory viene en kilobytes; dividir por 1MB lo 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-WmiObject fue eliminado de PowerShell 7, pero en Windows Server seguía respondiendo porque Get-Command lo reveló como una función proxy que reenvía a la sesión WinPSCompatSession. Los scripts que dependen de él corren más lentos y reciben objetos deserializados. Migra a Get-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)

  1. Un servidor SSH corriendo y accesible en el puerto 22.
  2. Una línea Subsystem powershell en la config de sshd que arranque pwsh en modo servidor.
  3. 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 -Parallel dentro del bloque remoto fallará en el endpoint por defecto aunque lo lances desde pwsh. Pasa -ConfigurationName PowerShell.7 o 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:$false no 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-PSResource lleva -TrustRepository.
  • La imagen ejecuta una vez Set-PSResourceRepository -Name PSGallery -Trusted (verificable con Get-PSResourceRepository → Trusted: True).
  • Los builds internos apuntan a tu propio repositorio, registrado con Register-PSResourceRepository y marcado como confiable.

💡 PSScriptAnalyzer es especialmente útil para el problema de alias de la sección 3: apuntado a function f { gci }, marca PSAvoidUsingCmdletAliases con el mensaje 'gci' is an alias of 'Get-ChildItem'.

10.4 Rutas de módulos por plataforma

ÁmbitoUbuntu 26.04Windows Server 2025
Usuario actual~/.local/share/powershell/ModulesDocuments\PowerShell\Modules
Todos los usuarios/usr/local/share/powershell/ModulesC:\Program Files\PowerShell\Modules
Incluidos con pwsh/opt/microsoft/powershell/7/ModulesC:\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 de Documents\WindowsPowerShell de 5.1: tu perfil antiguo no te sigue a pwsh. 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 exit arranca en unos cientos de ms de pwsh -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 sshd destino no tiene la línea Subsystem 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 -t y reinicia sshd.

❌ “This parameter set requires WSMan, and no supported WSMan client library was found”

  • Causa: usaste -ComputerName en Linux/macOS (eso implica WinRM, y pwsh allí no tiene cliente WSMan).
  • Solución: cambia a -HostName y -UserName para que la sesión vaya por SSH.

❌ “Connecting to remote server localhost failed … Access is denied”

  • Causa: Invoke-Command -ComputerName localhost ejecutado como la cuenta SYSTEM, que no tiene credencial de red para WinRM.
  • Solución: una sesión de admin normal o un -Credential explí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 ejecuta Set-PSResourceRepository -Name PSGallery -Trusted en 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 systemctl desde pwsh (p. ej. systemctl is-active ssh), y envuelve los bloques Windows-only en if ($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-preview en la prueba.
  • Solución: instala el .deb de GitHub (sección 2) y revisa más tarde con apt-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 --version muestra la versión esperada y $PSVersionTable.PSEdition lee Core.
  • Update-Help -UICulture es-ES se ejecutó para tu cuenta (o una vez con -Scope AllUsers), así Get-Help muestra ejemplos completos.
  • Test-Path $PROFILE devuelve True, y el perfil solo carga módulos de uso diario.
  • Get-PSResourceRepository muestra PSGallery confiable, o tus scripts pasan -TrustRepository.
  • sshd -T lista el subsistema powershell en cada host que planees alcanzar con -HostName.
  • En Windows, Get-PSSessionConfiguration lista PowerShell.7, y tus llamadas WinRM pasan -ConfigurationName PowerShell.7.
  • Los scripts compartidos usan nombres completos de cmdlet, e Invoke-ScriptAnalyzer no reporta avisos PSAvoidUsingCmdletAliases.

🎁 Bonus: actualizaciones y mejoras sugeridas

Añadidas a esta versión en español, fruto del análisis del contenido original:

  1. Update-Help -UICulture es-ES en lugar de en-US — la ayuda oficial en español existe y ahorra fricción a lectores hispanohablantes.
  2. Advertencia sobre el fin del MSI: desde 7.7 no se distribuye MSI; el script con msiexec del original tiene fecha de caducidad. Migra a winget/MSIX.
  3. Nota sobre -ErrorAction SilentlyContinue vs Stop en un script completo: prefiere $ErrorActionPreference = 'Stop' para detectar errores temprano y SilentlyContinue solo donde realmente esperas un fallo benigno.
  4. 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í.
  5. PSScriptAnalyzer como 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.
  6. Rutas de módulos en macOS (añadido): ~/.local/share/powershell/Modules para usuario y /usr/local/share/powershell/Modules para todos — igual que Linux.
  7. Nota sobre $using: en ForEach-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.

Vistas: 59

Descubre más desde CIBERED

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Scroll al inicio