Capítulo 1. El intérprete de comandos interactivo PowerShell
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
1.0 Introducción
Por encima de todo lo demás, el diseño de PowerShell da prioridad a su uso como shell interactivo eficaz y potente. Incluso su lenguaje de scripting desempeña un papel crítico en este esfuerzo, ya que favorece en gran medida el uso interactivo.
Lo que sorprende a la mayoría de la gente cuando inicia PowerShell por primera vez es su similitud con el símbolo del sistema que existe desde hace mucho tiempo como parte de Windows. Las herramientas conocidas siguen ejecutándose. Los comandos conocidos siguen ejecutándose. Incluso las teclas de acceso rápido son las mismas. Pero detrás de esta interfaz familiar hay un potente motor que te permite realizar con facilidad tareas administrativas y de scripting que antes eran engorrosas.
Este capítulo presenta PowerShell desde la perspectiva de su shell interactivo.
1.1 Instalar PowerShell Core
Solución
Visita el sitio web de Microsoft para encontrar las instrucciones de instalación para el sistema operativo y la plataforma en la que quieras instalar. Para los más habituales:
Windows
Instala PowerShell desde Microsoft a través de la aplicación Microsoft Store en el Menú Inicio. A continuación, instala Windows Terminal desde Microsoft a través de la aplicación Microsoft Store del Menú Inicio.
Mac
Instala PowerShell desde Homebrew:
brew install --cask powershell
Linux
Las instrucciones de instalación varían según la distribución de Linux, pero la distribución más común entre los usuarios de PowerShell Core es Ubuntu:
# Update the list of packages sudo apt-get update # Install pre-requisite packages. sudo apt-get install -y wget apt-transport-https software-properties-common # Download the Microsoft repository GPG keys wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb # Register the Microsoft repository GPG keys sudo dpkg -i packages-microsoft-prod.deb # Update the list of packages after we added packages.microsoft.com sudo apt-get update # Install PowerShell sudo apt-get install -y powershell
Debate
PowerShell ya ha tenido una vida larga y emocionante. Durante sus primeros 15 años de existencia, se le conoció como "Windows PowerShell": una fantástica plataforma y shell de gestión basada en objetos que facilitaba y divertía el trabajo de administradores, desarrolladores y usuarios avanzados.
En sus primeras etapas, este soporte formaba parte de "Windows Management Framework": una descarga independiente que proporcionaba esta funcionalidad tan necesaria en Windows. Con el tiempo, Windows PowerShell pasó a formar parte del propio Windows, y ha sido una parte esencial del sistema operativo desde Windows 7.
En 2016, PowerShell dio un giro tremendo al anunciar que distribuiríaPowerShell en múltiples plataformas de sistemas operativos y, de paso, ¡hizo que todo el proyecto fuera de código abierto al mismo tiempo! Windows PowerShell recibió un nuevo nombre con su nuevo futuro: simplemente PowerShell. Este gran cambio abrió las puertas a una innovación mucho más rápida, a la participación de la comunidad y a la disponibilidad a través de vías que antes nunca habrían sido posibles. Aunque el Windows PowerShell clásico sigue incluido por defecto en el sistema operativo, ya no recibe actualizaciones y debe evitarse.
Instalar y ejecutar PowerShell en Windows
Como se menciona en la Solución, la mejor forma de obtener PowerShell es instalarlo a través de Microsoft Store. Esto facilita su instalación y actualización. Una vez instalado, puedes encontrar PowerShell en el Menú Inicio como harías con cualquier otraaplicación.
Nota
Si quieres instalar una versión de PowerShell en todo el sistema para la automatización y otras tareas de administración, probablemente prefieras el mecanismo de instalación basado en MSI. Para más información, consulta el sitio web de Microsoft.
Aunque en estás instalando PowerShell desde la Tienda Microsoft, ahora es un buen momento para instalar la aplicación Terminal de Windows desde la Tienda Microsoft. La interfaz de consola tradicional (la ventana dentro de la que se ejecuta PowerShell) incluida en Windows tiene tantas herramientas y aplicaciones que dependen de sus peculiaridades exactas que es casi imposible cambiarla de forma significativa. Se ha quedado lamentablemente atrás en lo que cabría esperar de una interfaz de consola de terminal, por lo que la aplicación Terminal de Windows de la Tienda Microsoft, como se muestra en la Figura 1-1, es la solución. Al igual que PowerShell, es de código abierto, un foco de rápida innovación y una gran mejora de lo que Windows incluye por defecto.
Puedes ejecutar muchos shells dentro de pestañas en el Terminal de Windows: PowerShell, Windows PowerShell, cmd.exe, Bash (si has activado el Subsistema de Windows para Linux), e incluso una conexión a Azure Cloud Shell. Windows Terminal utiliza por defecto PowerShell si lo tienes instalado.
Personalizar PowerShell en el Terminal de Windows
Hay dos cambios en la instalación por defecto de Windows Terminal + PowerShell que realmente mejoran la experiencia: la fijación de la barra de tareas y los temas.
Fijación de la barra de tareas
Cuando inicies el Terminal de Windows, haz clic con el botón derecho del ratón en el icono de su barra de tareas. Selecciona "Anclar a la barra de tareas" y, a continuación, arrastra el icono al extremo izquierdo de la barra de tareas. A partir de ahora, cada vez que pulses al mismo tiempo la tecla Windows + 1
, iniciarás Windows Terminal y PowerShell (si no está ya abierto), o lo activarás. Es una forma increíble de tener a mano tu shell favorito.
Temas
Windows PowerShell tiene un precioso tema Azul Noble. Es agradable a la vista, rápido de identificar y lo distingue de las docenas de shells que existen. PowerShell Core no lleva este esquema de colores por defecto, pero aún es posible personalizar tu instalación. Desde el Terminal de Windows, pulsa Ctrl+Coma o haz clic en la flecha hacia abajo situada a la derecha de la barra de pestañas para abrir el cuadro de diálogo Configuración del Terminal de Windows. El archivo que contiene esta configuración se abrirá en tu editor de texto predeterminado. En Profiles
, busca el elemento con Windows.Terminal.PowershellCore
como fuente, y añade Campbell Powershell
como colorScheme
. El resultado debería tener este aspecto:
{ "guid": ... "hidden": false, "name": "PowerShell", "colorScheme": "Campbell Powershell", "source": "Windows.Terminal.PowershellCore" },
Presta atención a las mayúsculas, las comillas, los dos puntos y las comas, ¡y tus sesiones PowerShell volverán a parecer nobles en un santiamén!
Instalar y ejecutar PowerShell en Mac y Linux
En su mayor parte, la instalación de PowerShell en Mac y Linux sigue los patrones con los que probablemente ya estés familiarizado.
En Mac, el método de instalación recomendado es a través del popular gestor de paquetes Homebrew. Homebrew no está instalado por defecto en macOS, pero su instalación es bastante sencilla. Si aún no has instalado Homebrew, puedes encontrar instrucciones en el sitio oficial de Homebrew.
En Linux, los métodos de instalación varían en función de la distribución que te interese. En la mayoría de los casos, la instalación es tan sencilla como registrar el repositorio de Microsoft para el gestor de paquetes de tu distribución y, a continuación, instalar PowerShell. La Solución proporciona un ejemplo específico para Ubuntu 20.04
, pero puedes obtener instrucciones específicas para tu distribución y versión concreta en el sitio web de Microsoft.
1.2 Ejecutar programas, scripts y herramientas existentes
Problema
Tú dependes de mucho esfuerzo invertido en tus herramientas actuales. Tienes ejecutables tradicionales, scripts Perl, VBScript y, por supuesto, un sistema de compilación heredado que ha crecido orgánicamente hasta convertirse en un enredo de archivos por lotes. Quieres utilizar PowerShell, pero no quieres renunciar a todo lo que ya tienes.
Solución
Para ejecutar un programa, script, archivo por lotes u otro comando ejecutable en la ruta del sistema, introduce su nombre de archivo. Para estos tipos de ejecutables, la extensión es opcional:
Program
.
exe
arguments
ScriptName
.
ps1
arguments
BatchFile
.
cmd
arguments
Para ejecutar un comando que contenga un espacio en su nombre, encierra su nombre de archivo entre comillas simples ('
) y precede al comando con un ampersand (&
), conocido en PowerShell como operador de invocación:
&
'C:\Program Files\Program\Program.exe'
arguments
Para que ejecute un comando en el directorio actual, coloca .\
delante de su nombre de archivo:
.\
Program
.
exe
arguments
Para ejecutar un comando con espacios en su nombre desde el directorio actual, prepáralo con un ampersand y .\
:
&
'.\Program With Spaces.exe'
arguments
Debate
En este caso, la solución consiste principalmente en utilizar tus herramientas actuales como siempre lo has hecho. La única diferencia es que las ejecutas en el intérprete de comandos interactivo PowerShell en lugar de cmd.exe.
Especificar el nombre del comando
Los tres últimos consejos de la Solución merecen una atención especial. Son las características de PowerShell con las que tropiezan muchos nuevos usuarios a la hora de ejecutar programas. El primero es la ejecución de comandos que contienen espacios. En cmd.exe, la forma de ejecutar un comando que contiene espacios es rodearlo de comillas:
"C:\Program Files\Program\Program.exe"
En PowerShell, sin embargo, colocar texto entre comillas forma parte de una función que te permite evaluar expresiones complejas en el prompt, como se muestra en el Ejemplo 1-1.
Ejemplo 1-1. Evaluar expresiones en el indicador PowerShell
PS > 1 + 1 2 PS > 26 * 1.15 29.9 PS > "Hello" + " World" Hello World PS > "Hello World" Hello World PS > "C:\Program Files\Program\Program.exe" C:\Program Files\Program\Program.exe PS >
Por tanto, un nombre de programa entre comillas no es diferente de cualquier otra cadena entre comillas. Es sólo una expresión. Como se ha mostrado anteriormente, la forma de ejecutar un comando en una cadena es preceder esa cadena con el operador de invocación (&
). Si el comando que quieres ejecutar es un archivo por lotes que modifica su entorno, consulta la Receta 3.5.
Nota
Por defecto, las políticas de seguridad de PowerShell impiden la ejecución de scripts. Sin embargo, cuando empieces a escribir o utilizar scripts, deberás configurar esta política a algo menos restrictivo. Para obtener información sobre cómo configurar tu política de ejecución, consulta la Receta 18.1.
El segundo comando con el que a veces tropiezan los nuevos usuarios (¡y los veteranos antes del café!) es ejecutar comandos desde el directorio actual. En cmd.exe, el directorio actual se considera parte de la ruta: la lista de directorios que Windows busca para encontrar el nombre del programa que has escrito. Si estás en el directorio C:\Programas, cmd.exe busca en C:\Programas (entre otros lugares) aplicaciones para ejecutar.
PowerShell, como la mayoría de los shells Unix, requiere que indiques explícitamente tu deseo de ejecutar un programa desde el directorio actual. Para ello, utiliza la sintaxis .\Program.exe
, como se ha mostrado anteriormente. Esto evita que los usuarios malintencionados de tu sistema ensucien tu disco duro con programas malignos que tienen nombres similares (o iguales) a los comandos que podrías ejecutar al visitar ese directorio.
Para ahorrarse tener que escribir la ubicación de los scripts y programas más utilizados, muchos usuarios colocan las utilidades de uso común junto con sus scripts de PowerShell en un directorio de "herramientas", que añaden a la ruta de su sistema. Si PowerShell puede encontrar un script o utilidad en la ruta de tu sistema, no necesitas especificar explícitamente su ubicación.
Si quieres que PowerShell busque automáticamente scripts en tu directorio de trabajo actual, puedes añadir un punto (.
) a tu variable de entorno PATH.
Para más información sobre cómo actualizar la ruta de tu sistema, consulta la Receta 16.2.
Si quieres capturar la salida de un comando, puedes guardar los resultados en una variable o guardarlos en un archivo. Para guardar los resultados en una variable, consulta la Receta 3.3. Para guardar los resultados en un archivo, consulta la Receta 9.2.
Especificar los argumentos del comando
Para especificar en los argumentos de un comando, puedes escribirlos como lo harías en otros shells. Por ejemplo, para hacer que un archivo especificado sea de sólo lectura (dos argumentos para attrib.exe), simplemente escribe:
attrib
+
R
c
:
\
path
\
to
\
file
.
txt
Donde muchos scripters se despistan cuando se trata de argumentos de comandos es en cómo cambiarlos dentro de tus scripts. Por ejemplo, ¿cómo se obtiene el nombre de archivo de una variable PowerShell? La respuesta es definir una variable que contenga el valor del argumento y utilizarla en el lugar donde escribiste el argumento del comando:
$filename
=
"c:\path\to\other\file.txt"
attrib
+
R
$filename
Puedes utilizar la misma técnica cuando llames a un cmdlet, script ofunción de PowerShell:
$filename
=
"c:\path\to\other\file.txt"
Get-Acl
-Path
$filename
Si en ves una solución que utiliza el cmdlet Invoke-Expression
para componer argumentos de comando, es casi seguro que es incorrecta. El cmdlet Invoke-Expression
toma la cadena que le das y la trata como un script completo de PowerShell. Como sólo un ejemplo de los problemas que esto puede causar, considera lo siguiente: se permite que los nombres de archivo contengan el carácter punto y coma (;
), pero cuando Invoke-Expression
ve un punto y coma, asume que se trata de una nueva línea de script PowerShell. Por ejemplo, prueba a ejecutar esto
$filename
=
"c:\file.txt; Write-Warning 'This could be bad'"
Invoke-Expression
"Get-Acl -Path $filename"
Dado que estos argumentos dinámicos suelen proceder de la entrada del usuario, utilizar Invoke-Expression
para componer comandos puede (en el mejor de los casos) provocar resultados impredecibles en los scripts. Peor aún, podría provocar daños en tu sistema o una vulnerabilidad de seguridad.
Además de permitirte suministrar argumentos a través de variables de uno en uno, PowerShell también te permite suministrar varios de ellos a la vez mediante una técnica conocida como splatting. Para más información sobre splatting, consulta la Receta 11.14.
1.3 Ejecutar un comando PowerShell
Solución
Para ejecutar un comando PowerShell, escribe su nombre en el símbolo del sistema. Por ejemplo
PS > Get-Process NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName ------ ----- ----- ------ -- -- ----------- 14 3.47 10.55 0.00 6476 0 AGMService 14 3.16 10.57 0.00 3704 0 AGSService 37 40.12 40.51 2.06 17676 1 ApplicationFrameHost
Debate
El comando Get-Process
es un ejemplo de comando nativo de PowerShell, llamado cmdlet. En comparación con los comandos tradicionales, los cmdlets ofrecen importantes ventajas tanto a los administradores como a los desarrolladores:
-
Comparten una sintaxis de línea de comandos común y habitual.
-
Admiten escenarios de canalización enriquecidos (utilizando la salida de un comando como entrada de otro).
-
Producen una salida basada en objetos fácilmente manejable, en lugar de una salida de texto plano propensa a errores.
Como el cmdlet Get-Process
genera una salida rica basada en objetos, puedes utilizar su salida para muchas tareas relacionadas con los procesos.
Todos los comandos de PowerShell te permiten proporcionar información al comando a través de sus parámetros. Para más información sobre cómo introducir datos en los comandos, consulta "Ejecutar comandos".
El cmdlet Get-Process
es sólo uno de los muchos que admite PowerShell. Consulta la Receta 1.12 para aprender técnicas para encontrar comandos adicionales compatibles con PowerShell.
Para más información sobre cómo trabajar con clases del .NET Framework, consulta la Receta 3.8.
1.4 Resolver errores al llamar a ejecutables nativos
Solución
Encierra cualquier argumento de comando afectado entre comillas simples para evitar que PowerShell lo interprete, y sustituye las comillas simples del comando por dos comillas simples:
PS > cmd /c echo '!"#$%&''()*+,-./09:;<=>?@AZ[\]^_`az{|}~' !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
Para los comandos complicados de en los que esto no funcione, utiliza la sintaxis de argumento literal(--%
):
PS > cmd /c echo 'quotes' "and" $variables @{ etc = $true } quotes and System.Collections.Hashtable PS > cmd --% /c echo 'quotes' "and" $variables @{ etc = $true } 'quotes' "and" $variables @{ etc = $true }
Debate
Uno de los principales objetivos de PowerShell ha sido siempre la coherencia de los comandos. Por eso, los cmdlets son muy regulares en la forma en que aceptan parámetros. Los ejecutables nativos escriben su propio análisis sintáctico de parámetros, por lo que nunca sabes qué esperar cuando trabajas con ellos. Además, PowerShell ofrece muchas funciones que te hacen más eficiente en la línea de comandos: sustitución de comandos, expansión de variables y mucho más. Como muchos ejecutables nativos se escribieron antes de que se desarrollara PowerShell, pueden utilizar caracteres especiales que entren en conflicto con estas funciones.
Como ejemplo, el comando que aparece en la Solución utiliza todos los caracteres especiales disponibles en un teclado típico. Sin las comillas, PowerShell trata algunos de ellos como características del lenguaje, como se muestra en la Tabla 1-1.
Carácter especial | Significado |
---|---|
|
El principio (o final) del texto citado |
|
El comienzo de un comentario |
|
El inicio de una variable |
|
El operador de canalización de fondo |
|
Paréntesis utilizados para subexpresiones |
|
Separador de declaraciones |
|
Bloque de guión |
|
Separador de tuberías |
|
Carácter de escape |
Cuando están rodeados de comillas simples, PowerShell acepta estos caracteres tal y como están escritos, sin el significado especial.
A pesar de estas precauciones, puede que a veces te encuentres con un comando que no parece funcionar cuando se invoca desde PowerShell. En la mayoría de los casos, puedes resolverlos con el marcador de argumento literal (--%
) que impide que PowerShell interprete cualquiera de los caracteres restantes de la línea. Puedes colocar este marcador en cualquier lugar de los argumentos del comando, lo que te permitirá beneficiarte de las construcciones de PowerShell cuando proceda. El siguiente ejemplo utiliza una variable PowerShell para algunos de los argumentos del comando, pero utiliza argumentos literales para el resto:
PS > $username = "Lee" PS > cmd /c echo Hello $username with 'quotes' "and" $variables @{ etc = $true } Hello Lee with quotes and System.Collections.Hashtable PS > cmd /c echo Hello $username ` --% with 'quotes' "and" $variables @{ etc = $true } Hello Lee with 'quotes' "and" $variables @{ etc = $true }
Mientras está en este modo, PowerShell también acepta variables de entorno al estilo cmd.exe, ya que éstas se utilizan con frecuencia en comandos que "simplemente solían funcionar":
PS > $env:host = "localhost" PS > ping %host% Ping request could not find host %host%. Please check the name and try again. PS > ping --% %host% Pinging localhost [127.0.1.1] with 32 bytes of data: (...)
Ver también
1.5 Suministrar valores por defecto para los parámetros
Solución
Añade una entrada a la tabla hash PSDefaultParameterValues
:
PS > Get-Process Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 150 13 9692 9612 39 21.43 996 audiodg 1013 84 45572 42716 315 1.67 4596 WWAHost (...) PS > $PSDefaultParameterValues["Get-Process:ID"] = $pid PS > Get-Process Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 584 62 132776 157940 985 13.15 9104 powershell PS > Get-Process -Id 0 Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 0 0 0 20 0 0 Idle
Debate
En PowerShell, muchos comandos (cmdlets y funciones avanzadas) tienen parámetros que te permiten configurar su comportamiento. Para una descripción completa de cómo proporcionar entradas a los comandos, consulta "Ejecutar comandos". A veces, sin embargo, proporcionar valores para esos parámetros en cada invocación resulta incómodo o repetitivo.
En las primeras versiones de de PowerShell, era responsabilidad del autor de cada cmdlet reconocer las propiedades de configuración incómodas o repetitivas y crear soporte para "variables de preferencia" en el propio cmdlet. Por ejemplo, el cmdlet Send-MailMessage
busca la variable $PSEmailServer
si no proporcionas un valor para su parámetro-SmtpServer
parámetro.
Para que este soporte sea más coherente y configurable, PowerShell admite la variable de preferencia PSDefaultParameterValues
. Esta variable de preferencia es una hashtable. Como todas las demás hashtables de PowerShell, las entradas constan de dos partes: la clave y el valor.
Las claves de la tabla hash PSDefaultParameterValues
deben coincidir con el patrón cmdlet
:parameter
-es decir, un nombre de cmdlet y un nombre de parámetro, separados por dos puntos. Cualquiera de ellos (o ambos) puede utilizar comodines, y se ignoran los espacios entre el nombre del comando, los dos puntos y el parámetro.
Los valores de los pares cmdlet/parámetro pueden ser un valor de parámetro simple (una cadena, un valor booleano, un número entero, etc.) o un bloque de secuencia de comandos. Los valores de parámetros simples son los que utilizarás más a menudo.
Si necesitas que el valor por defecto cambie dinámicamente en función de los valores de los parámetros que se proporcionen hasta el momento, puedes utilizar un bloque de secuencia de comandos como valor por defecto. Al hacerlo, PowerShell evalúa el bloque de secuencia de comandos y utiliza su resultado como valor por defecto. Si tu bloque de secuencia de comandos no devuelve un resultado, PowerShell no aplica un valor por defecto.
Cuando PowerShell invoca tu bloque de script, $args[0]
contiene información sobre los parámetros vinculados hasta el momento: BoundDefaultParameters
, BoundParameters
, y BoundPositionalParameters
. Como ejemplo de esto, considera la posibilidad de proporcionar valores por defecto al parámetro -Credential
en función del ordenador al que se esté conectando. Aquí tienes una función que simplemente muestra la credencial que se está utilizando:
function
RemoteConnector
{
param
(
[
Parameter
()]
$ComputerName
,
[
Parameter
(
Mandatory
=
$true
)]
$Credential
)
"Connecting as "
+
$Credential
.
UserName
}
Ahora puedes definir un mapa de credenciales:
PS > $credmap = @{} PS > $credmap["RemoteComputer1"] = Get-Credential PS > $credmap["RemoteComputer2"] = Get-Credential
A continuación, crea un parámetro por defecto para todos los parámetros de Credential
que busque el parámetroComputerName
parámetro vinculado:
$PSDefaultParameterValues
[
"*:Credential"
]
=
{
if
(
$args
[
0
].
BoundParameters
-contains
"ComputerName"
)
{
$cred
=
$credmap
[
$PSBoundParameters
[
"ComputerName"
]]
if
(
$cred
)
{
$cred
}
}
}
Aquí tienes un ejemplo de su uso:
PS > RemoteConnector -ComputerName RemoteComputer1 Connecting as UserForRemoteComputer1 PS > RemoteConnector -ComputerName RemoteComputer2 Connecting as UserForRemoteComputer2 PS > RemoteConnector -ComputerName RemoteComputer3 cmdlet RemoteConnector at command pipeline position 1 Supply values for the following parameters: Credential: (...)
Para más información sobre cómo trabajar con hashtables en PowerShell, consulta "Hashtables (matrices asociativas)".
1.6 Invocar un comando de larga ejecución o en segundo plano
Solución
Invocar el comando como Job
para que PowerShell lo ejecute en segundo plano:
PS > Start-Job { while($true) { Get-Random; Start-Sleep 5 } } -Name Sleeper Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 Sleeper Running True localhost PS > Receive-Job Sleeper 671032665 1862308704 PS > Stop-Job Sleeper
O, si tu comando es una única canalización, coloca un carácter &
al final de la línea para ejecutar esa canalización en segundo plano:
PS > dir c:\windows\system32 -recurse & Id Name PSJobTypeName State HasMore Data -- ---- ------------- ----- ------- 1 Job1 BackgroundJob Running True PS > 1+1 2 PS > Receive-Job -id 1 | Select -First 5 Directory: C:\Windows\System32 Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 12/7/2019 1:50 AM 0409 d---- 11/5/2020 7:09 AM 1028 d---- 11/5/2020 7:09 AM 1029 d---- 11/5/2020 7:09 AM 1031 d---- 11/5/2020 7:09 AM 1033
Debate
Los cmdlets de trabajo de PowerShell proporcionan una forma coherente de crear tareas en segundo plano e interactuar con ellas. En la Solución, utilizamos el cmdlet Start-Job
para lanzar un trabajo en segundo plano en el ordenador local. Le damos el nombre de Sleeper
, pero por lo demás no personalizamos mucho su entorno de ejecución.
En además de permitirte personalizar el nombre del trabajo, el cmdlet Start-Job
también te permite lanzar el trabajo bajo credenciales de usuario alternativas o como un proceso de 32 bits (si se ejecuta originalmente desde un proceso de 64 bits).
Como alternativa al cmdlet Start-Job
, también puedes utilizar el cmdlet Start-ThreadJob
. El cmdlet Start-ThreadJob
es un poco más rápido a la hora de iniciar trabajos en segundo plano y también te permite suministrar e interactuar con objetos vivos en los trabajos que crees. Sin embargo, consume recursos de tu proceso PowerShell actual y no te permite ejecutar tu trabajo con credenciales de usuario alternativas.
Una vez que hayas lanzado un trabajo, puedes utilizar los demás cmdlets de Job
para interactuar con él:
Get-Job
-
Obtiene todos los trabajos asociados a la sesión actual. Además, los parámetros
-Before
,-After
,-Newest
, y-State
te permiten filtrar los trabajos en función de su estado o tiempo de finalización. Wait-Job
-
Espera por un trabajo hasta que tenga salida lista para ser recuperada.
Receive-Job
-
Recupera cualquier salida que el trabajo haya generado desde la última llamada a
Receive-Job
. Stop-Job
Remove-Job
Nota
En además del cmdlet Start-Job
, también puedes utilizar el parámetro -AsJob
en muchos cmdlets para que realicen sus tareas en segundo plano. Dos de los ejemplos más útiles son el cmdlet Invoke-Command
(cuando se opera contra ordenadores remotos) y el cmdlet ForEach-Object
.
Si tu trabajo genera un error, el cmdlet Receive-Job
te lo mostrará cuando recibas los resultados, como se muestra en el Ejemplo 1-2. Si quieres investigar más a fondo estos errores, el objeto devuelto por Get-Job
los expone a través de la propiedad Error
.
Ejemplo 1-2. Recuperar errores de un Trabajo
PS > Start-Job -Name ErrorJob { Write-Error Error! } Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 ErrorJob Running True localhost PS > Receive-Job ErrorJob Write-Error: Error! PS > $job = Get-Job ErrorJob PS > $job | Format-List * State : Completed HasMoreData : False StatusMessage : Location : localhost Command : Write-Error Error! JobStateInfo : Completed Finished : System.Threading.ManualResetEvent InstanceId : 801e932c-5580-4c8b-af06-ddd1024840b7 Id : 1 Name : ErrorJob ChildJobs : {Job2} Output : {} Error : {} Progress : {} Verbose : {} Debug : {} Warning : {} PS > $job.ChildJobs[0] | Format-List * State : Completed StatusMessage : HasMoreData : False Location : localhost Runspace : System.Management.Automation.RemoteRunspace Command : Write-Error Error! JobStateInfo : Completed Finished : System.Threading.ManualResetEvent InstanceId : 60fa85da-448b-49ff-8116-6eae6c3f5006 Id : 2 Name : Job2 ChildJobs : {} Output : {} Error : {Microsoft.PowerShell.Commands.WriteErrorException,Microso ft.PowerShell.Commands.WriteErrorCommand} Progress : {} Verbose : {} Debug : {} Warning : {} PS > $job.ChildJobs[0].Error Write-Error: Error! PS >
Como muestra este ejemplo , a veces los trabajos son contenedores de otros trabajos, llamados trabajos hijo. Los trabajos creados mediante el cmdlet Start-Job
siempre serán trabajos hijo adjuntos a un contenedor genérico. Para acceder a los errores devueltos por estos trabajos, accede en su lugar a los errores de su primer trabajo hijo (llamado trabajo hijo número cero).
Además de los trabajos de larga duración que se ejecutan bajo el control de la sesión actual de PowerShell, es posible que quieras registrar y controlar trabajos que se ejecutan según una programación, o independientemente de la sesión actual de PowerShell. PowerShell tiene un puñado de comandos que te permiten trabajar con trabajos programados de este tipo; para más información, consulta la Receta 27.14.
1.7 Programa: Monitorear los cambios de un comando
Por muy emocionantes que sean nuestras vidas, algunos días se reducen a ejecutar un comando una y otra y otra vez. ¿Han terminado ya de copiarse los archivos? ¿Ha terminado la construcción? ¿Sigue funcionando el sitio?
Normalmente, la respuesta a estas preguntas viene de ejecutar un comando, mirar su salida y decidir si cumple tus criterios. Y normalmente esto significa esperar a que cambie la salida, esperar a que aparezca algún texto o esperar a que desaparezca algún texto.
Afortunadamente, el Ejemplo 1-3 automatiza este tedioso proceso por ti.
Ejemplo 1-3. Observar-Comando.ps1
##############################################################################
##
## Watch-Command
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Watches the result of a command invocation, alerting you when the output
either matches a specified string, lacks a specified string, or has simply
changed.
.EXAMPLE
PS > Watch-Command { Get-Process -Name Notepad | Measure } -UntilChanged
Monitors Notepad processes until you start or stop one.
.EXAMPLE
PS > Watch-Command { Get-Process -Name Notepad | Measure } -Until "Count : 1"
Monitors Notepad processes until there is exactly one open.
.EXAMPLE
PS > Watch-Command {
Get-Process -Name Notepad | Measure } -While 'Count : \d\s*\n'
Monitors Notepad processes while there are between 0 and 9 open
(once number after the colon).
#>
[
CmdletBinding
(
DefaultParameterSetName
=
"Forever"
)]
param
(
## The script block to invoke while monitoring
[
Parameter
(
Mandatory
=
$true
,
Position
=
0
)]
[ScriptBlock]
$ScriptBlock
,
## The delay, in seconds, between monitoring attempts
[
Parameter
()]
[Double]
$DelaySeconds
=
1
,
## Specifies that the alert sound should not be played
[
Parameter
()]
[Switch]
$Quiet
,
## Monitoring continues only while the output of the
## command remains the same.
[
Parameter
(
ParameterSetName
=
"UntilChanged"
,
Mandatory
=
$false
)]
[Switch]
$UntilChanged
,
## The regular expression to search for. Monitoring continues
## until this expression is found.
[
Parameter
(
ParameterSetName
=
"Until"
,
Mandatory
=
$false
)]
[String]
$Until
,
## The regular expression to search for. Monitoring continues
## until this expression is not found.
[
Parameter
(
ParameterSetName
=
"While"
,
Mandatory
=
$false
)]
[String]
$While
)
Set-StrictMode
-Version
3
$initialOutput
=
""
$lastCursorTop
=
0
Clear-Host
## Start a continuous loop
while
(
$true
)
{
## Run the provided script block
$r
=
&
$ScriptBlock
## Clear the screen and display the results
$buffer
=
$ScriptBlock
.
ToString
().
Trim
()
+
"
`r`n
"
$buffer
+=
"
`r`n
"
$textOutput
=
$r
|
Out-String
$buffer
+=
$textOutput
[Console]
::
SetCursorPosition
(
0
,
0
)
[Console]
::
Write
(
$buffer
)
$currentCursorTop
=
[Console]
::
CursorTop
$linesToClear
=
$lastCursorTop
-
$currentCursorTop
if
(
$linesToClear
-gt
0
)
{
[Console]
::
Write
((
" "
*
[Console]
::
WindowWidth
*
$linesToClear
))
}
$lastCursorTop
=
[Console]
::
CursorTop
[Console]
::
SetCursorPosition
(
0
,
0
)
## Remember the initial output, if we haven't
## stored it yet
if
(
-not
$initialOutput
)
{
$initialOutput
=
$textOutput
}
## If we are just looking for any change,
## see if the text has changed.
if
(
$UntilChanged
)
{
if
(
$initialOutput
-ne
$textOutput
)
{
break
}
}
## If we need to ensure some text is found,
## break if we didn't find it.
if
(
$While
)
{
if
(
$textOutput
-notmatch
$While
)
{
break
}
}
## If we need to wait for some text to be found,
## break if we find it.
if
(
$Until
)
{
if
(
$textOutput
-match
$Until
)
{
break
}
}
## Delay
Start-Sleep
-Seconds
$DelaySeconds
}
## Notify the user
if
(
-not
$Quiet
)
{
[Console]
::
Beep
(
1000
,
1000
)
}
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.8 Notifícate de la finalización del trabajo
Solución
Utiliza el comando Register-TemporaryEvent
que aparece en la Receta 31.3 para registrarte en el evento StateChanged
:
PS > $job = Start-Job -Name TenSecondSleep { Start-Sleep 10 } PS > Register-TemporaryEvent $job StateChanged -Action { [Console]::Beep(100,100) Write-Host "Job #$($sender.Id) ($($sender.Name)) complete." } PS > Job #6 (TenSecondSleep) complete. PS >
Debate
Cuando un trabajo finaliza, lanza un evento StateChanged
para notificar a los suscriptores que su estado ha cambiado. Podemos utilizar los cmdlets de gestión de eventos de PowerShell para registrarnos para recibir notificaciones sobre este evento, pero no están orientados a este tipo de gestión de eventos puntuales. Para solucionarlo, utilizamos el comando Register-TemporaryEvent
que aparece en la Receta 31.3.
En nuestro bloque de acción de ejemplo de la Solución, simplemente emitimos un pitido y escribimos un mensaje diciendo que el trabajo se ha completado.
Como otra opción, también puedes actualizar tu función prompt
para resaltar los trabajos que están completos pero que todavía tienen salida que no has procesado:
$psJobs
=
@(
Get-Job
-State
Completed
|
?
{
$_
.
HasMoreData
})
if
(
$psJobs
.
Count
-gt
0
)
{
(
$psJobs
|
Out-String
).
Trim
()
|
Write-Host
-Fore
Yellow
}
Para más información sobre los eventos y este tipo de gestión automática de eventos, consulta el Capítulo 31.
1.9 Personalizar tu Shell, Perfil y Prompt
Solución
Cuando quieras personalizar aspectos de PowerShell, coloca esas personalizaciones en tu script de perfil personal. PowerShell facilita el acceso a este script de perfil almacenando su ubicación en la variable $profile
.
Nota
Por defecto, las políticas de seguridad de PowerShell impiden la ejecución de scripts (incluido tu perfil). Sin embargo, una vez que empieces a escribir scripts, deberás configurar esta política a algo menos restrictivo. Para obtener información sobre cómo configurar tu política de ejecución, consulta la Receta 18.1.
Para crear un nuevo perfil (y sobrescribir uno si ya existe):
New-Item
-type
file
-force
$profile
Para editar tu perfil (en Visual Studio Code, si lo tienes instalado):
code
$profile
Para ver tu archivo de perfil:
Get-ChildItem
$profile
Una vez crees un script de perfil, puedes añadir una función llamada prompt
que devuelva una cadena. PowerShell muestra la salida de esta función como indicador de tu línea de comandos:
function
prompt
{
"PS [$env:COMPUTERNAME] >"
}
Este ejemplo muestra el nombre de tu ordenador y se parece a PS [LEE-DESK] >
.
También puede resultarte útil añadir alias a tu perfil. Los alias te permiten referirte a comandos comunes con un nombre que tú elijas. Los scripts de perfil personal te permiten definir automáticamente alias, funciones, variables o cualquier otra personalización que puedas establecer interactivamente desde el indicador de PowerShell. Los alias se encuentran entre las personalizaciones más comunes, ya que te permiten referirte a los comandos PowerShell (y a tus propios scripts) con un nombre más fácil de escribir.
Nota
Si quieres definir un alias para un comando pero también necesitas modificar los parámetros de ese comando, entonces define una función en su lugar. Para más información, consulta la Receta 11.14.
Por ejemplo:
Set-Alias
new
New-Object
Set-Alias
browse
'C:\Users\lee\AppData\Local\Microsoft\*\MicrosoftEdge.exe'
Tus cambios se harán efectivos cuando guardes tu perfil y reinicies PowerShell. Alternativamente, puedes recargar tu perfil inmediatamente ejecutando este comando:
.
$profile
Las funciones también son personalizaciones muy comunes, siendo la más popular la función prompt
.
Debate
La Solución analiza tres técnicas para realizar personalizaciones útiles en tu entorno PowerShell: alias, funciones y un prompt a medida. Puedes (y a menudo lo harás) aplicar estas técnicas en cualquier momento de tu sesión PowerShell, pero tu script de perfil es el lugar estándar para poner las personalizaciones que quieras aplicar a cada sesión.
Nota
Para eliminar un alias o una función, utiliza el cmdlet Remove-Item
:
Remove-Item
function
:
\
MyCustomFunction
Remove-Item
alias
:
\
new
Aunque la función prompt
devuelve una simple cadena, también puedes utilizarla para tareas más complejas. Por ejemplo, muchos usuarios actualizan el título de la ventana de la consola (cambiando la variable $host.UI.RawUI.WindowTitle
) o utilizan el cmdlet Write-Host
para dar salida al prompt en color. Si tu función prompt se encarga ella misma de la salida en pantalla, aún debe devolver una cadena (por ejemplo, un solo espacio) para evitar que PowerShell utilice su valor por defecto. Si no quieres que este espacio extra aparezca en tu prompt, añade un espacio extra al final de tu comando Write-Host
y devuelve el carácter de retroceso ("`b"
), como se muestra en el Ejemplo 1-4.
Ejemplo 1-4. Ejemplo de instrucción PowerShell
##############################################################################
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
Set-StrictMode
-Version
3
function
prompt
{
$id
=
1
$historyItem
=
Get-History
-Count
1
if
(
$historyItem
)
{
$id
=
$historyItem
.
Id
+
1
}
Write-Host
-ForegroundColor
DarkGray
"
`n
[
$(
Get-Location
)
]"
Write-Host
-NoNewLine
"PS:$id > "
$host
.
UI
.
RawUI
.
WindowTitle
=
"
$(
Get-Location
)
"
"
`b
"
}
En además de mostrar la ubicación actual, este indicador también muestra el ID de ese comando en tu historial. Esto te permite localizar e invocar comandos anteriores con relativa facilidad:
[C:\] PS:73 >5 * 5 25 [C:\] PS:74 >1 + 1 2 [C:\] PS:75 >Invoke-History 73 5 * 5 25 [C:\] PS:76 >
Aunque el perfil al que hace referencia $profile
es el que casi siempre querrás utilizar, PowerShell admite en realidad cuatro scripts de perfil distintos. Para más detalles sobre estos scripts (junto con otras opciones de personalización del shell), consulta "Puntos comunes de personalización".
1.10 Personalizar el comportamiento de la entrada de usuario de PowerShell
Solución
Utiliza el cmdlet Set-PSReadLineOption
para configurar propiedades como EditMode
(Windows, VI, Emacs) y la gestión del historial. Por ejemplo, para que la línea de continuación de una entrada incompleta sea un poco más roja de lo habitual:
Set-PSReadLineOption
-Colors
@{
ContinuationPrompt
=
"#663333"
}
Utiliza el comando Set-PSReadLineKeyHandler
para configurar cómo responde PSReadLine a tus pulsaciones reales. Por ejemplo, para añadir navegación hacia delante y hacia atrás en el historial de directorios para Alt+Comma
y Alt+Period
:
Set-PSReadLineKeyHandler
-Chord
'Alt+,'
-ScriptBlock
{
Set-Location
-
[Microsoft.PowerShell.PSConsoleReadLine]
::
RevertLine
()
[Microsoft.PowerShell.PSConsoleReadLine]
::
AcceptLine
()
}
Set-PSReadLineKeyHandler
-Chord
'Alt+.'
-ScriptBlock
{
Set-Location
+
[Microsoft.PowerShell.PSConsoleReadLine]
::
RevertLine
()
[Microsoft.PowerShell.PSConsoleReadLine]
::
AcceptLine
()
}
Debate
Cuando PowerShell apareció en escena, los usuarios de Unix fueron de los primeros en darse cuenta. Llevaban años disfrutando de un potente shell y de una vigorosa herencia de automatización, y "cuando me veo obligado a usar Windows, PowerShell mola" es una frase que hemos oído muchas veces.
Esta asimilación natural no fue un error. Hay muchos miembros del equipo que provienen de una profunda formación Unix, y las similitudes con los shells Unix tradicionales eran intencionadas. Sin embargo, para la gente que viene de otros shells, todavía oímos alguna queja ocasional de que alguna función resulta extraña. ¿Alt+P no inicia la utilidad de paginación integrada? ¿Ctrl+XX no se mueve entre el principio de la línea y la posición actual del cursor? ¡Abominable!
En las primeras versiones de PowerShell, no había nada que pudieras hacer razonablemente para solucionar esto. En esas versiones, PowerShell leía su entrada desde la consola en lo que se conoce como Modo Cocinado -dondeel subsistema de consola de Windows se encarga de todas las pulsaciones de teclas, los menús F7 extravagantes y demás-. Cuando pulsas Intro o Tabulador, PowerShell obtiene el texto de lo que has escrito hasta ese momento, pero eso es todo. No hay forma de que sepa que has pulsado las teclas (tipo Unix) Ctrl+R, Ctrl+A, Ctrl+E o cualquier otra.
En versiones posteriores de PowerShell, la mayoría de estas cuestiones han desaparecido con la introducción del fantástico módulo PSReadLine que PowerShell utiliza para la entrada de línea de comandos. PSReadLine
añade un rico resaltado de sintaxis, completado de tabulaciones, navegación por el historial y mucho más.
El módulo PSReadLine
te permite configurarlo hasta un grado increíble. El cmdletSet-PSReadLineOption
cmdlet admite opciones para su interfaz de usuario, modo de manejo de la entrada, procesamiento del historial y mucho más:
EditMode BellStyle ContinuationPrompt CompletionQueryItems HistoryNoDuplicates WordDelimiters AddToHistoryHandler HistorySearchCaseSensitive CommandValidationHandler HistorySaveStyle HistorySearchCursorMovesToEnd HistorySavePath MaximumHistoryCount AnsiEscapeTimeout MaximumKillRingCount PromptText ShowToolTips ViModeIndicator ExtraPromptLineCount ViModeChangeHandler DingTone PredictionSource DingDuration Colors
En, además de permitirte configurar su comportamiento en tiempo de ejecución, también puedes configurar cómo tus pulsaciones de teclas hacen que reaccione. Para ver todos los comportamientos que puedes asignar a las pulsaciones de teclas, ejecuta Get-PSReadLineKeyHandler
. PSReadLine
ofrece páginas de opciones -muchas de ellas no asignadas actualmente a ninguna pulsación de tecla:
PS > Get-PSReadLineKeyHandler Basic editing functions ======================= Key Function Description --- -------- ----------- Enter AcceptLine Accept the input or move to the next line if input is missing a closing token. Shift+Enter AddLine Move the cursor to the next line without attempting to execute the input Backspace BackwardDeleteChar Delete the character before the cursor Ctrl+h BackwardDeleteChar Delete the character before the cursor Ctrl+Home BackwardDeleteLine Delete text from the cursor to the start of the line Ctrl+Backspace BackwardKillWord Move the text from the start of the current or previous word to the cursor to the kill ring Ctrl+w BackwardKillWord Move the text from the start of the current or previous word to the cursor to... (...)
Para configurar cualquiera de estas funciones, utiliza el comando Set-PSReadLineKeyHandler
. Por ejemplo, para configurar Ctrl+Shift+C
para que capture regiones coloreadas del búfer en tu portapapeles, ejecuta:
Set-PSReadLineKeyHandler
-Chord
Ctrl
+
Shift
+
C
-Function
CaptureScreen
Si en no hay una función predefinida para hacer lo que quieres, puedes utilizar el parámetro-ScriptBlock
para que PSReadLine
ejecute cualquier código de tu elección cuando pulses una tecla o combinación de teclas. El ejemplo de la Solución lo demuestra añadiendo la navegación hacia delante y hacia atrás en el historial de directorios.
Para que cualquiera de estos cambios persista, sólo tienes que añadir estos comandos a tu Perfil PowerShell.
Aunque realmente sólo sirve para escenarios extremadamente avanzados ahora que PSReadLine
cubre casi todo lo que podrías necesitar, puedes personalizar o aumentar aún más esta funcionalidad mediante la función PSConsoleHostReadLine
. Cuando defines este método en el host de la consola PowerShell, PowerShell llama a esa función en lugar de a la funcionalidad de entrada por defecto de Windows en Modo Cocinado. La versión por defecto de esta función lanza PSReadLine
's ReadLine
input handler. Pero si deseas redefinir esto completamente, eso es todo: el resto depende de ti. Si quieres implementar un método de entrada personalizado, la libertad (y la responsabilidad) es toda tuya.
Cuando definas esta función, debe procesar la entrada del usuario y devolver el comando resultante. El Ejemplo 1-5 implementa un mecanismo de entrada de usuario basado en el Bloc de Notas un tanto ridículo:
Ejemplo 1-5. Un mecanismo de entrada de usuario basado en el Bloc de Notas
function
PSConsoleHostReadLine
{
$inputFile
=
Join-Path
$env:TEMP
PSConsoleHostReadLine
Set-Content
$inputFile
"PS > "
## Notepad opens. Enter your command in it, save the file,
## and then exit.
notepad
$inputFile
|
Out-Null
$userInput
=
Get-Content
$inputFile
$resultingCommand
=
$userInput
.
Replace
(
"PS >"
,
""
)
$resultingCommand
}
Para más información sobre el manejo de las pulsaciones de teclas y otras formas de entrada del usuario, consulta el Capítulo 13.
1.11 Personalizar el comportamiento de resolución de comandos de PowerShell
Solución
Asigna a un bloque de guión a una o todas las propiedades PreCommandLookupAction
, PostCommandLookupAction
o CommandNotFoundAction
de $executionContext.SessionState.InvokeCommand
. El ejemplo 1-6 permite navegar fácilmente por el directorio padre cuando escribes varios puntos.
Debate
Cuando invocas un comando en PowerShell, el motor pasa por tres fases distintas:
-
Recupera el texto de la orden.
-
Busca el comando para ese texto.
-
Invoca el comando encontrado.
En PowerShell, la propiedad $executionContext.SessionState.InvokeCommand
te permite anular cualquiera de estas etapas con bloques de script para interceptar alguna o todas las etapasPreCommandLookupAction
PostCommandLookupAction
o CommandNotFoundAction
.
Cada bloque de script recibe dos parámetros: el nombre del comando y un objeto(CommandLookupEventArgs
) para controlar el comportamiento de búsqueda del comando. Si tu manejador asigna un bloque de secuencia de comandos a la propiedad CommandScriptBlock
del CommandLookupEventArgs
o asigna un CommandInfo
a la propiedad Command
del CommandLookupEventArgs
, PowerShell utilizará ese bloque de secuencia de comandos o comando, respectivamente. Si tu bloque de secuencia de comandos asigna la propiedad StopSearch
a true
, PowerShell no realizará ninguna otra resolución de comandos.
PowerShell invoca el bloque de script PreCommandLookupAction
cuando conoce el nombre de un comando (por ejemplo, Get-Process
) pero aún no ha buscado el comando en sí. Puedes anular esta acción si quieres reaccionar basándote principalmente en el texto del nombre del comando o si quieres adelantarte a la resolución habitual de comandos o alias de PowerShell. Por ejemplo, el Ejemplo 1-7 muestra un PreCommandLookupAction
que busca comandos con un asterisco delante de su nombre. Cuando encuentra uno, activa el parámetro-Verbose
parámetro
Ejemplo 1-7. Personalización de la PreCommandLookupAction
$executionContext.SessionState.InvokeCommand.PreCommandLookupAction = { param($CommandName, $CommandLookupEventArgs) ## If the command name starts with an asterisk, then ## enable its Verbose parameter if($CommandName -match "\*") { ## Remove the leading asterisk $NewCommandName = $CommandName -replace '\*','' ## Create a new script block that invokes the actual command, ## passes along all original arguments, and adds in the -Verbose ## parameter $CommandLookupEventArgs.CommandScriptBlock = { & $NewCommandName @args -Verbose ## We call GetNewClosure() so that the reference to $NewCommandName ## can be used in the new command. }.GetNewClosure() } } PS > dir > 1.txt PS > dir > 2.txt PS > del 1.txt PS > *del 2.txt VERBOSE: Performing operation "Remove file" on Target "C:\temp\tempfolder\2.txt".
Después de que PowerShell ejecute el PreCommandLookupAction
(si existe y no devuelve un comando), pasa por su resolución de comandos habitual. Si encuentra un comando, invoca el bloque de script asociado al PostCommandLookupAction
. Puedes anular esta acción si quieres reaccionar principalmente a un comando que está a punto de ser invocado. El Ejemplo 1-8 muestra un PostCommandLookupAction
que hace un recuento de los comandos que utilizas con más frecuencia.
Ejemplo 1-8. Personalización de la PostCommandLookupAction
$executionContext.SessionState.InvokeCommand.PostCommandLookupAction = { param($CommandName, $CommandLookupEventArgs) ## Stores a hashtable of the commands we use most frequently if(-not (Test-Path variable:\CommandCount)) { $global:CommandCount = @{} } ## If it was launched by us (rather than as an internal helper ## command), record its invocation. if($CommandLookupEventArgs.CommandOrigin -eq "Runspace") { $commandCount[$CommandName] = 1 + $commandCount[$CommandName] } } PS > Get-Variable commandCount PS > Get-Process -id $pid PS > Get-Process -id $pid PS > $commandCount Name Value ---- ----- Out-Default 4 Get-Variable 1 prompt 4 Get-Process 2
Si la resolución del comando no tiene éxito, PowerShell invoca el bloque de script CommandNotFoundAction
si existe. En su forma más simple, puedes anular esta acción si quieres recuperarte o anular el comportamiento de error de PowerShell cuando no puede encontrar uncomando.
Como aplicación más avanzada, la CommandNotFoundAction
te permite escribir extensiones PowerShell que alteran su comportamiento basándose en la forma del nombre, en lugar de en los argumentos que se le pasan. Por ejemplo, puede que quieras lanzar automáticamente URLs con sólo escribirlas o navegar por proveedores con sólo escribir ubicaciones de rutas relativas.
La Solución ofrece un ejemplo de implementación de este tipo de manejador. Aunque la navegación dinámica por rutas relativas no es una función incorporada en PowerShell, es posible obtener una alternativa muy razonable interceptando el CommandNotFoundAction
. Si vemos un comando perdido que tiene un patrón que queremos manejar (una serie de puntos), devolvemos un bloque de secuencia de comandos que realiza la navegación por rutas relativas adecuada.
1.12 Encontrar una orden para realizar una tarea
Solución
Utiliza el cmdlet Get-Command
para buscar e investigar comandos.
Para obtener la información resumida sobre un comando concreto, especifica el nombre del comando como argumento:
Get-Command CommandName
Para obtener información detallada sobre un comando concreto, canaliza la salida deGet-Command
al cmdlet Format-List
:
Get-Command CommandName
| Format-List
Para buscar todos los comandos con un nombre que contenga texto, rodea el texto con caracteres asterisco:
Get-Command *text
*
Para buscar todos los comandos que utilicen el verbo Get
, introduce Get
en el parámetro-Verb
parámetro:
Get-Command
-Verb
Get
Para buscar todos los comandos que actúan sobre un servicio, utiliza Service
como valor del parámetro-Noun
:
Get-Command -Noun Service
Debate
Una de las ventajas que PowerShell proporciona a los administradores es la coherencia de los nombres de sus comandos. Todos los comandos de PowerShell (llamados cmdlets) siguen un patrón Verbo-Sustantivo regular: por ejemplo, Get-Process
, Get-Service
y Set-Location
. Los verbos de proceden de un conjunto relativamente pequeño de verbos estándar (enumerados en el Apéndice J) y describen la acción que realiza el cmdlet. Los sustantivos son específicos del cmdlet y describen sobre qué actúa el cmdlet.
Conociendo esta filosofía, puedes aprender fácilmente a trabajar con grupos de cmdlets. Si quieres iniciar un servicio en la máquina local, el verbo estándar para ello es Start
. Una buena suposición sería probar primero con Start-Service
(que en este caso sería correcto), pero escribir Get-Command -Verb Start
también sería una forma eficaz de ver qué cosas puedes iniciar. Yendo en sentido contrario, puedes ver qué acciones son compatibles con los servicios escribiendo Get-Command -Noun Service
.
Cuando utilizas el cmdlet Get-Command
, PowerShell devuelve resultados de la lista de todos los comandos disponibles en tu sistema. Si en cambio quieres buscar sólo comandos de módulos que hayas cargado explícitamente o mediante carga automática, utiliza el parámetro-ListImported
parámetro. Para más información sobre la carga automática de comandos de PowerShell, consulta la Receta 1.28.
Consulta la Receta 1.13 para ver una lista de todos los comandos junto con una breve descripción de lo que hacen.
El cmdlet Get-Command
es uno de los tres comandos que utilizarás con más frecuencia a medida que explores PowerShell. Los otros dos comandos son Get-Help
y Get-Member
.
En hay un punto importante que debes tener en cuenta cuando busques un comando PowerShell para realizar una tarea concreta. Muchas veces, ese comando PowerShell no existe, porque la tarea se realiza mejor de la misma forma que siempre-por ejemplo, ipconfig.exe
para obtener información de configuración IP, netstat.exe
para listar estadísticas de protocolo y conexiones de red TCP/IP actuales, y muchos más.
Para más información sobre el cmdlet Get-Command
, escribe Get-Help Get-Command
.
Ver también
1.13 Obtener ayuda sobre una orden
Solución
El comando que proporciona ayuda e información de uso sobre un comando se llama Get-Help
. Admite varias vistas diferentes de la información de ayuda, en función de tus necesidades.
Para obtener el resumen de la información de ayuda de un comando concreto, proporciona el nombre del comando como argumento al cmdlet Get-Help
. Esto incluye principalmente su sinopsis, sintaxis y descripción detallada:
Get-Help CommandName
o:
CommandName
-?
Para obtener la información de ayuda detallada de un comando concreto, proporciona la bandera -Detailed
al cmdlet Get-Help
. Además de la vista resumida, también incluye la descripción de sus parámetros y ejemplos:
Get-Help CommandName
-Detailed
Para obtener la información de ayuda completa de un comando concreto, proporciona la bandera -Full
al cmdlet Get-Help
. Además de la vista detallada, también incluye la descripción completa de sus parámetros y notas adicionales:
Get-Help CommandName
-Full
Para obtener sólo los ejemplos de un comando concreto, proporciona la bandera -Examples
al cmdlet Get-Help
:
Get-Help CommandName
-Examples
Para que recupere la versión en línea más actualizada del tema de ayuda de un comando, proporciona la bandera -Online
al cmdlet Get-Help
:
Get-Help CommandName
-Online
Para consultar en una vista gráfica de un tema de ayuda en la que se puedan realizar búsquedas, utiliza el parámetro -ShowWindow
:
Get-Help CommandName
-ShowWindow
Para buscar todos los temas de ayuda que contengan una palabra clave determinada, proporciona esa palabra clave como argumento al cmdlet Get-Help
. Si la palabra clave no es también el nombre de un tema de ayuda concreto, éste devuelve todos los temas de ayuda que contengan la palabra clave, incluyendo su nombre, categoría y sinopsis:
Get-Help Keyword
Debate
El cmdlet Get-Help
es la forma principal de interactuar con el sistema de ayuda en PowerShell. Al igual que el cmdlet Get-Command
, el cmdlet Get-Help
admite comodines. Si quieres listar todos los comandos cuyo contenido de ayuda coincida con un determinado patrón (por ejemplo, proceso), sólo tienes que escribir:
Get-Help
*
process
*
Si el patrón coincide con un solo comando, PowerShell muestra la ayuda de ese comando. Aunque el comodín de comandos y la búsqueda por palabras clave son una forma útil de buscar en la ayuda de PowerShell, consulta la Receta 1.15 para ver un script que te permite buscar en el contenido de la ayuda un patrón especificado.
Aunque tienes a tu disposición miles de páginas de contenido de ayuda escrito a medida en , PowerShell sólo incluye por defecto la información que puede generar automáticamente a partir de la contenida en los propios comandos: nombres, parámetros, sintaxis y parámetros por defecto. Necesitas actualizar el contenido de la ayuda para recuperar el resto. Cuando ejecutes Get-Help
para un comando para el que no hayas descargado contenido de ayuda, verás las siguientes observaciones como parte de esa ayuda:
REMARKS Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help. -- To download and install Help files for the module that includes this cmdlet, use Update-Help. -- To view the Help topic for this cmdlet online, type: "Get-Help Get-Process -Online" or go to https://go.microsoft.com/fwlink/?LinkID=2096814.
Ejecuta el cmdlet Update-Help
, y PowerShell descargará e instalará automáticamente el contenido de ayuda más reciente para todos los módulos de tu sistema. Para más información sobre la ayuda actualizable, consulta la Receta 1.14.
Si quieres generar una lista de todos los cmdlets y alias (junto con sus breves sinopsis), ejecuta el siguiente comando:
Get-Help
*
-Category
Cmdlet
|
Select-Object
Name
,
Synopsis
|
Format-Table
-Auto
Además de la ayuda basada en la consola, PowerShell también ofrece acceso en línea a su contenido de ayuda. La Solución muestra cómo acceder rápidamente al contenido de la ayuda en línea.
El cmdlet Get-Help
es uno de los tres comandos que utilizarás con más frecuencia a medida que explores PowerShell. Los otros dos comandos son Get-Command
y Get-Member
.
Para más información sobre el cmdlet Get-Help
, escribe Get-Help Get-Help
.
1.14 Actualizar el contenido de la Ayuda del Sistema
Debate
Uno de los mayores puntos fuertes de PowerShell es el increíble detalle de su contenido de ayuda. Contando sólo el contenido de la ayuda y los temas de about_*
que describen la funcionalidad básica, la ayuda de PowerShell incluye aproximadamente medio millón de palabras y abarcaría 1.200 páginas si se imprimiera.
El reto al que cada versión de PowerShell se ha visto obligada a enfrentarse es que este contenido de ayuda se escribe al mismo tiempo que la propia PowerShell. Dado que su objetivo es ayudar al usuario, el contenido que está listo en el momento del lanzamiento de una versión de PowerShell es la mejor estimación posible de aquello para lo que los usuarios necesitarán ayuda.
A medida que los usuarios se familiarizan con PowerShell, empiezan a tener preguntas. Algunas de ellas se abordan en los temas de ayuda, mientras que otras no. A veces, la ayuda es simplemente incorrecta debido a un cambio en el producto durante el lanzamiento. Para solucionar esto, PowerShell admite la ayuda actualizable en.
No sólo es posible actualizar la ayuda, sino que, de hecho, el comando Update-Help
es la única forma de obtener ayuda en tu sistema. Fuera de la caja, PowerShell proporciona una experiencia derivada únicamente de lo que está incorporado en los propios comandos: nombre, sintaxis, parámetros y valores por defecto.
Cuando ejecute Get-Help
para un comando para el que no hayas descargado contenido de ayuda, verás las siguientes observaciones como parte de esa ayuda:
REMARKS Get-Help cannot find the Help files for this cmdlet on this computer. It is displaying only partial help. -- To download and install Help files for the module that includes this cmdlet, use Update-Help. -- To view the Help topic for this cmdlet online, type: "Get-Help Get-Process -Online" or go to https://go.microsoft.com/fwlink/?LinkID=2096814.
Ejecuta el cmdlet Update-Help
, y PowerShell descargará e instalará automáticamente el contenido de ayuda más reciente para todos los módulos de tu sistema.
Cuando ejecutas Update-Help
, PowerShell busca en cada módulo de tu sistema, comparando la ayuda que tienes para ese módulo con la última versión en línea. Para los módulos en caja, PowerShell utiliza download.microsoft.com
para recuperar el contenido actualizado de la ayuda. Otros módulos que descargues de Internet pueden utilizar la clave de módulo HelpInfoUri
para respaldar su propia ayuda actualizable.
PowerShell almacena este contenido en el directorio PowerShell\Help
de tus documentos de usuario o directorio personal.
Por defecto, el comando Update-Help
recupera su contenido de Internet. Si quieres actualizar la ayuda en una máquina no conectada a Internet, puedes utilizar el parámetro-SourcePath
del cmdlet Update-Help
. Esta ruta representa un directorio o ruta UNC donde PowerShell debe buscar el contenido actualizado de la ayuda. Para rellenar este contenido, utiliza primero el cmdlet Save-Help
para descargar los archivos y, a continuación, cópialos en la ubicación de origen.
Para más información sobre la ayuda de PowerShell, consulta la Receta 1.13.
Ver también
1.15 Programa: Buscar ayuda por texto
Tanto como los cmdlets Get-Command
y Get-Help
te permiten buscar nombres de comandos que coincidan con un patrón determinado. Sin embargo, cuando no sepas exactamente qué partes del nombre de un comando estás buscando, te resultará más fácil buscar la respuesta en el contenido de la ayuda. En los sistemas Unix, este comando se llama Apropos
.
El cmdlet Get-Help
busca automáticamente referencias a palabras clave en la base de datos de ayuda cuando no encuentra un tema de ayuda para el argumento que le proporcionas. Además de eso, puede que quieras ampliarlo aún más para buscar patrones de texto o incluso temas de ayuda que hablen de temas de ayuda existentes. Las herramientas de ayuda de PowerShell admiten una versión de búsqueda de contenido con comodines, pero no admiten expresiones regulares completas.
Pero eso no tiene por qué detenernos, ya que podemos escribir la funcionalidad nosotros mismos.
Para ejecutar este programa, proporciona una cadena de búsqueda al script Search-Help
(que aparece en el Ejemplo 1-9). La cadena de búsqueda puede ser un texto simple o una expresión regular. A continuación, el script muestra el nombre y la sinopsis de todos los temas de ayuda que coincidan. Para ver el contenido de la ayuda de ese tema, utiliza el cmdlet Get-Help
.
Ejemplo 1-9. Buscar-Ayuda.ps1
##############################################################################
##
## Search-Help
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Search the PowerShell help documentation for a given keyword or regular
expression. For simple keyword searches in PowerShell version two or three,
simply use "Get-Help <keyword>"
.EXAMPLE
PS > Search-Help hashtable
Searches help for the term 'hashtable'
.EXAMPLE
PS > Search-Help "(datetime|ticks)"
Searches help for the term datetime or ticks, using the regular expression
syntax.
#>
param
(
## The pattern to search for
[
Parameter
(
Mandatory
=
$true
)]
$Pattern
)
$helpNames
=
$(
Get-Help
*
|
Where-Object
{
$_
.
Category
-ne
"Alias"
})
## Go through all of the help topics
foreach
(
$helpTopic
in
$helpNames
)
{
## Get their text content, and
$content
=
Get-Help
-Full
$helpTopic
.
Name
|
Out-String
if
(
$content
-match
"(.{0,30}$pattern.{0,30})"
)
{
$helpTopic
|
Add-Member
NoteProperty
Match
$matches
[
0
].
Trim
()
$helpTopic
|
Select-Object
Name
,
Match
}
}
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.16 Iniciar PowerShell en una ubicación específica
Debate
Si estás navegando por el sistema de archivos con el Explorador de Windows, escribe pwsh.exe
opowershell.exe
en la barra de direcciones se inicia PowerShell en esa ubicación (como se muestra en la Figura 1-2).
Ten en cuenta que lo que escribas debe terminar con la extensión .exe
, de lo contrario el Explorador abrirá generalmente tu carpeta de documentos PowerShell
. Además, puedes abrir Windows PowerShell directamente desde el menú Archivo, como se muestra en la Figura 1-3.
Para otra forma de iniciar PowerShell desde el Explorador de Windows, el Terminal de Windows (si lo has instalado) añade una opción "Abrir en el Terminal de Windows" cuando haces clic con el botón derecho en una carpeta desde el Explorador de Windows.
Si no estás navegando por la carpeta deseada con el Explorador de Windows, puedes utilizar Inicio→Ejecutar (o cualquier otro medio de lanzar una aplicación) para lanzar PowerShell en una ubicación específica. Para ello, utiliza el parámetro -NoExit
de PowerShell, junto con el parámetro-Command
de PowerShell. En el parámetro -Command
, llama al cmdlet Set-Location
para desplazarte inicialmente a la ubicación que desees.
pwsh -NoExit -Command Set-Location 'C:\Program Files'
1.17 Invocar un comando o script PowerShell desde fuera de PowerShell
Debate
Por defecto en, cualquier argumento a pwsh.exe se interpreta como un script a ejecutar. Si utilizas el parámetro -Command
, PowerShell ejecuta el comando como si lo hubieras escrito en el shell interactivo y, a continuación, sale. Puedes personalizar este comportamiento suministrando otros parámetros a pwsh.exe, como -NoExit
, -NoProfile
, y otros.
Nota
Si eres el autor de un programa que necesita ejecutar scripts o comandos PowerShell, PowerShell te permite llamar a estos scripts y comandos de forma mucho más sencilla que llamando a su interfaz de línea de comandos. Para más información sobre este enfoque, consulta la Receta 17.10.
Puesto que lanzar un script es algo tan habitual, PowerShell proporciona el parámetro -File
para eliminar las complejidades que surgen al tener que invocar un script desde el parámetro-Command
parámetro. Esta técnica te permite invocar un script de PowerShell como objetivo de un script de inicio de sesión, una asociación avanzada de archivos, una tarea programada, etc.
Nota
Cuando PowerShell detecta que sus flujos de entrada o salida han sido redirigidos, suprime los avisos que podría mostrar normalmente. Si quieres alojar un aviso interactivo de PowerShell dentro de otra aplicación (como Emacs), utiliza -
como argumento para el parámetro -File
. En PowerShell (como en los shells Unix tradicionales), esto implica "tomado de la entrada estándar".
pwsh -File -
Si el script es para una automatización en segundo plano o una tarea programada, estos scripts a veces pueden interferir con el entorno del usuario (o verse influidos por él). Para estas situaciones, resultan útiles tres parámetros:
-NoProfile
-
Ejecuta el comando o script sin cargar los scripts de perfil de usuario. Esto hace que el script se inicie más rápido, pero principalmente evita que las preferencias del usuario (por ejemplo, alias y variables de preferencia) interfieran en el entorno de trabajo del script.
-WindowStyle
-
Ejecuta el comando o script con el estilo de ventana especificado -más comúnmente
Hidden
. Cuando se ejecuta con un estilo de ventana deHidden
, PowerShell oculta su ventana principal inmediatamente. Para conocer más formas de controlar el estilo de ventana desde PowerShell, consulta la Receta 24.3. -ExecutionPolicy
-
Ejecuta el comando o script con una política de ejecución especificada aplicada sólo a esta instancia de PowerShell. Esto te permite escribir scripts PowerShell para gestionar un sistema sin tener que cambiar la política de ejecución de todo el sistema. Para más información sobre las políticas de ejecución de ámbito, consulta la Receta 18.1.
Si los argumentos del parámetro -Command
se vuelven complejos, el manejo de caracteres especiales en la aplicación que llama a PowerShell (como cmd.exe) podría interferir con el comando que quieres enviar a PowerShell. Para esta situación, PowerShell admite un parámetro EncodedCommand
: una representación codificada en Base64 de la cadena Unicode que quieres ejecutar. El Ejemplo 1-10 demuestra cómo convertir una cadena que contiene comandos PowerShell a una forma codificada en Base64.
Ejemplo 1-10. Convertir comandos PowerShell en una forma codificada en Base64
$commands
=
'1..10 | % { "PowerShell Rocks" }'
$bytes
=
[System.Text.Encoding]
::
Unicode
.
GetBytes
(
$commands
)
$encodedString
=
[Convert]
::
ToBase64String
(
$bytes
)
Una vez que tengas la cadena codificada, puedes utilizarla como valor del parámetro EncodedCommand
, como se muestra en el Ejemplo 1-11.
Ejemplo 1-11. Iniciar PowerShell con un comando codificado desde cmd.exe
Microsoft Windows [Version 10.0.19041.685] (c) 2020 Microsoft Corporation. All rights reserved. C:\Users\Lee>PowerShell -EncodedCommand MQAuAC4AMQAwACAAfAAgACUAIAB7ACAAIgBQAG8A dwBlAHIAUwBoAGUAbABsACAAUgBvAGMAawBzACIAIAB9AA== PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.18 Entender y Personalizar el Completado de Pestañas de PowerShell
Debate
Cuando pulsas Tabulador, PowerShell invoca una función conocida como expansión del tabulador: sustituir lo que has escrito hasta ese momento por una versión expandida de ello (si se aplica alguna). Por ejemplo, si escribes Set-Location C:\
y luego pulsas Tabulador, PowerShell empieza a recorrer los directorios de C:\
para que navegues hasta ellos.
Las funciones que ofrece la expansión de pestañas incorporada de PowerShell son bastante ricas, como se muestra en la Tabla 1-2.
Descripción | Ejemplo |
---|---|
Completar comando. Completa los nombres de los comandos cuando el texto actual parece representar una invocación de comando. |
|
Completar parámetros. Completa los parámetros del comando actual. |
|
Completar argumentos. Completa los argumentos de comando para el parámetro de comando actual. Esto se aplica a cualquier argumento de comando que tome un conjunto fijo de valores (enumeraciones o parámetros que definen un atributo |
|
Completar texto del historial. Sustituye la entrada actual por elementos del historial de comandos que coincidan con el texto después del carácter |
|
Finalización del ID del historial. Sustituye la entrada actual por la línea de comando del número de elemento |
|
Completar nombre de archivo. Sustituye el valor del parámetro actual por nombres de archivo que coincidan con lo que has escrito hasta ahora. Cuando se aplica al cmdlet |
|
Completar operador. Sustituye el texto actual por un operador coincidente. Esto incluye las banderas suministradas a la sentencia |
|
Completar variables. Sustituye el texto actual por las variables PowerShell disponibles. PowerShell incorpora incluso variables del contenido de scripts que nunca han sido invocados. |
|
Completar miembro. Sustituye los nombres de los miembros de la variable o tipo a los que se hace referencia en ese momento. Cuando PowerShell puede deducir los miembros a partir de comandos anteriores de la cadena, admite incluso la compleción de miembros dentro de bloques de secuencia de comandos. |
|
Completar tipos. Sustituye los nombres abreviados de los tipos por su nombre cualificado por el espacio de nombres. |
|
Si quieres ampliar las capacidades de expansión de pestañas de PowerShell, define una función llamada TabExpansion2
. Puedes añadirla directamente a tu perfil de PowerShell, o utilizarla como fuente de tu perfil. El Ejemplo 1-12 muestra un ejemplo de función personalizada de expansión de pestañas que amplía la funcionalidad ya incorporada en PowerShell.
Ejemplo 1-12. Ejemplo de implementación de TabExpansion2
##############################################################################
##
## TabExpansion2
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
function
TabExpansion2
{
[
CmdletBinding
(
DefaultParameterSetName
=
'ScriptInputSet'
)]
Param
(
[
Parameter
(
ParameterSetName
=
'ScriptInputSet'
,
Mandatory
=
$true
,
Position
=
0
)]
[string]
$inputScript
,
[
Parameter
(
ParameterSetName
=
'ScriptInputSet'
,
Mandatory
=
$true
,
Position
=
1
)]
[int]
$cursorColumn
,
[
Parameter
(
ParameterSetName
=
'AstInputSet'
,
Mandatory
=
$true
,
Position
=
0
)]
[System.Management.Automation.Language.Ast]
$ast
,
[
Parameter
(
ParameterSetName
=
'AstInputSet'
,
Mandatory
=
$true
,
Position
=
1
)]
[System.Management.Automation.Language.Token[]]
$tokens
,
[
Parameter
(
ParameterSetName
=
'AstInputSet'
,
Mandatory
=
$true
,
Position
=
2
)]
[System.Management.Automation.Language.IScriptPosition]
$positionOfCursor
,
[
Parameter
(
ParameterSetName
=
'ScriptInputSet'
,
Position
=
2
)]
[
Parameter
(
ParameterSetName
=
'AstInputSet'
,
Position
=
3
)]
[Hashtable]
$options
=
$null
)
End
{
## Create a new 'Options' hashtable if one has not been supplied.
## In this hashtable, you can add keys for the following options, using
## $true or $false for their values:
##
## IgnoreHiddenShares - Ignore hidden UNC shares (such as \\COMPUTER\ADMIN$)
## RelativePaths - When expanding filenames and paths, $true forces PowerShell
## to replace paths with relative paths. When $false, forces PowerShell to
## replace them with absolute paths. By default, PowerShell makes this
## decision based on what you had typed so far before invoking tab completion.
## LiteralPaths - Prevents PowerShell from replacing special file characters
## (such as square brackets and back-ticks) with their escaped equivalent.
if
(
-not
$options
)
{
$options
=
@{}
}
## Demonstrate some custom tab expansion completers for parameters.
## This is a hash table of parameter names (and optionally cmdlet names)
## that we add to the $options hashtable.
##
## When PowerShell evaluates the script block, $args gets the
## following: command name, parameter, word being completed,
## AST of the command being completed, and currently-bound arguments.
$options
[
"CustomArgumentCompleters"
]
=
@{
"Get-ChildItem:Filter"
=
{
"*.ps1"
,
"*.txt"
,
"*.doc"
}
"ComputerName"
=
{
"ComputerName1"
,
"ComputerName2"
,
"ComputerName3"
}
}
## Also define a completer for a native executable.
## When PowerShell evaluates the script block, $args gets the
## word being completed, and AST of the command being completed.
$options
[
"NativeArgumentCompleters"
]
=
@{
"attrib"
=
{
"+R"
,
"+H"
,
"+S"
}
}
## Define a "quick completions" list that we'll cycle through
## when the user types '!!' followed by TAB.
$quickCompletions
=
@(
'Get-Process -Name PowerShell | ? Id -ne $pid | Stop-Process'
,
'Set-Location $pshome'
,
(
'$errors = $error | % { $_.InvocationInfo.Line }; Get-History | '
+
' ? { $_.CommandLine -notin $errors }'
)
)
## First, check the built-in tab completion results
$result
=
$null
if
(
$psCmdlet
.
ParameterSetName
-eq
'ScriptInputSet'
)
{
$result
=
[System.Management.Automation.CommandCompletion]
::
CompleteInput
(
<#inputScript#>
$inputScript
,
<#cursorColumn#>
$cursorColumn
,
<#options#>
$options
)
}
else
{
$result
=
[System.Management.Automation.CommandCompletion]
::
CompleteInput
(
<#ast#>
$ast
,
<#tokens#>
$tokens
,
<#positionOfCursor#>
$positionOfCursor
,
<#options#>
$options
)
}
## If we didn't get a result
if
(
$result
.
CompletionMatches
.
Count
-eq
0
)
{
## If this was done at the command-line or in a remote session,
## create an AST out of the input
if
(
$psCmdlet
.
ParameterSetName
-eq
'ScriptInputSet'
)
{
$ast
=
[System.Management.Automation.Language.Parser]
::
ParseInput
(
$inputScript
,
[ref]
$tokens
,
[ref]
$null
)
}
## In this simple example, look at the text being supplied.
## We could do advanced analysis of the AST here if we wanted,
## but in this case just use its text. We use a regular expression
## to check if the text started with two exclamations, and then
## use a match group to retain the rest.
$text
=
$ast
.
Extent
.
Text
if
(
$text
-match
'^!!(.*)'
)
{
## Extract the rest of the text from the regular expression
## match group.
$currentCompletionText
=
$matches
[
1
].
Trim
()
## Go through each of our quick completions and add them to
## our completion results. The arguments to the completion results
## are the text to be used in tab completion, a potentially shorter
## version to use for display (i.e.: intellisense in the ISE),
## the type of match, and a potentially more verbose description to
## be used as a tool tip.
$quickCompletions
|
Where-Object
{
$_
-match
$currentCompletionText
}
|
Foreach
-Object
{
$result
.
CompletionMatches
.
Add
(
(
New-Object
Management
.
Automation
.
CompletionResult
$_
,
$_
,
"Text"
,
$_
)
)
}
}
}
return
$result
}
}
1.19 Programa: Aprende alias para comandos comunes
En el uso interactivo de , los nombres completos de los cmdlets (como Get-ChildItem
) son engorrosos y lentos de escribir. Aunque los alias son mucho más eficientes, lleva un tiempo descubrirlos. Para aprender más fácilmente los alias, puedes modificar tu prompt para que te recuerde la versión abreviada de los comandos con alias que utilices.
Esto implica dos pasos:
-
Añade el programa,
Get-AliasSuggestion.ps1
, que se muestra en el Ejemplo 1-13, a tu directorio de herramientas o a otro directorio.Ejemplo 1-13. Get-AliasSugerencia.ps1
##############################################################################
##
## Get-AliasSuggestion
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Get an alias suggestion from the full text of the last command. Intended to
be added to your prompt function to help learn aliases for commands.
.EXAMPLE
PS > Get-AliasSuggestion Remove-ItemProperty
Suggestion: An alias for Remove-ItemProperty is rp
#>
param
(
## The full text of the last command
$LastCommand
)
Set-StrictMode
-Version
3
$helpMatches
=
@()
## Find all of the commands in their last input
$tokens
=
[Management.Automation.PSParser]
::
Tokenize
(
$lastCommand
,
[ref]
$null
)
$commands
=
$tokens
|
Where-Object
{
$_
.
Type
-eq
"Command"
}
## Go through each command
foreach
(
$command
in
$commands
)
{
## Get the alias suggestions
foreach
(
$alias
in
Get-Alias
-Definition
$command
.
Content
)
{
$helpMatches
+=
"Suggestion: An alias for "
+
"
$(
$alias
.
Definition
)
is
$(
$alias
.
Name
)
"
}
}
$helpMatches
-
Añade el texto del Ejemplo 1-14 a la función
Prompt
de tu perfil. Si todavía no tienes una funciónPrompt
, consulta la Receta 1.9 para aprender a añadir una.Ejemplo 1-14. Un prompt útil para enseñarte alias de comandos comunes
function
prompt
{
## Get the last item from the history
$historyItem
=
Get-History
-Count
1
## If there were any history items
if
(
$historyItem
)
{
## Get the training suggestion for that item
$suggestions
=
@(
Get-AliasSuggestion
$historyItem
.
CommandLine
)
## If there were any suggestions
if
(
$suggestions
)
{
## For each suggestion, write it to the screen
foreach
(
$aliasSuggestion
in
$suggestions
)
{
Write-Host
"$aliasSuggestion"
}
Write-Host
""
}
}
## Rest of prompt goes here
"PS [$env:COMPUTERNAME] >"
}
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.20 Programa: Aprender alias para parámetros comunes
Solución
Utiliza el script Get-ParameterAlias
, como se muestra en el Ejemplo 1-15, para devolver todos los alias de los parámetros utilizados por el comando anterior en tu historial de sesiones.
Ejemplo 1-15. Get-ParámetroAlias.ps1
##############################################################################
##
## Get-ParameterAlias
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Looks in the session history, and returns any aliases that apply to
parameters of commands that were used.
.EXAMPLE
PS > dir -ErrorAction SilentlyContinue
PS > Get-ParameterAlias
An alias for the 'ErrorAction' parameter of 'dir' is ea
#>
Set-StrictMode
-Version
3
## Get the last item from their session history
$history
=
Get-History
-Count
1
if
(
-not
$history
)
{
return
}
## And extract the actual command line they typed
$lastCommand
=
$history
.
CommandLine
## Use the Tokenizer API to determine which portions represent
## commands and parameters to those commands
$tokens
=
[System.Management.Automation.PsParser]
::
Tokenize
(
$lastCommand
,
[ref]
$null
)
$currentCommand
=
$null
## Now go through each resulting token
foreach
(
$token
in
$tokens
)
{
## If we've found a new command, store that.
if
(
$token
.
Type
-eq
"Command"
)
{
$currentCommand
=
$token
.
Content
}
## If we've found a command parameter, start looking for aliases
if
((
$token
.
Type
-eq
"CommandParameter"
)
-and
(
$currentCommand
))
{
## Remove the leading "-" from the parameter
$currentParameter
=
$token
.
Content
.
TrimStart
(
"-"
)
## Determine all of the parameters for the current command.
(
Get-Command
$currentCommand
).
Parameters
.
GetEnumerator
()
|
## For parameters that start with the current parameter name,
Where-Object
{
$_
.
Key
-like
"$currentParameter*"
}
|
## return all of the aliases that apply. We use "starts with"
## because the user might have typed a shortened form of
## the parameter name.
Foreach
-Object
{
$_
.
Value
.
Aliases
|
Foreach
-Object
{
"Suggestion: An alias for the '$currentParameter' "
+
"parameter of '$currentCommand' is '$_'"
}
}
}
}
Debate
Para facilitar la escritura de parámetros de comandos, PowerShell te permite escribir sólo la parte del parámetro del comando que sea necesaria para desambiguarlo de otros parámetros de ese comando. Además de los acortamientos implícitos admitidos por el shell, los autores de cmdlets también pueden definir alias explícitos para sus parámetros; por ejemplo, CN
como forma abreviada de ComputerName
.
Aunque son útiles, estos alias son difíciles de descubrir.
Si quieres ver los alias de un comando concreto, puedes acceder a su colección Parameters
:
PS > (Get-Command New-TimeSpan).Parameters.Values | Select Name,Aliases Name Aliases ---- ------- Start {LastWriteTime} End {} Days {} Hours {} Minutes {} Seconds {} Verbose {vb} Debug {db} ErrorAction {ea} WarningAction {wa} InformationAction {infa} ErrorVariable {ev} WarningVariable {wv} InformationVariable {iv} OutVariable {ov} OutBuffer {ob} PipelineVariable {pv}
Si quieres aprender cualquier alias de los parámetros de tu comando anterior, simplemente ejecuta Get-ParameterAlias.ps1
. Para que PowerShell lo haga automáticamente, añade una llamada a Get-ParameterAlias.ps1
en tu prompt.
Este script de se basa en dos características principales: La API Tokenizer de PowerShell y la rica información que devuelve el cmdlet Get-Command
. La API Tokenizer de PowerShell examina su entrada y devuelve la interpretación que PowerShell hace de ella: comandos, parámetros, valores de parámetros, operadores y mucho más. Al igual que la rica salida producida por la mayoría de los comandos de PowerShell, Get-Command
devuelve información sobre los parámetros de un comando, conjuntos de parámetros, tipo de salida (si se especifica) y mucho más.
Para más información sobre la API Tokenizer, consulta la Receta 10.10.
1.21 Acceder y Gestionar el Historial de tu Consola
Solución
Los atajos que se dan en la Receta 1.9 te permiten gestionar tu historial, pero PowerShell ofrece varias funciones que te ayudarán a trabajar con tu consola de forma aún más detallada.
Para obtener los comandos más recientes de tu sesión, utiliza el cmdlet Get-History
(o su alias h
):
Get-History
Para volver a ejecutar un comando específico de tu historial de sesiones, proporciona su ID al cmdlet Invoke-History
(o su alias ihy
):
Invoke-History ID
Para aumentar (o limitar) el número de comandos almacenados en tu historial de sesión, asigna un nuevo valor a la variable $MaximumHistoryCount
:
$MaximumHistoryCount = Count
Para guardar tu historial de comandos en un archivo, canaliza la salida de Get-History
al cmdlet Export-CliXml
:
Get-History | Export-CliXml Filename
Para que añada un historial de comandos previamente guardado al historial de la sesión actual, llama al cmdlet Import-CliXml
y luego canaliza esa salida al cmdlet Add-History
:
Import-CliXml Filename
| Add-History
Para borrar todos los comandos del historial de tu sesión, utiliza el cmdlet Clear-History
:
Clear-History
Debate
A diferencia de las teclas rápidas del historial de la consola que se comentan en la Receta 1.9, el cmdlet Get-History
produce objetos ricos que representan información sobre los elementos de tu historial. Cada objeto contiene el ID de ese elemento, la línea de comandos, la hora de inicio de ejecución y la hora de fin de ejecución.
Una vez que conozcas el ID de un elemento del historial (como se muestra en la salida de Get-History
), puedes pasarlo a Invoke-History
para volver a ejecutar ese comando. La función prompt de ejemplo que se muestra en la Receta 1.9 facilita el trabajo con elementos del historial anteriores, ya que el prompt de cada comando incluye el ID del historial que lo representará.
Nota
En puedes ver fácilmente cuánto tiempo se tardó en invocar una serie de comandos consultando la propiedad Duration
. Es una forma estupenda de saber exactamente cuánto tiempo se tardó en invocar los comandos que acaban de ahorrarte horas de trabajo manual:
PS:29 > Get-History 27,28 | Format-Table * Id CommandLine StartExecutionTime Duration -- ----------- ------------------ -------- 27 dir 2/15/2021 5:12:49 PM 00:00:00.0319401 28 Start-Sleep -Seconds 45 2/15/2021 5:12:53 PM 00:00:45.0073792
Los IDs proporcionados por el cmdlet Get-History
difieren de los IDs proporcionados por las teclas rápidas comunes del historial de la consola de Windows (como F7), porque sus técnicas de gestión del historial difieren.
Por defecto, PowerShell almacena las últimas 4.096 entradas de tu historial de comandos. Si quieres aumentar o disminuir esta cantidad, establece la variable $MaximumHistoryCount
al tamaño que desees. Para que este cambio sea permanente, establece la variable en tu script de perfil de PowerShell.
Con diferencia, la función más útil del historial de comandos de PowerShell es para revisar la experimentación ad hoc y capturarla en un script que luego puedas utilizar una y otra vez. Para una visión general de ese proceso (y un script que ayuda a automatizarlo), consulta la Receta 1.22.
1.22 Programa: Crea Scripts a partir de tu Historial de Sesiones
Después de experimentando interactivamente en la línea de comandos durante un tiempo para resolver una tarea de varios pasos, a menudo querrás conservar o compartir los pasos exactos que utilizaste para resolver finalmente el problema. El script te sonríe desde el búfer del historial, pero desgraciadamente está rodeado de muchos más comandos que no quieres conservar.
Nota
Para ver un ejemplo de cómo utilizar el cmdlet Out-GridView
para hacerlo gráficamente, consulta la Receta 2.4.
Para resolver este problema en, utiliza el cmdlet Get-History
para ver los comandos recientes que has escrito. A continuación, llama a Copy-History
con los ID de los comandos que quieras conservar, como se muestra en el Ejemplo 1-16.
Ejemplo 1-16. Copiar-Historial.ps1
##############################################################################
##
## Copy-History
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Copy selected commands from the history buffer into the clipboard as a script.
.EXAMPLE
PS > Copy-History
Copies the entire contents of the history buffer into the clipboard.
.EXAMPLE
PS > Copy-History -5
Copies the last five commands into the clipboard.
.EXAMPLE
PS > Copy-History 2,5,8,4
Copies commands 2,5,8, and 4.
.EXAMPLE
PS > Copy-History (1..10+5+6)
Copies commands 1 through 10, then 5, then 6, using PowerShell's array
slicing syntax.
#>
[
CmdletBinding
()]
param
(
## The range of history IDs to copy
[
Alias
(
"Id"
)]
[int[]]
$Range
)
Set-StrictMode
-Version
3
$history
=
@()
## If they haven't specified a range, assume it's everything
if
((
-not
$range
)
-or
(
$range
.
Count
-eq
0
))
{
$history
=
@(
Get-History
-Count
(
[Int16]
::
MaxValue
))
}
## If it's a negative number, copy only that many
elseif
((
$range
.
Count
-eq
1
)
-and
(
$range
[
0
]
-lt
0
))
{
$count
=
[Math]
::
Abs
(
$range
[
0
])
$history
=
(
Get-History
-Count
$count
)
}
## Otherwise, go through each history ID in the given range
## and add it to our history list.
else
{
foreach
(
$commandId
in
$range
)
{
if
(
$commandId
-eq
-
1
)
{
$history
+=
Get-History
-Count
1
}
else
{
$history
+=
Get-History
-Id
$commandId
}
}
}
## Finally, export the history to the clipboard.
$history
|
Foreach
-Object
{
$_
.
CommandLine
}
|
clip
.
exe
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.23 Invocar un Comando desde tu Historial de Sesiones
Debate
Cuando lleves un rato con el intérprete de comandos abierto, el búfer del historial se llenará rápidamente de comandos útiles. Las teclas de acceso rápido para la gestión del historial de descritas en la Receta 1.9 muestran una forma de navegar por tu historial, pero este tipo de navegación por el historial sólo funciona para las líneas de comandos que hayas escrito en esa sesión concreta. Si mantienes un historial de comandos persistente (como se muestra en la Receta 1.31), estos atajos no se aplican.
El cmdlet Invoke-History
ilustra el ejemplo más sencillo de trabajar con tu historial de comandos. Dado un ID de historial concreto (quizás mostrado en tu función prompt), al llamar a Invoke-History
con ese ID se ejecutará de nuevo ese comando. Para más información sobre esta técnica, consulta la Receta 1.9.
Como parte de su compatibilidad con el completado con tabulador, PowerShell también te facilita el acceso a comandos anteriores. Si antepones a tu comando el carácter #
, la finalización con tabulador adopta uno de dos enfoques:
- Finalización de ID
-
Si en tecleas un número, el tabulador de finalización encuentra la entrada de tu historial de comandos con ese ID y, a continuación, sustituye tu línea de comandos por el texto de esa entrada del historial. Esto es especialmente útil cuando quieres modificar ligeramente una entrada anterior del historial, ya que
Invoke-History
por sí mismo no admite eso. - Finalización del patrón
-
Si escribes cualquier otra cosa, el completado de tabulación busca entradas en tu historial de comandos que contengan ese texto. Bajo el capó, PowerShell utiliza el operador
-like
para hacer coincidir tus entradas de comandos, por lo que puedes utilizar todos los caracteres comodín que admite ese operador. Para más información sobre la búsqueda de patrones en el texto, consulta la Receta 5.7.
El completado de pestañas de PowerShell está impulsado en gran medida por la función TabExpansion2
totalmente personalizable. Puedes modificar fácilmente esta función para incluir funciones más avanzadas, o incluso sólo personalizar comportamientos concretos para adaptarlos a tus preferencias personales. Para más información, consulta la Receta 1.18.
1.24 Programa: Buscar un patrón en la salida formateada
Aunque las funciones de filtrado integradas de PowerShell son increíblemente flexibles (por ejemplo, el cmdlet Where-Object
), generalmente operan contra propiedades específicas del objeto entrante. Si buscas texto en la salida formateada del objeto, o no sabes qué propiedad contiene el texto que buscas, a veces resulta útil el sencillo filtrado basado en texto.
Para resolver este problema en, puedes canalizar la salida en el cmdlet Out-String
antes de pasarla al cmdlet Select-String
:
Get-Service
|
Out-String
-Stream
|
Select-String
audio
O, utilizando alias incorporados:
Get-Service
|
oss
|
sls
audio
En forma de script, Select-TextOutput
(mostrado en el Ejemplo 1-17) hace exactamente esto, y te permite buscar un patrón en la representación visual de la salida del comando.
Ejemplo 1-17. Seleccionar-TextoSalida.ps1
##############################################################################
##
## Select-TextOutput
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Searches the textual output of a command for a pattern.
.EXAMPLE
PS > Get-Service | Select-TextOutput audio
Finds all references to "Audio" in the output of Get-Service
#>
param
(
## The pattern to search for
$Pattern
)
Set-StrictMode
-Version
3
$input
|
Out-String
-Stream
|
Select-String
$pattern
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.25 Ver y procesar interactivamente la salida de comandos
Debate
El cmdlet Out-GridView
es uno de los pocos cmdlets de PowerShell que muestra una interfaz gráfica de usuario. Mientras que los cmdlets Where-Object
y Sort-Object
son la forma más habitual de ordenar y filtrar listas de elementos, el cmdlet Out-GridView
es muy eficaz en el estilo de refinamiento repetido que a veces te ayuda a desarrollar consultas complejas. La Figura 1-4 muestra el cmdlet Out-GridView
en acción.
Out-GridView
permite a filtrar principalmente la salida de tus comandos de dos formas: una expresión de filtro rápido y un filtro de criterios.
Los filtros rápidos son bastante sencillos. Cuando escribes un texto en la ventana superior "Filtro", Out-GridView
filtra la lista para que sólo contenga elementos que coincidan con ese texto. Si quieres restringir este filtrado de texto a columnas específicas, simplemente proporciona un nombre de columna antes de tu cadena de búsqueda y separa ambos con dos puntos. Puedes proporcionar varias cadenas de búsqueda, en cuyo caso Out-GridView
sólo devolverá las filas que coincidan con todas las cadenas requeridas.
Nota
A diferencia de la mayoría de los cmdlets de filtrado de PowerShell, los filtros rápidos del cmdlet Out-GridView
no admiten comodines ni expresiones regulares. Para este tipo de consulta avanzada, puede ser útil el filtrado basado en criterios.
Los filtros de criterios proporcionan a un control detallado sobre el filtrado utilizado por el cmdlet Out-GridView
. Para aplicar un filtro de criterios, haz clic en el botón "Añadir criterios" y selecciona una propiedad sobre la que filtrar. Out-GridView
añade una fila debajo del campo de filtro rápido y te permite elegir una de varias operaciones para aplicar a esta propiedad:
-
Inferior o igual a
-
Mayor o igual que
-
Entre
-
Es igual a
-
No es igual a
-
Contiene
-
No contiene
Además de estas opciones de filtrado, Out-GridView
también te permite hacer clic y reorganizar las columnas de cabecera para ordenarlas.
Salida de procesamiento
Una vez que hayas troceado la salida del comando, puedes seleccionar las filas que quieras conservar y pulsar Ctrl+C para copiarlas en el portapapeles. Out-GridView
copia los elementos en el portapapeles como datos separados por tabuladores, de modo que puedas pegar fácilmente la información en una hoja de cálculo u otro archivo para procesarla posteriormente.
Además de admitir la salida del portapapeles, el cmdlet Out-GridView
admite el filtrado de objetos de fidelidad completa si utilizas su parámetro -PassThru
. Para ver un ejemplo de este filtrado de fidelidad completa, consulta la Receta 2.4.
1.26 Programa: Ver y Explorar Objetos Interactivamente
Cuando trabajas con objetos desconocidos en PowerShell, gran parte de tu tiempo lo pasas con los comandos Get-Member
y Format-List
: navegando por las propiedades, revisando los miembros, etc.
Para la investigación ad hoc, suele ser útil una interfaz gráfica.
Para resolver este problema en, el Ejemplo 1-18 proporciona una vista de árbol interactiva que puedes utilizar para explorar y navegar por los objetos. Por ejemplo, para examinar la estructura de un script tal y como la ve PowerShell (su árbol de sintaxis abstracta):
$ps
=
{
Get-Process
-ID
$pid
}.
Ast
Show-Object
$ps
Para más información sobre cómo analizar la estructura de los scripts PowerShell, consulta la Receta 10.10.
Ejemplo 1-18. Mostrar-Objeto.ps1
#############################################################################
##
## Show-Object
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Provides a graphical interface to let you explore and navigate an object.
.EXAMPLE
PS > $ps = { Get-Process -ID $pid }.Ast
PS > Show-Object $ps
#>
param
(
## The object to examine
[
Parameter
(
ValueFromPipeline
=
$true
)]
$InputObject
)
Set-StrictMode
-Version
3
Add-Type
-Assembly
System
.
Windows
.
Forms
## Figure out the variable name to use when displaying the
## object navigation syntax. To do this, we look through all
## of the variables for the one with the same object identifier.
$rootVariableName
=
dir
variable
:
\*
-Exclude
InputObject
,
Args
|
Where-Object
{
$_
.
Value
-and
(
$_
.
Value
.
GetType
()
-eq
$InputObject
.
GetType
())
-and
(
$_
.
Value
.
GetHashCode
()
-eq
$InputObject
.
GetHashCode
())
}
## If we got multiple, pick the first
$rootVariableName
=
$rootVariableName
|
%
Name
|
Select
-First
1
## If we didn't find one, use a default name
if
(
-not
$rootVariableName
)
{
$rootVariableName
=
"InputObject"
}
## A function to add an object to the display tree
function
PopulateNode
(
$node
,
$object
)
{
## If we've been asked to add a NULL object, just return
if
(
-not
$object
)
{
return
}
## If the object is a collection, then we need to add multiple
## children to the node
if
(
[System.Management.Automation.LanguagePrimitives]
::
GetEnumerator
(
$object
))
{
## Some very rare collections don't support indexing (i.e.: $foo[0]).
## In this situation, PowerShell returns the parent object back when you
## try to access the [0] property.
$isOnlyEnumerable
=
$object
.
GetHashCode
()
-eq
$object
[
0
].
GetHashCode
()
## Go through all the items
$count
=
0
foreach
(
$childObjectValue
in
$object
)
{
## Create the new node to add, with the node text of the item and
## value, along with its type
$newChildNode
=
New-Object
Windows
.
Forms
.
TreeNode
$newChildNode
.
Text
=
"
$(
$node
.
Name
)
[$count] = $childObjectValue"
$newChildNode
.
ToolTipText
=
$childObjectValue
.
GetType
()
## Use the node name to keep track of the actual property name
## and syntax to access that property.
## If we can't use the index operator to access children, add
## a special tag that we'll handle specially when displaying
## the node names.
if
(
$isOnlyEnumerable
)
{
$newChildNode
.
Name
=
"@"
}
$newChildNode
.
Name
+=
"[$count]"
$null
=
$node
.
Nodes
.
Add
(
$newChildNode
)
## If this node has children or properties, add a placeholder
## node underneath so that the node shows a '+' sign to be
## expanded.
AddPlaceholderIfRequired
$newChildNode
$childObjectValue
$count
++
}
}
else
{
## If the item was not a collection, then go through its
## properties
foreach
(
$child
in
$object
.
PSObject
.
Properties
)
{
## Figure out the value of the property, along with
## its type.
$childObject
=
$child
.
Value
$childObjectType
=
$null
if
(
$childObject
)
{
$childObjectType
=
$childObject
.
GetType
()
}
## Create the new node to add, with the node text of the item and
## value, along with its type
$childNode
=
New-Object
Windows
.
Forms
.
TreeNode
$childNode
.
Text
=
$child
.
Name
+
" = $childObject"
$childNode
.
ToolTipText
=
$childObjectType
if
(
[Management.Automation.LanguagePrimitives]
::
GetEnumerator
(
$childObject
))
{
$childNode
.
ToolTipText
+=
"[]"
}
$childNode
.
Name
=
$child
.
Name
$null
=
$node
.
Nodes
.
Add
(
$childNode
)
## If this node has children or properties, add a placeholder
## node underneath so that the node shows a '+' sign to be
## expanded.
AddPlaceholderIfRequired
$childNode
$childObject
}
}
}
## A function to add a placeholder if required to a node.
## If there are any properties or children for this object, make a temporary
## node with the text "..." so that the node shows a '+' sign to be
## expanded.
function
AddPlaceholderIfRequired
(
$node
,
$object
)
{
if
(
-not
$object
)
{
return
}
if
(
[System.Management.Automation.LanguagePrimitives]
::
GetEnumerator
(
$object
)
-or
@(
$object
.
PSObject
.
Properties
))
{
$null
=
$node
.
Nodes
.
Add
(
(
New-Object
Windows
.
Forms
.
TreeNode
"..."
)
)
}
}
## A function invoked when a node is selected.
function
OnAfterSelect
{
param
(
$Sender
,
$TreeViewEventArgs
)
## Determine the selected node
$nodeSelected
=
$Sender
.
SelectedNode
## Walk through its parents, creating the virtual
## PowerShell syntax to access this property.
$nodePath
=
GetPathForNode
$nodeSelected
## Now, invoke that PowerShell syntax to retrieve
## the value of the property.
$resultObject
=
Invoke-Expression
$nodePath
$outputPane
.
Text
=
$nodePath
## If we got some output, put the object's member
## information in the text box.
if
(
$resultObject
)
{
$members
=
Get-Member
-InputObject
$resultObject
|
Out-String
$outputPane
.
Text
+=
"
`n
"
+
$members
}
}
## A function invoked when the user is about to expand a node
function
OnBeforeExpand
{
param
(
$Sender
,
$TreeViewCancelEventArgs
)
## Determine the selected node
$selectedNode
=
$TreeViewCancelEventArgs
.
Node
## If it has a child node that is the placeholder, clear
## the placeholder node.
if
(
$selectedNode
.
FirstNode
-and
(
$selectedNode
.
FirstNode
.
Text
-eq
"..."
))
{
$selectedNode
.
Nodes
.
Clear
()
}
else
{
return
}
## Walk through its parents, creating the virtual
## PowerShell syntax to access this property.
$nodePath
=
GetPathForNode
$selectedNode
## Now, invoke that PowerShell syntax to retrieve
## the value of the property.
Invoke-Expression
"
`$
resultObject = $nodePath"
## And populate the node with the result object.
PopulateNode
$selectedNode
$resultObject
}
## A function to handle key presses on the tree view.
## In this case, we capture ^C to copy the path of
## the object property that we're currently viewing.
function
OnTreeViewKeyPress
{
param
(
$Sender
,
$KeyPressEventArgs
)
## [Char] 3 = Control-C
if
(
$KeyPressEventArgs
.
KeyChar
-eq
3
)
{
$KeyPressEventArgs
.
Handled
=
$true
## Get the object path, and set it on the clipboard
$node
=
$Sender
.
SelectedNode
$nodePath
=
GetPathForNode
$node
[System.Windows.Forms.Clipboard]
::
SetText
(
$nodePath
)
$form
.
Close
()
}
elseif
(
[System.Windows.Forms.Control]
::
ModifierKeys
-eq
"Control"
)
{
if
(
$KeyPressEventArgs
.
KeyChar
-eq
'+'
)
{
$SCRIPT:currentFontSize
++
UpdateFonts
$SCRIPT:currentFontSize
$KeyPressEventArgs
.
Handled
=
$true
}
elseif
(
$KeyPressEventArgs
.
KeyChar
-eq
'-'
)
{
$SCRIPT:currentFontSize
--
if
(
$SCRIPT:currentFontSize
-lt
1
)
{
$SCRIPT:currentFontSize
=
1
}
UpdateFonts
$SCRIPT:currentFontSize
$KeyPressEventArgs
.
Handled
=
$true
}
}
}
## A function to handle key presses on the form.
## In this case, we handle Ctrl-Plus and Ctrl-Minus
## to adjust font size.
function
OnKeyUp
{
param
(
$Sender
,
$KeyUpEventArgs
)
if
(
[System.Windows.Forms.Control]
::
ModifierKeys
-eq
"Control"
)
{
if
(
$KeyUpEventArgs
.
KeyCode
-in
'Add'
,
'OemPlus'
)
{
$SCRIPT:currentFontSize
++
UpdateFonts
$SCRIPT:currentFontSize
$KeyUpEventArgs
.
Handled
=
$true
}
elseif
(
$KeyUpEventArgs
.
KeyCode
-in
'Subtract'
,
'OemMinus'
)
{
$SCRIPT:currentFontSize
--
if
(
$SCRIPT:currentFontSize
-lt
1
)
{
$SCRIPT:currentFontSize
=
1
}
UpdateFonts
$SCRIPT:currentFontSize
$KeyUpEventArgs
.
Handled
=
$true
}
elseif
(
$KeyUpEventArgs
.
KeyCode
-eq
'D0'
)
{
$SCRIPT:currentFontSize
=
12
UpdateFonts
$SCRIPT:currentFontSize
$KeyUpEventArgs
.
Handled
=
$true
}
}
}
## A function to handle mouse wheel scrolling.
## In this case, we translate Ctrl-Wheel to zoom.
function
OnMouseWheel
{
param
(
$Sender
,
$MouseEventArgs
)
if
(
(
[System.Windows.Forms.Control]
::
ModifierKeys
-eq
"Control"
)
-and
(
$MouseEventArgs
.
Delta
-ne
0
))
{
$SCRIPT:currentFontSize
+=
(
$MouseEventArgs
.
Delta
/
120
)
if
(
$SCRIPT:currentFontSize
-lt
1
)
{
$SCRIPT:currentFontSize
=
1
}
UpdateFonts
$SCRIPT:currentFontSize
$MouseEventArgs
.
Handled
=
$true
}
}
## A function to walk through the parents of a node,
## creating virtual PowerShell syntax to access this property.
function
GetPathForNode
{
param
(
$Node
)
$nodeElements
=
@()
## Go through all the parents, adding them so that
## $nodeElements is in order.
while
(
$Node
)
{
$nodeElements
=
,
$Node
+
$nodeElements
$Node
=
$Node
.
Parent
}
## Now go through the node elements
$nodePath
=
""
foreach
(
$Node
in
$nodeElements
)
{
$nodeName
=
$Node
.
Name
## If it was a node that PowerShell is able to enumerate
## (but not index), wrap it in the array cast operator.
if
(
$nodeName
.
StartsWith
(
'@'
))
{
$nodeName
=
$nodeName
.
Substring
(
1
)
$nodePath
=
"@("
+
$nodePath
+
")"
}
elseif
(
$nodeName
.
StartsWith
(
'['
))
{
## If it's a child index, we don't need to
## add the dot for property access
}
elseif
(
$nodePath
)
{
## Otherwise, we're accessing a property. Add a dot.
$nodePath
+=
"."
}
## Append the node name to the path
$tempNodePath
=
$nodePath
+
$nodeName
if
(
$nodeName
-notmatch
'^[$\[\]a-zA-Z0-9]+$'
)
{
$nodePath
+=
"'"
+
$nodeName
+
"'"
}
else
{
$nodePath
=
$tempNodePath
}
}
## And return the result
$nodePath
}
function
UpdateFonts
{
param
(
$fontSize
)
$treeView
.
Font
=
New-Object
System
.
Drawing
.
Font
"Consolas"
,
$fontSize
$outputPane
.
Font
=
New-Object
System
.
Drawing
.
Font
"Consolas"
,
$fontSize
}
$SCRIPT:currentFontSize
=
12
## Create the TreeView, which will hold our object navigation
## area.
$treeView
=
New-Object
Windows
.
Forms
.
TreeView
$treeView
.
Dock
=
"Top"
$treeView
.
Height
=
500
$treeView
.
PathSeparator
=
"."
$treeView
.
ShowNodeToolTips
=
$true
$treeView
.
Add_AfterSelect
(
{
OnAfterSelect
@args
}
)
$treeView
.
Add_BeforeExpand
(
{
OnBeforeExpand
@args
}
)
$treeView
.
Add_KeyPress
(
{
OnTreeViewKeyPress
@args
}
)
## Create the output pane, which will hold our object
## member information.
$outputPane
=
New-Object
System
.
Windows
.
Forms
.
TextBox
$outputPane
.
Multiline
=
$true
$outputPane
.
WordWrap
=
$false
$outputPane
.
ScrollBars
=
"Both"
$outputPane
.
Dock
=
"Fill"
## Create the root node, which represents the object
## we are trying to show.
$root
=
New-Object
Windows
.
Forms
.
TreeNode
$root
.
ToolTipText
=
$InputObject
.
GetType
()
$root
.
Text
=
$InputObject
$root
.
Name
=
'$'
+
$rootVariableName
$root
.
Expand
()
$null
=
$treeView
.
Nodes
.
Add
(
$root
)
UpdateFonts
$currentFontSize
## And populate the initial information into the tree
## view.
PopulateNode
$root
$InputObject
## Finally, create the main form and show it.
$form
=
New-Object
Windows
.
Forms
.
Form
$form
.
Text
=
"Browsing "
+
$root
.
Text
$form
.
Width
=
1000
$form
.
Height
=
800
$form
.
Controls
.
Add
(
$outputPane
)
$form
.
Controls
.
Add
(
$treeView
)
$form
.
Add_MouseWheel
(
{
OnMouseWheel
@args
}
)
$treeView
.
Add_KeyUp
(
{
OnKeyUp
@args
}
)
$treeView
.
Select
()
$null
=
$form
.
ShowDialog
()
$form
.
Dispose
()
Para más información sobre la ejecución de guiones, consulta la Receta 1.2.
1.27 Grabar una transcripción de tu sesión de Shell
Solución
Para grabar una transcripción de tu sesión de shell, ejecuta el comando Start-Transcript
. Tiene un parámetro opcional -Path
que por defecto elige un nombre de archivo basado en la hora actual del sistema. Por defecto, PowerShell coloca este archivo en el directorio Mis Documentos. Para detener la grabación de la transcripción de tu sistema shell, ejecuta el comando Stop-Transcript
.
Debate
Aunque el cmdlet Get-History
es útil, no registra la salida producida durante tu sesión PowerShell. Para ello, utiliza el cmdlet Start-Transcript
. Además del parámetro Path
descrito anteriormente, el cmdlet Start-Transcript
también admite parámetros que te permiten controlar cómo interactúa PowerShell con el archivo de salida.
Si no especificas un parámetro -Path
, PowerShell genera un nombre de archivo aleatorio por ti. Si quieres procesar este archivo después de detener la transcripción, PowerShell lo añade como nombre de propiedad a la salida de Start-Transcript
o Stop-Transcript
:
PS > $myTranscript = Start-Transcript PS > Stop-Transcript Transcript stopped, output file is D:\Lee\PowerShell_transcript... PS > $myTranscript | fl * -force Path : D:\Lee\PowerShell_transcript.LEE-DESKTOP.kg_Vsm_o.20201217195052.txt Length : 104 PS > $myTranscript.Path D:\Lee\PowerShell_transcript.LEE-DESKTOP.kg_Vsm_o.20201217195052.txt
Las transcripciones de PowerShell comienzan con una cabecera de archivo estándar que incluye la hora, el usuario, el nombre de host y otros elementos útiles. Si especificas el parámetro-IncludeInvocationHeader
de forma interactiva o mediante una política para todo el sistema, PowerShell también incluye un separador entre los comandos para facilitar el análisis automático.
********************** PowerShell transcript start Start time: 20201217190500 Username: ubuntu-20-04\lee Machine: ubuntu-20-04 (Unix 4.19.128.0) Host Application: /opt/microsoft/powershell/7/pwsh.dll Process ID: 1925 OS: Linux 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020 (...) ********************** ********************** Command start time: 20201217190502 ********************** PS /mnt/c/Users/lee> Get-Process NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName ------ ----- ----- ------ -- -- ----------- 0 0.00 5.26 0.16 984 984 bash 0 0.00 0.53 0.02 1 0 init 0 0.00 0.07 0.00 982 982 init 0 0.00 0.08 0.32 983 982 init 0 0.00 96.52 0.64 1925 984 pwsh 0 0.00 3.25 0.00 1873 …73 rsyslogd ********************** Command start time: 20201217190504 ********************** PS /mnt/c/Users/lee> cat /var/log/powershell.log (...)
Además de permitirte grabar transcripciones manualmente, PowerShell también te permite establecer una política de sistema para grabarlas automáticamente. Para más información sobre cómo configurarla, consulta la Receta 18.2.
1.28 Ampliar tu Shell con comandos adicionales
Solución
Si el módulo forma parte de la ruta estándar de módulos de PowerShell, sólo tienes que ejecutar el comando que desees:
Invoke-NewCommand
Si no lo está, utiliza el comando Import-Module
para importar comandos de terceros a tu sesión PowerShell.
Para importar un módulo de un directorio concreto:
Import-Module c:\path\to\module
Para importar un módulo desde un archivo concreto (módulo, script o ensamblaje):
Import-Module c:\path\to\module\file.ext
Debate
PowerShell admite dos conjuntos de comandos que habilitan cmdlets y proveedores adicionales: *-Module
y *-PsSnapin
. Los Snapins eran los paquetes para extensiones en la versión 1 de PowerShell, y se utilizan raramente. Los Snapins sólo admitían extensiones compiladas y tenían onerosos requisitos de instalación.
La versión 2 de PowerShell introdujo módulos que admiten todo lo que admiten los snapins (y más) sin el dolor de instalación asociado. Dicho esto, la versión 2 de PowerShell también requería que recordaras qué módulos contenían qué comandos y que cargaras manualmente esos módulos antes de utilizarlos. Windows incluye ahora miles de comandos en cientos de módulos, lo que hace que depender de la memoria de cada uno sea un enfoque insostenible.
Cualquier versión reciente de PowerShell mejora significativamente la situación al cargar automáticamente los módulos por ti. Internamente, PowerShell mantiene un mapeo de nombres de comandos con el módulo que los contiene. Sólo tienes que empezar a utilizar un comando (que el cmdlet Get-Command
puede ayudarte a descubrir), y PowerShell carga automáticamente el módulo adecuado. Si deseas personalizar este comportamiento de carga automática, puedes utilizar la variable de preferencia $PSModuleAutoLoadingPreference
.
Cuando PowerShell importa un módulo con un nombre determinado, busca en todos los directorios enumerados en la variable de entorno PSModulePath
, buscando el primer módulo que contenga los subdirectorios que coincidan con el nombre que especifiques. Dentro de esos directorios, busca el módulo (*.psd1
, *.psm1
, y *.dll
) con el mismo nombre y lo carga.
Cuando instalas un módulo en tu propio sistema, el lugar más habitual para colocarlo es en el directorio PowerShell\Modules de tu directorio Mis Documentos. En Windows PowerShell, esta ubicación será WindowsPowerShell\Modules. Para que PowerShell busque los módulos en otro directorio, añádelo a tu variable de entorno personal PSModulePath
, igual que añadirías un directorio Herramientas a tu ruta personal.
Para más información sobre la gestión de las rutas del sistema, consulta la Receta 16.2.
Si quieres cargar un módulo de un directorio que no está en PSModulePath
, puedes proporcionar el nombre completo del directorio y el nombre del módulo al comando Import-Module
. Por ejemplo, para un módulo llamado Test
, utiliza Import-Module c:\path\to\Test
. Al igual que con la carga de módulos por nombre, PowerShell busca en c:\temp\path\to
un módulo(*.psd1, *.psm1 o *.dll) llamado Test
y lo carga.
Si conoces el archivo específico del módulo que quieres cargar, también puedes especificar la ruta completa a ese módulo.
Si quieres encontrar comandos adicionales, consulta la Receta 1.29.
1.29 Buscar e instalar scripts y módulos PowerShell adicionales
Solución
Utiliza el comando Find-Module
para encontrar módulos interesantes:
PS > Find-Module *Cookbook* | Format-List Name : PowerShellCookbook Version : 1.3.6 Type : Module Description : Sample scripts from the PowerShell Cookbook Author : Lee Holmes (...)
A continuación, utiliza Install-Module
para añadirlos a tu sistema.
Install-Module
PowerShellCookbook
-Scope
CurrentUser
Del mismo modo, utiliza los comandos Find-Script
y Install-Script
si el elemento se ha publicado como un script independiente. Si aún no lo has hecho en tu máquina, asegúrate de añadir My Documents\PowerShell\Scripts
a la ruta de tu sistema. Para más información sobre cómo modificar la ruta de tu sistema, consulta la Receta 16.2.
PS > Find-Script Get-WordCluster | Install-Script -Scope CurrentUser PS > Get-WordCluster -Count 3 "Hello","World","Jello", "Mellow","Jealous","Wordy","Sword" Representative Items -------------- ----- Wordd {World, Wordy, Sword} Jealou {Jello, Jealous} Hellow {Hello, Mellow}
Debate
La Galería PowerShell de es el centro mundial para publicar y compartir scripts y módulos PowerShell. Contiene miles de módulos: versiones corporativas oficiales de Microsoft y muchas otras empresas, proyectos populares de la comunidad como el módulo DbaTools para la gestión de SQL, y divertidos caprichos como OutConsolePicture para mostrar imágenes como gráficos ANSI.
La interfaz web de la Galería PowerShell te permite buscar, examinar y explorar, pero, por supuesto, no es así como se utiliza a través dePowerShell.
En PowerShell, los comandos Find-Module
y Install-Module
te permiten interactuar con la Galería PowerShell e instalar módulos desde ella. Puedes encontrar módulos por nombre, etiquetas e incluso por las capacidades del rol de Administración Suficiente.
La primera vez que intentes instalar un módulo de la Galería PowerShell, PowerShell te mostrará una advertencia:
PS > Install-Module someModule -Scope CurrentUser Untrusted repository You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from 'PSGallery'? [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "N"):
Al igual que en todos los demás repositorios de código compartido, no hay restricciones sobre quién puede publicar en la Galería PowerShell o qué puede publicar. Si se denuncia un módulo a través de los mecanismos de denuncia de abusos y se considera malicioso o contrario a las Condiciones de servicio de la galería, se eliminará, por supuesto. Pero aparte deeso, no debes considerar que la galería ha sido examinada, aprobada o que es implícitamente de confianza. Para reconocer esto y eliminar la advertencia de futuras instalaciones del módulo, puedes declarar que la Galería PowerShell es de confianza en tu máquina:
Set-PSRepository
-Name
PSGallery
-InstallationPolicy
Trusted
Además de la Galería PowerShell pública, PowerShell también puede comunicarse con galerías privadas (¡incluidos los archivos compartidos!). PowerShell utiliza el protocolo NuGet. Para más información sobre cómo crear una Galería PowerShell privada, consulta la documentación de la Galería PowerShell.
1.30 Usar comandos desde shells personalizados
Debate
Como se describe en la Receta 1.28, los módulos PowerShell y los snapins son las dos formas en que terceros pueden distribuir y añadir comandos PowerShell adicionales. Los productos que proporcionan versiones personalizadas de la consola PowerShell lo hacen lanzando PowerShell con uno de tres parámetros:
-
-PSConsoleFile
, para cargar un archivo de consola que proporciona una lista de snapins a cargar. -
-Command
para especificar en un comando de arranque inicial (que luego carga un snapin o módulo) -
-File
para especificar en un script de arranque inicial (que luego carga un snapin o módulo)
Independientemente de cuál se utilice, puedes examinar el conjunto resultante de extensiones cargadas para ver cuáles puedes importar a tus otras sesiones PowerShell.
Detectar snapins cargados
El comando Get-PsSnapin
devuelve todos los snapins cargados en la sesión actual. Siempre devuelve el conjunto de snapins centrales de PowerShell, pero también devolverá cualquier snapin adicional cargado por el entorno personalizado. Por ejemplo, si el nombre de un snapin que reconoces es Product.Feature.Commands
puedes cargarlo en futuras sesiones de PowerShell escribiendo Add-PsSnapin
Product.Feature.Commands
. Para automatizar esto, añade el comando a tu perfil PowerShell.
Si no estás seguro de qué snapin cargar, también puedes utilizar el comando Get-Command
para descubrir qué snapin define un comando concreto:
PS > Get-Command Get-Counter | Select PsSnapin PSSnapIn -------- Microsoft.PowerShell.Diagnostics
Detectar módulos cargados
Al igual que el comando Get-PsSnapin
, el comando Get-Module
devuelve todos los módulos cargados en la sesión actual. Devuelve cualquier módulo que hayas añadido hasta el momento en esa sesión, pero también devolverá cualquier módulo adicional cargado por el entorno personalizado. Por ejemplo, si el nombre de un módulo que reconoces es ProductModule
puedes cargarlo en futuras sesiones PowerShell escribiendo Import-Module
ProductModule
. Para automatizar esto, añade el comando a tu perfil PowerShell.
Si no estás seguro de qué módulo debes cargar, también puedes utilizar el comando Get-Command
para descubrir qué módulo define un comando concreto:
PS > Get-Command Start-BitsTransfer | Select Module Module ------ BitsTransfer
1.31 Guardar estado entre sesiones
Debate
PowerShell proporciona un fácil acceso basado en scripts a una amplia variedad de eventos del sistema, del motor y de otros tipos. Puedes registrarte para recibir notificaciones de estos eventos e incluso procesar automáticamente cualquiera de ellos. En el siguiente ejemplo, nos suscribimos al evento llamado PowerShell.Exiting
. PowerShell genera este evento cuando cierras una sesión.
En puedes utilizar este evento para guardar y restaurar estados, variables y cualquier otra cosa que necesites. Aunque el módulo PSReadLine
ya guarda automáticamente tu historial de comandos entre sesiones, a efectos de demostración podemos implementar una funcionalidad similar a través del evento PowerShell.Exiting
. Colocarías una llamada a Enable-HistoryPersistence
en tu perfil(Ejemplo 1-19).
Ejemplo 1-19. Activar-HistoriaPersistencia.ps1
##############################################################################
##
## Enable-HistoryPersistence
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Reloads any previously saved command history, and registers for the
PowerShell.Exiting engine event to save new history when the shell
exits.
#>
Set-StrictMode
-Version
3
## Load our previous history
$GLOBAL:maximumHistoryCount
=
32767
$historyFile
=
(
Join-Path
(
Split-Path
$profile
)
"commandHistory.clixml"
)
if
(
Test-Path
$historyFile
)
{
Import-CliXml
$historyFile
|
Add-History
}
## Register for the engine shutdown event
$null
=
Register-EngineEvent
-SourceIdentifier
`
(
[System.Management.Automation.PsEngineEvent]
::
Exiting
)
-Action
{
## Save our history
$historyFile
=
(
Join-Path
(
Split-Path
$profile
)
"commandHistory.clixml"
)
$maximumHistoryCount
=
1kb
## Get the previous history items
$oldEntries
=
@()
if
(
Test-Path
$historyFile
)
{
$oldEntries
=
Import-CliXml
$historyFile
-ErrorAction
SilentlyContinue
}
## And merge them with our changes
$currentEntries
=
Get-History
-Count
$maximumHistoryCount
$additions
=
Compare-Object
$oldEntries
$currentEntries
`
-Property
CommandLine
|
Where-Object
{
$_
.
SideIndicator
-eq
"=>"
}
|
Foreach
-Object
{
$_
.
CommandLine
}
$newEntries
=
$currentEntries
|
?
{
$additions
-contains
$_
.
CommandLine
}
## Keep only unique command lines. First sort by CommandLine in
## descending order (so that we keep the newest entries,) and then
## re-sort by StartExecutionTime.
$history
=
@(
$oldEntries
+
$newEntries
)
|
Sort
-Unique
-Descending
CommandLine
|
Sort
StartExecutionTime
## Finally, keep the last 100
Remove-Item
$historyFile
$history
|
Select
-Last
100
|
Export-CliXml
$historyFile
}
Este script podría hacer cualquier cosa, pero en este ejemplo hacemos que guarde nuestro historial de comandos y lo restaure cuando iniciemos PowerShell. ¿Por qué querríamos hacer esto? Bueno, con un búfer de historial rico, podemos encontrar y reutilizar más fácilmente los comandos que hemos ejecutado anteriormente. Para ver dos ejemplos de cómo hacerlo, consulta las Recetas 1.21 y 1.23.
Enable-HistoryPersistence
realiza dos acciones principales. Primero, cargamos nuestro historial de comandos almacenado (si existe). Después, registramos una acción automática que se procesará cada vez que el motor genere su evento PowerShell.Exiting
. La acción en sí es relativamente sencilla, aunque exportar nuestro nuevo historial requiere un poco de delicadeza. Si tienes varias sesiones abiertas al mismo tiempo, cada una de ellas actualizará el archivo del historial guardado cuando salga. Como no queremos sobrescribir el historial guardado por los otros intérpretes de comandos, primero recargamos el historial desde el disco y lo combinamos con el historial del intérprete de comandos actual.
Una vez que tenemos la lista combinada de líneas de comandos, las ordenamos y elegimos las únicas antes de almacenarlas de nuevo en el archivo.
Para más información sobre cómo trabajar con eventos del motor PowerShell, consulta la Receta 31.2.
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.