Visita guiada a PowerShell
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Introducción
PowerShell promete revolucionar el mundo de la gestión de sistemas y los shells de línea de comandos. Desde sus canalizaciones basadas en objetos hasta su enfoque para administradores, pasando por su enorme alcance en otras tecnologías de gestión de Microsoft, PowerShell mejora drásticamente la productividad de administradores y usuarios avanzados por igual.
Cuando estás aprendiendo una nueva tecnología, es natural que al principio te sientas desconcertado por todas las características y funciones desconocidas. Tal vez esto sea especialmente cierto para los usuarios nuevos en PowerShell, porque puede ser su primera experiencia con un shell de línea de comandos con todas las funciones. O peor aún, han oído hablar de las fantásticas capacidades de scripting integradas de PowerShell y temen verse obligados a entrar en un mundo de programación que habían evitado activamente hasta ahora.
Afortunadamente, estos temores son totalmente erróneos; PowerShell es una shell que crece contigo y crece sobre ti. Hagamos un recorrido para ver de lo que es capaz:
-
PowerShell funciona con comandos y aplicaciones estándar de Windows. No tienes que desechar lo que ya conoces y utilizas.
-
PowerShell introduce un nuevo y potente tipo de comando. Los comandos PowerShell (llamados cmdlets) comparten una sintaxis común
Verb-Noun
y ofrecen muchas mejoras de usabilidad respecto a los comandos estándar. -
PowerShell entiende de objetos. Trabajar directamente con objetos ricamente estructurados hace que trabajar con (y combinar) comandos PowerShell sea inmensamente más fácil que trabajar en el mundo de texto plano de los shells tradicionales.
-
PowerShell está pensado para administradores. Incluso con todos sus avances, PowerShell se centra mucho en su uso como shell interactivo: la experiencia de introducir comandos en una aplicación PowerShell en ejecución.
-
PowerShell admite el descubrimiento. Utilizando tres sencillos comandos, puedes aprender y descubrir casi todo lo que PowerShell tiene que ofrecer.
-
PowerShell permite el scripting ubicuo. Con un lenguaje de scripting completo que funciona directamente desde la línea de comandos, PowerShell te permite automatizar tareas con facilidad.
-
PowerShell tiende puentes entre muchas tecnologías. Al permitirte trabajar con .NET, COM, WMI, XML y Active Directory, PowerShell hace que trabajar con estas tecnologías antes aisladas sea más fácil que nunca.
-
PowerShell simplifica la gestión de los almacenes de datos. A través de su modelo de proveedor, PowerShell te permite gestionar almacenes de datos utilizando las mismas técnicas que ya usas para gestionar archivos y carpetas.
Exploraremos cada uno de estos pilares en esta visita introductoria a PowerShell. Si estás ejecutando cualquier versión compatible de Windows (Windows 7 o posterior, o Windows 2012 R2 o posterior), Windows PowerShell ya está instalado. Dicho esto, un importante paso adelante respecto a esta instalación por defecto es el PowerShell Core de código abierto. Si quieres adelantarte un poco, puedes obtener más información sobre la actualización a PowerShell Core (o simplemente "PowerShell") en la Receta 1.1.
Un caparazón interactivo
En su núcleo, PowerShell es ante todo un intérprete de comandos interactivo. Aunque admite secuencias de comandos y otras potentes funciones, su enfoque como shell lo sustenta todo.
Iniciarse en PowerShell es una simple cuestión de lanzar powerShell.exe en lugar de cmd.exe -losshells empiezan a divergir a medida que exploras la funcionalidad intermedia y avanzada, pero puedes ser productivo en PowerShell inmediatamente.
Para iniciar PowerShell, haz clic en Start
y escribe PowerShell
(o pwsh
si te has adelantado).
Se abre una ventana del símbolo del sistema de PowerShell casi idéntica a la del símbolo del sistema tradicional de sus antepasados. El indicador PS C:\Users\Lee>
indica que PowerShell está listo para ser introducido, como se muestra en la Figura P-1.
Una vez que hayas lanzado el símbolo del sistema PowerShell, puedes introducir comandos al estilo DOS y Unix para navegar por el sistema de archivos como lo harías con cualquier símbolo del sistema Windows o Unix, como en la sesión interactiva que se muestra en el Ejemplo P-1. En este ejemplo, utilizamos los comandos pushd
, cd
, dir
, pwd
, y popd
para almacenar la ubicación actual, navegar por el sistema de archivos, listar los elementos del directorio actual y volver a la ubicación original. ¡Pruébalo!
Ejemplo P-1. Introducir muchos comandos estándar de manipulación de archivos al estilo DOS y Unix produce los mismos resultados que cuando los utilizas con cualquier otro shell de Windows
PS C:\Users\Lee> function prompt { "PS > " } PS > pushd . PS > cd \ PS > dir Directory: C:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 5/8/2007 8:37 PM Blurpark d---- 5/15/2016 4:32 PM Chocolatey d---- 3/8/2020 12:45 PM DXLab d---- 4/30/2020 7:00 AM Go d---- 4/2/2016 3:05 PM Intel d-r-- 12/15/2020 1:41 PM Program Files d-r-- 11/28/2020 5:06 PM Program Files (x86) d---- 5/12/2019 6:37 PM Python27 d---- 3/25/2018 1:11 PM Strawberry d---- 12/16/2020 8:13 AM temp d-r-- 8/11/2020 5:02 PM Users da--- 12/16/2020 10:51 AM Windows PS > popd PS > pwd Path ---- C:\Users\Lee
En este ejemplo, nuestro primer comando personaliza el prompt. En cmd.exe, la personalización del prompt se parece a prompt $P$G
. En Bash, es como PS1="[\h] \w> "
. EnPowerShell, defines una función que devuelve lo que quieras que se muestre. La receta 11.2 presenta las funciones y cómo escribirlas.
El comando pushd
es un nombre alternativo (alias) del comando PowerShell Push-Location
, que tiene un nombre mucho más descriptivo. Del mismo modo, los comandos cd
, dir
, popd
y pwd
tienen homólogos más memorables.
Aunque navegar por el sistema de archivos es útil, también lo es ejecutar las herramientas que conoces y adoras, como ipconfig
y notepad
. Escribe el nombre del comando y verás resultados como los que se muestran en el Ejemplo P-2.
Ejemplo P-2. Las herramientas y aplicaciones de Windows, como ipconfig, se ejecutan en PowerShell igual que en cmd.exe
PS > ipconfig Windows IP Configuration Ethernet adapter Wireless Network Connection 4: Connection-specific DNS Suffix . : hsd1.wa.comcast.net. IP Address. . . . . . . . . . . . : 192.168.1.100 Subnet Mask . . . . . . . . . . . : 255.255.255.0 Default Gateway . . . . . . . . . : 192.168.1.1 PS > notepad (notepad launches)
Introduciendo ipconfig
se muestran las direcciones IP de tus conexiones de red actuales. Si escribes notepad
, se ejecutará -como cabría esperar- el editor Bloc de Notas que viene con Windows. Prueba en tu propio ordenador.
Comandos estructurados (Cmdlets)
En además de soportar los ejecutables tradicionales de Windows, PowerShell introduce un nuevo y potente tipo de comando llamado cmdlet (pronunciado "comando-let"). Todos los cmdlets de se nombran siguiendo un Verb-Noun
patrón, como Get-Process
, Get-Content
y Stop-Process
.
PS > Get-Process -Name lsass Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 668 13 6228 1660 46 932 lsass
En este ejemplo, proporcionas un valor al parámetro ProcessName
para obtener un proceso concreto por su nombre.
Nota
Una vez que conozcas el puñado de verbos comunes en PowerShell, aprender a trabajar con nuevos sustantivos te resultará mucho más fácil. Aunque nunca antes hayas trabajado con un determinado objeto (como un Servicio), las acciones estándar Get
, Set
, Start
, y Stop
siguen siendo aplicables. Para ver una lista de estos verbos comunes, consulta la Tabla J-1 del Apéndice J.
Sin embargo, no siempre tienes que escribir los nombres completos de los cmdlets. PowerShell te permite utilizar la tecla Tab
para autocompletar los nombres de los cmdlets y los nombres de los parámetros:
PS > Get-Pro<TAB
> -N<TAB
> lsass
Para un uso interactivo rápido, incluso eso puede ser escribir demasiado. Para ayudarte a mejorar tu eficacia, PowerShell define alias para todos los comandos comunes y te permite definir los tuyos propios. Además de los nombres de alias, PowerShell sólo requiere que escribas lo suficiente del nombre del parámetro para desambiguarlo del resto de parámetros de ese cmdlet. PowerShell tampoco distingue entre mayúsculas y minúsculas. Utilizando el alias incorporado gps
(que representa el cmdlet Get-Process
) junto con el acortamiento de parámetros, puedes escribir en su lugar:
PS > gps -n lsass
Yendo a aún más lejos, PowerShell admite parámetros posicionales en los cmdlets. Los parámetros posicionales te permiten proporcionar valores de parámetros en una posición determinada de la línea de comandos, en lugar de tener que especificarlos por su nombre. El cmdlet Get-Process
toma un nombre de proceso como primer parámetro posicional. Este parámetro de admite incluso comodines:
PS > gps l*s
Integración profunda de objetos
PowerShell comienza a flexionar más su músculo a medida que exploras la forma en que maneja los datos estructurados y los objetos ricamente funcionales. Por ejemplo, el siguiente comando genera una simple cadena de texto. Como nada captura esa salida, PowerShell te la muestra:
PS > "Hello World" Hello World
La cadena que acabas de generar es, de hecho, un objeto totalmente funcional del .NET Framework. En el ejemplo , puedes acceder a su propiedad Length
, que te indica cuántos caracteres hay en la cadena. Para acceder a una propiedad, coloca un punto entre el objeto y el nombre de su propiedad:
PS > "Hello World".Length 11
Todos los comandos PowerShell que producen salida generan esa salida también como objetos. Por ejemplo, el cmdlet Get-Process
genera un objeto System.Diagnostics.Process
, que puedes almacenar en una variable. En PowerShell, los nombres de las variables empiezan por un carácter $
. Si tienes una instancia del Bloc de Notas en ejecución, el siguiente comando almacena una referencia a ella:
$process
=
Get-Process
notepad
Como es un objeto Process
totalmente funcional del .NET Framework, puedes llamar a métodos de ese objeto para realizar acciones sobre él. Este comando llama al método Kill()
, que detiene un proceso. Para acceder a un método, coloca un punto entre el objeto y el nombre de su método:
$process
.
Kill
()
PowerShell admite esta funcionalidad más directamente a través del cmdlet Stop-Process
, pero este ejemplo demuestra un punto importante sobre tu capacidad para interactuar con estos objetos enriquecidos.
Administradores como usuarios de primera clase
Aunque la compatibilidad de PowerShell con objetos del .NET Framework acelera el pulso de la mayoría de los usuarios, PowerShell sigue centrándose mucho en las tareas administrativas. Por ejemplo, PowerShell admite MB
(para megabyte) y GB
(para gigabyte) como algunas de sus constantes administrativas estándar. Por ejemplo, ¿cuántos memes GIF caben en un disco duro de 800 GB?
PS > 800GB / 2.2MB 372363.636363636
Aunque el .NET Framework es tradicionalmente una plataforma de desarrollo, ¡contiene una gran cantidad de funcionalidades útiles también para los administradores! De hecho, convierte a PowerShell en un gran calendario. Por ejemplo, ¿es 2096 un año bisiesto? PowerShell puede decírtelo:
PS > [DateTime]::IsLeapYear(2096) True
Yendo más lejos, ¿cómo podrías determinar cuánto tiempo queda hasta el Epochalypse del Año 2038? El siguiente comando convierte "01/19/2038"
(la fecha del problema del Año 2038) en una fecha, y luego le resta la fecha actual. Almacena el resultado en la variable $result
, y luego accede a la propiedad TotalDays
.
PS > $result = [DateTime] "01/19/2038" - [DateTime]::Now PS > $result.TotalDays 6242.49822756465
Comandos componibles
Siempre que un comando genere una salida, puedes utilizar un carácter de canalización (|) para pasar esa salida directamente a otro comando como entrada. Si el segundo comando entiende los objetos producidos por el primero, puede operar sobre los resultados. Puedes encadenar muchos comandos de esta forma, creando potentes composiciones a partir de unas pocas operaciones sencillas. Por ejemplo, el siguiente comando obtiene todos los elementos del directorio Ruta1 y los mueve al directorio Ruta2:
Get-Item
Path1
\*
|
Move-Item
-Destination
Path2
Puedes crear comandos aún más complejos añadiendo cmdlets adicionales a la canalización. En el Ejemplo P-3, el primer comando obtiene todos los procesos que se están ejecutando en el sistema. los pasa al cmdlet Where-Object
, que ejecuta una comparación con cada elemento entrante. En este caso, la comparación es $_.Handles -ge 500
, que comprueba si la propiedad Handles
del objeto actual (representada por la variable $_
) es mayor o igual que 500
. Para cada objeto en el que esta comparación sea verdadera, pasas los resultados al cmdlet Sort-Object
, pidiéndole que ordene los elementos por su propiedad Handles
. Por último, pasas los objetos al cmdlet Format-Table
para generar una tabla que contenga los Handles
, Name
y Description
del proceso.
Ejemplo P-3. Puedes construir comandos PowerShell más complejos utilizando canalizaciones para enlazar cmdlets, como se muestra aquí con Get-Process, Where-Object, Sort-Object y Format-Table
PS > Get-Process | Where-Object { $_.Handles -ge 500 } | Sort-Object Handles | Format-Table Handles,Name,Description -Auto Handles Name Description ------- ---- ----------- 588 winlogon 592 svchost 667 lsass 725 csrss 742 System 964 WINWORD Microsoft Office Word 1112 OUTLOOK Microsoft Office Outlook 2063 svchost
Técnicas para protegerte de ti mismo
Aunque los alias, comodines y tuberías componibles de son potentes, su uso en comandos que modifican la información del sistema puede ser fácilmente desesperante. Después de todo, ¿qué hace este comando? Piénsalo, pero no lo intentes todavía:
PS > gps [b-t]*[c-r] | Stop-Process
Parece que detiene todos los procesos que empiezan por las letras b
hasta t
y acaban por las letras c
hasta r
. ¿Cómo puedes estar seguro? Deja que PowerShell te lo diga. Para los comandos que modifican datos, PowerShell admite los parámetros -WhatIf
y -Confirm
que te permiten ver lo que haría un comando:
PS > gps [b-t]*[c-r] | Stop-Process -whatif What if: Performing operation "Stop-Process" on Target "ctfmon (812)". What if: Performing operation "Stop-Process" on Target "Ditto (1916)". What if: Performing operation "Stop-Process" on Target "dsamain (316)". What if: Performing operation "Stop-Process" on Target "ehrecvr (1832)". What if: Performing operation "Stop-Process" on Target "ehSched (1852)". What if: Performing operation "Stop-Process" on Target "EXCEL (2092)". What if: Performing operation "Stop-Process" on Target "explorer (1900)". (...)
En esta interacción, utilizar el parámetro -WhatIf
con el comando Stop-Process
pipelined te permite previsualizar qué procesos de tu sistema se detendrán antes de realizar realmente la operación.
Ten en cuenta que este ejemplo no es un reto. En palabras de un crítico
No sólo lo paró todo, sino que en una de mis viejas máquinas, ¡forzó un apagado con sólo un minuto de aviso!
Aunque fue muy divertido... ¡Al menos tuve tiempo suficiente para guardarlo todo antes!
Comandos comunes de descubrimiento
Aunque leer una visita guiada es útil, creo que la mayor parte del aprendizaje se produce de forma ad hoc. Para encontrar todos los comandos que coinciden con un determinado comodín, utiliza el cmdlet Get-Command
. Por ejemplo, introduciendo lo siguiente, puedes averiguar qué comandos de PowerShell (y aplicaciones de Windows) contienen la palabra proceso:
PS > Get-Command *process* CommandType Name Definition ----------- ---- ---------- Cmdlet Get-Process Get-Process [[-Name] <Str... Application qprocess.exe c:\windows\system32\qproc... Cmdlet Stop-Process Stop-Process [-Id] <Int32...
Para ver lo que hace un comando como Get-Process
, utiliza el cmdlet Get-Help
, de la siguiente manera:
PS > Get-Help Get-Process
Como PowerShell te permite trabajar con objetos del .NET Framework, proporciona el cmdlet Get-Member
para recuperar información sobre las propiedades y métodos que admite un objeto, como un .NET System.String
. Al enviar una cadena al comando Get-Member
se muestra el nombre de su tipo y sus miembros:
PS > "Hello World" | Get-Member TypeName: System.String Name MemberType Definition ---- ---------- ---------- (...) PadLeft Method System.String PadLeft(Int32 tota... PadRight Method System.String PadRight(Int32 tot... Remove Method System.String Remove(Int32 start... Replace Method System.String Replace(Char oldCh... Split Method System.String[] Split(Params Cha... StartsWith Method System.Boolean StartsWith(String... Substring Method System.String Substring(Int32 st... ToCharArray Method System.Char[] ToCharArray(), Sys... ToLower Method System.String ToLower(), System.... ToLowerInvariant Method System.String ToLowerInvariant() ToString Method System.String ToString(), System... ToUpper Method System.String ToUpper(), System.... ToUpperInvariant Method System.String ToUpperInvariant() Trim Method System.String Trim(Params Char[]... TrimEnd Method System.String TrimEnd(Params Cha... TrimStart Method System.String TrimStart(Params C... Chars ParameterizedProperty System.Char Chars(Int32 index) {... Length Property System.Int32 Length {get;}
Secuencias de comandos ubicuas
PowerShell no distingue entre los comandos escritos en la línea de comandos y los comandos escritos en un script. Tus cmdlets favoritos funcionan en scripts y tus técnicas de scripting favoritas (por ejemplo, la sentencia foreach
) funcionan directamente en la línea de comandos. Por ejemplo, para sumar el recuento de asas de todos los procesos en ejecución:
PS > $handleCount = 0 PS > foreach($process in Get-Process) { $handleCount += $process.Handles } PS > $handleCount 19403
Aunque PowerShell proporciona un comando (Measure-Object
) para medir estadísticas sobre colecciones, este breve ejemplo muestra cómo PowerShell te permite aplicar técnicas que normalmente requieren un lenguaje de scripting o programación aparte.
Además de utilizar las palabras clave de scripting de PowerShell, también puedes crear y trabajar directamente con objetos del .NET Framework con los que estés familiarizado. PowerShell se convierte casi en el modo inmediato de C# en Visual Studio. El Ejemplo P-4 muestra cómo PowerShell te permite interactuar fácilmente con el.NET Framework.
Ejemplo P-4. Utilizar objetos del .NET Framework para recuperar una página web y procesar su contenido
PS > $webClient = New-Object System.Net.WebClient PS > $content = $webClient.DownloadString( "https://devblogs.microsoft.com/powershell/feed/") PS > $content.Substring(0,1000) <?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" > <channel> <title>PowerShell</title> <atom:link href="https://devblogs.microsoft.com/powershell/feed/" <link>https://devblogs.microsoft.com/powershell</link> <description>Automating the world one-liner at a time...</description> (...)
Desarrollo ad hoc
Al difuminar las líneas entre la administración interactiva y la escritura de scripts, los búferes del historial de las sesiones PowerShell se convierten rápidamente en la base para el desarrollo de scripts ad hoc. En este ejemplo, llamas al cmdlet Get-History
para recuperar el historial de tu sesión. Para cada elemento, obtienes su propiedad CommandLine
(lo que has escrito) y envías la salida a un nuevo archivo de script.
PS > Get-History | ForEach-Object { $_.CommandLine } > c:\temp\script.ps1 PS > notepad c:\temp\script.ps1 (save the content you want to keep) PS > c:\temp\script.ps1
Nota
Si es la primera vez que ejecutas un script en PowerShell, tendrás que configurar tu política de ejecución. Para más información sobre cómo seleccionar una política de ejecución, consulta la Receta 18.1.
Para más detalles sobre cómo guardar tu historial de sesiones en un script, consulta la Receta 1.22.
Tecnologías puente
Hemos visto cómo PowerShell te permite aprovechar al máximo el .NET Framework en tus tareas, pero su compatibilidad con tecnologías comunes va aún más allá. Como muestra el Ejemplo P-5 (continuación del Ejemplo P-4), PowerShell es compatible con XML.
Ejemplo P-5. Trabajar con contenido XML en PowerShell
PS > $xmlContent = [xml] $content PS > $xmlContent xml xml-stylesheet rss --- -------------- --- version="1.0" encoding... type="text/xsl" href="... rss PS > $xmlContent.rss version : 2.0 content : http://purl.org/rss/1.0/modules/content/ wfw : http://wellformedweb.org/CommentAPI/ dc : http://purl.org/dc/elements/1.1/ atom : http://www.w3.org/2005/Atom sy : http://purl.org/rss/1.0/modules/syndication/ slash : http://purl.org/rss/1.0/modules/slash/ channel : channel PS > $xmlContent.rss.channel.item | select Title title ----- PowerShell 7.2 Preview 2 release Announcing PowerShell Crescendo Preview.1 You’ve got Help! SecretManagement preview 6 and SecretStore preview 4 Announcing PowerShell 7.1 Announcing PSReadLine 2.1+ with Predictive IntelliSense Updating help for the PSReadLine module PowerShell Working Groups (...)
PowerShell también te permite trabajar con Windows Management Instrumentation (WMI) y Common Information Model (CIM):
PS > Get-CimInstance Win32_Bios SMBIOSBIOSVersion : ASUS A7N8X Deluxe ACPI BIOS Rev 1009 Manufacturer : Phoenix Technologies, LTD Name : Phoenix - AwardBIOS v6.00PG SerialNumber : xxxxxxxxxxx Version : Nvidia - 42302e31
O, como muestra Ejemplo P-6, puedes trabajar con Interfaces de Servicio de Directorio Activo (ADSI).
Ejemplo P-6. Trabajar con Active Directory en PowerShell
PS > [ADSI] "WinNT://./Administrator" | Format-List * UserFlags : {66113} MaxStorage : {-1} PasswordAge : {19550795} PasswordExpired : {0} LoginHours : {255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255} FullName : {} Description : {Built-in account for administering the computer/domain} BadPasswordAttempts : {0} LastLogin : {5/21/2007 3:00:00 AM} HomeDirectory : {} LoginScript : {} Profile : {} HomeDirDrive : {} Parameters : {} PrimaryGroupID : {513} Name : {Administrator} MinPasswordLength : {0} MaxPasswordAge : {3710851} MinPasswordAge : {0} PasswordHistoryLength : {0} AutoUnlockInterval : {1800} LockoutObservationInterval : {1800} MaxBadPasswordsAllowed : {0} RasPermissions : {1} objectSid : {1 5 0 0 0 0 0 5 21 0 0 0 121 227 252 83 122 130 50 34 67 23 10 50 244 1 0 0}
O, como muestra Ejemplo P-7, incluso puedes utilizar PowerShell para programar objetos COM tradicionales.
Ejemplo P-7. Trabajar con objetos COM en PowerShell
PS > $firewall = New-Object -com HNetCfg.FwMgr PS > $firewall.LocalPolicy.CurrentProfile Type : 1 FirewallEnabled : True ExceptionsNotAllowed : False NotificationsDisabled : False UnicastResponsesToMulticastBroadcastDisabled : False RemoteAdminSettings : System.__ComObject IcmpSettings : System.__ComObject GloballyOpenPorts : {Media Center Extender Service, Remote Media Center Experience, Adam Test Instance, QWAVE...} Services : {File and Printer Sharing, UPnP Framework, Remote Desktop} AuthorizedApplications : {Remote Assistance, Windows Messenger, Media Center, Trillian...}
Mucho, mucho más
Por muy emocionante que haya sido esta visita guiada, apenas araña la superficie de cómo puedes utilizar PowerShell para mejorar tu productividad y tus habilidades de gestión de sistemas. Para más información sobre cómo iniciarte en PowerShell, consulta el Capítulo 1.
Get Libro de cocina PowerShell, 4ª edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.