Qué vas a conseguir
Un script que, dada una lista de equipos, se conecta a cada uno, saca modelo, número de serie, versión de Windows, disco libre y último arranque, y deja un CSV con una fila por equipo, incluidos los que no respondieron y por qué. Es el patrón que repetirás en la mitad de los scripts de IT.
Antes de empezar
- PowerShell 5.1 (viene con Windows) o PowerShell 7 (
winget install Microsoft.PowerShell). El script vale en los dos. - WinRM activado en los equipos de destino (en dominio suele estarlo por GPO; si no,
Enable-PSRemoting -Forceen cada uno, o la GPO *Permitir la administración remota del servidor a través de WinRM*). - Tu cuenta con administrador local en los equipos, o una credencial para pasarla con
-Credential. - Un editor con PowerShell integrado: VS Code con la extensión de PowerShell marca errores mientras escribes.
1. Objetos, no texto
La diferencia con cmd y con Bash: un comando de PowerShell devuelve objetos con propiedades, no líneas. Por eso puedes seleccionar, filtrar y exportar sin cortar cadenas.
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, LastBootUpTimeCaption Version LastBootUpTime------- ------- --------------Microsoft Windows 11 Enterprise 10.0.26100 03/10/2026 8:12:40(Get-CimInstance Win32_OperatingSystem).LastBootUpTime -lt (Get-Date).AddDays(-14)TrueGet-CimInstance sustituye a Get-WmiObject (obsoleto). Get-Member sobre cualquier objeto te enseña qué propiedades tiene.
2. El script
#Requires -Version 5.1<#.SYNOPSIS Inventario básico de equipos por WinRM, con un CSV de salida..EXAMPLE .\Get-DeviceInventory.ps1 -ComputerName (Get-Content equipos.txt) -OutFile inventario.csv#>[CmdletBinding()]param( [Parameter(Mandatory)] [string[]] $ComputerName, [string] $OutFile = "inventario-$(Get-Date -Format yyyyMMdd).csv", [pscredential] $Credential) $remote = { $os = Get-CimInstance Win32_OperatingSystem $cs = Get-CimInstance Win32_ComputerSystem $bio = Get-CimInstance Win32_BIOS $c = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" [pscustomobject]@{ Computer = $env:COMPUTERNAME Model = "$($cs.Manufacturer) $($cs.Model)" Serial = $bio.SerialNumber OS = $os.Caption Build = $os.Version FreeGB = [math]::Round($c.FreeSpace / 1GB, 1) LastBoot = $os.LastBootUpTime LastUser = $cs.UserName }} $results = foreach ($pc in $ComputerName) { Write-Verbose "Consultando $pc" try { $p = @{ ComputerName = $pc; ScriptBlock = $remote; ErrorAction = 'Stop' } if ($Credential) { $p.Credential = $Credential } $r = Invoke-Command @p $r | Select-Object * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName | Add-Member -NotePropertyName Status -NotePropertyValue OK -PassThru } catch { [pscustomobject]@{ Computer = $pc; Model = $null; Serial = $null; OS = $null; Build = $null FreeGB = $null; LastBoot = $null; LastUser = $null Status = "ERROR: $($_.Exception.Message -replace '\s+',' ')" } }} $results | Export-Csv -Path $OutFile -NoTypeInformation -Encoding UTF8 -Delimiter ';'$ok = ($results | Where-Object Status -eq 'OK').CountWrite-Host "$ok de $($ComputerName.Count) equipos respondieron. CSV: $OutFile"Lo que hace que sea un script y no un apaño: param con tipos (así -ComputerName acepta una lista), [CmdletBinding()] que da -Verbose gratis, el bloque remoto que devuelve un objeto por equipo, try/catch con ErrorAction Stop para que un equipo apagado no pare el bucle, y el CSV con ; como separador para que Excel en español lo abra en columnas.
3. Ejecutarlo
.\Get-DeviceInventory.ps1 -ComputerName LAPTOP-7F2K9Q, DESK-MAD-201, LAPTOP-OLD-1 -VerboseVERBOSE: Consultando LAPTOP-7F2K9QVERBOSE: Consultando DESK-MAD-201VERBOSE: Consultando LAPTOP-OLD-12 de 3 equipos respondieron. CSV: inventario-20261021.csvImport-Csv .\inventario-20261021.csv -Delimiter ';' | Format-Table Computer, FreeGB, LastBoot, StatusComputer FreeGB LastBoot Status-------- ------ -------- ------LAPTOP-7F2K9Q 21,4 03/10/2026 8:12:40 OKDESK-MAD-201 188,0 20/10/2026 7:55:02 OKLAPTOP-OLD-1 ERROR: Connecting to remote server LAPTOP-OLD-1 failed with the following error message : WinRM cannot complete the operation...Para cientos de equipos, Invoke-Command acepta la lista entera a la vez y los consulta en paralelo (-ThrottleLimit 32); el bucle de arriba es más lento pero deja claro qué equipo falló y por qué.
4. Política de ejecución y firma
Si al ejecutarlo aparece *"no se puede cargar porque la ejecución de scripts está deshabilitada"*, es la política de ejecución. No es seguridad contra atacantes; es un seguro contra ejecutar scripts por accidente.
Get-ExecutionPolicy -List Scope ExecutionPolicy ----- --------------- MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser Undefined LocalMachine RestrictedSet-ExecutionPolicy -Scope CurrentUser RemoteSignedRemoteSigned ejecuta lo que escribes tú y exige firma a lo descargado. Si la empresa la fija por GPO en AllSigned, los scripts del equipo se firman con un certificado de firma de código de la CA interna (Set-AuthenticodeSignature). Es lo correcto en producción y encaja con el repositorio de la guía de Git: solo lo revisado se firma.
Siguientes pasos
- Cambia la lista de equipos por
Get-ADComputer -Filter {OperatingSystem -like "Windows 11*"}y tendrás el inventario de todo el dominio. - Pasa el resultado por
PSScriptAnalyzer(Invoke-ScriptAnalyzer .\Get-DeviceInventory.ps1) antes de subirlo al repo. - En el Nivel 3 verás Microsoft Graph desde PowerShell: el mismo patrón, contra Entra ID e Intune en lugar de WinRM.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
What you'll get
A script that, given a list of devices, connects to each one, pulls model, serial number, Windows version, free disk and last boot, and leaves a CSV with one row per device, including the ones that didn't answer and why. It is the pattern you'll repeat in half of all IT scripts.
Before you start
- PowerShell 5.1 (ships with Windows) or PowerShell 7 (
winget install Microsoft.PowerShell). The script works on both. - WinRM enabled on the target devices (in a domain it usually is, by GPO; if not,
Enable-PSRemoting -Forceon each, or the GPO *Allow remote server management through WinRM*). - Your account as local administrator on the devices, or a credential to pass with
-Credential. - An editor with PowerShell support: VS Code with the PowerShell extension flags errors as you type.
1. Objects, not text
The difference from cmd and Bash: a PowerShell command returns objects with properties, not lines. That is why you can select, filter and export without slicing strings.
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, LastBootUpTimeCaption Version LastBootUpTime------- ------- --------------Microsoft Windows 11 Enterprise 10.0.26100 03/10/2026 8:12:40(Get-CimInstance Win32_OperatingSystem).LastBootUpTime -lt (Get-Date).AddDays(-14)TrueGet-CimInstance replaces Get-WmiObject (deprecated). Get-Member on any object shows you which properties it has.
2. The script
#Requires -Version 5.1<#.SYNOPSIS Basic device inventory over WinRM, with a CSV output..EXAMPLE .\Get-DeviceInventory.ps1 -ComputerName (Get-Content devices.txt) -OutFile inventory.csv#>[CmdletBinding()]param( [Parameter(Mandatory)] [string[]] $ComputerName, [string] $OutFile = "inventory-$(Get-Date -Format yyyyMMdd).csv", [pscredential] $Credential) $remote = { $os = Get-CimInstance Win32_OperatingSystem $cs = Get-CimInstance Win32_ComputerSystem $bio = Get-CimInstance Win32_BIOS $c = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" [pscustomobject]@{ Computer = $env:COMPUTERNAME Model = "$($cs.Manufacturer) $($cs.Model)" Serial = $bio.SerialNumber OS = $os.Caption Build = $os.Version FreeGB = [math]::Round($c.FreeSpace / 1GB, 1) LastBoot = $os.LastBootUpTime LastUser = $cs.UserName }} $results = foreach ($pc in $ComputerName) { Write-Verbose "Querying $pc" try { $p = @{ ComputerName = $pc; ScriptBlock = $remote; ErrorAction = 'Stop' } if ($Credential) { $p.Credential = $Credential } $r = Invoke-Command @p $r | Select-Object * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName | Add-Member -NotePropertyName Status -NotePropertyValue OK -PassThru } catch { [pscustomobject]@{ Computer = $pc; Model = $null; Serial = $null; OS = $null; Build = $null FreeGB = $null; LastBoot = $null; LastUser = $null Status = "ERROR: $($_.Exception.Message -replace '\s+',' ')" } }} $results | Export-Csv -Path $OutFile -NoTypeInformation -Encoding UTF8$ok = ($results | Where-Object Status -eq 'OK').CountWrite-Host "$ok of $($ComputerName.Count) devices answered. CSV: $OutFile"What makes it a script rather than a hack: param with types (so -ComputerName takes a list), [CmdletBinding()] which gives you -Verbose for free, the remote block returning one object per device, try/catch with ErrorAction Stop so a switched-off device doesn't stop the loop, and a UTF-8 CSV Excel opens straight away (add -Delimiter ';' if your Excel locale expects semicolons).
3. Run it
.\Get-DeviceInventory.ps1 -ComputerName LAPTOP-7F2K9Q, DESK-MAD-201, LAPTOP-OLD-1 -VerboseVERBOSE: Querying LAPTOP-7F2K9QVERBOSE: Querying DESK-MAD-201VERBOSE: Querying LAPTOP-OLD-12 of 3 devices answered. CSV: inventory-20261021.csvImport-Csv .\inventory-20261021.csv | Format-Table Computer, FreeGB, LastBoot, StatusComputer FreeGB LastBoot Status-------- ------ -------- ------LAPTOP-7F2K9Q 21.4 03/10/2026 8:12:40 OKDESK-MAD-201 188.0 20/10/2026 7:55:02 OKLAPTOP-OLD-1 ERROR: Connecting to remote server LAPTOP-OLD-1 failed with the following error message : WinRM cannot complete the operation...For hundreds of devices, Invoke-Command takes the whole list at once and queries them in parallel (-ThrottleLimit 32); the loop above is slower but makes it obvious which device failed and why.
4. Execution policy and signing
If running it shows *"cannot be loaded because running scripts is disabled"*, that is the execution policy. It is not security against attackers; it is a guard against running scripts by accident.
Get-ExecutionPolicy -List Scope ExecutionPolicy ----- --------------- MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser Undefined LocalMachine RestrictedSet-ExecutionPolicy -Scope CurrentUser RemoteSignedRemoteSigned runs what you write and requires a signature on anything downloaded. If the company pins it to AllSigned by GPO, the team's scripts are signed with a code-signing certificate from the internal CA (Set-AuthenticodeSignature). That is the right thing in production and fits the repository from the Git guide: only what is reviewed gets signed.
Next steps
- Swap the device list for
Get-ADComputer -Filter {OperatingSystem -like "Windows 11*"}and you have the inventory of the whole domain. - Run the result through
PSScriptAnalyzer(Invoke-ScriptAnalyzer .\Get-DeviceInventory.ps1) before pushing it to the repo. - In Level 3 you'll see Microsoft Graph from PowerShell: the same pattern, against Entra ID and Intune instead of WinRM.
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 →