Scripts sencillos y funciones
Convirtiendo comandos en scripts
El proceso que vamos a seguir para convertir un conjunto de cmdlets, en una herramienta que podamos reutilizar, es:
- Hacer que funcione en la consola
- Convertir en script
- Parametrizar
Abrimos ISE (como Administrador). Ejecutamos el cmdlet que queremos en la consola:
1 PS> Get-WMIObject -Class win32_bios -computerName dc, vm1, vm2
Si lo ejecutamos y funciona, lo más conveniente es guardarlo en un fichero bios.ps1, para cuando lo volvamos a necesitar.
Una vez que hayamos creado el script, lo ejecutamos desde la línea de comandos para comprobar que funciona:
1 PS> .\bios.ps1
Sin embargo, ¿qué pasa si necesitamos obtener la informacion de diferentes equipos a los que hemos especificado en el script? Tenemos que buscar el script, abrirlo, identificar qué es lo que hay que cambiar y cambiarlo, volviendo a guardar el script para poder ejecutarlo.
Obviamente, esta no es la opción mas productiva.
Vamos a ver cual es el proceso a seguir partiendo del siguiente comando (guardado como dinfo.ps1, que tampoco es la mejor manera de llamar al script):
1 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= 'c:'" -ComputerName\
2 localhost
A partir de este script que funciona, vamos a ir refinando y formalizando el script hasta convertirlo en una herramienta que otros puedan usar para solucionar problemas.
Si ejecutamos el código anterior:
1 DeviceID : C:
2 DriveType : 3
3 ProviderName :
4 FreeSpace : 11966959616
5 Size : 26317156352
6 VolumeName : System
En primer lugar, vamos a modificar la información que se muestra al ejecutar el comando para adecuarlo a nuestras necesidades.
Sólo queremos mostrar el tamaño y el espacio libre del disco C:, así que seleccionamos:
1 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= 'c:'" -ComputerName\
2 localhost |
3 select pscomputername, DeviceID,
4 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
5 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
Además de obtener las propiedades directamente del objecto devuelto por Get-WMIObject, usamos las propiedades calculadas o propiedades dinámicas para modificar la información obtenida del objeto y generar una nueva al vuelo. La primera propiedad calculada es el tamaño del disco: Name es el nombre de la propiedad y Expression es una expresión que, en nuestro caso, convierte el valor de $_.size en gigabytes y después lo redondea convirtiéndolo a un entero.
En general, en estas propiedades calculadas suelen utilizarse los nombres cortos n (para Name) y e (para Expression).
El resultado es mucho más agradable y profesional:
1 PSComputerName deviceid Size(GB) Free(GB)
2 -------------- -------- -------- --------
3 WIN-DFHSBDD3VL1 C: 25 11
Eligiendo variables para parametrizar los scripts
El siguiente paso es identificar qué partes del script pueden cambiar si necesitamos volver a ejecutar el script. Por ejemplo, podemos estar interesados en el espacio libre de otro disco diferente al c: o de otro equipo que no sea el equipo local…
Así que sustituimos estos valores por variables. Aprovechamos para especificar el tipo de estas variables:
1 [string]$Drive = 'C:'
2 [string]$ComputerName = 'localhost'
3
4 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Computer\
5 Name $ComputerName |
6 select pscomputername, DeviceID,
7 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
8 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
Ejecutamos de nuevo el script para comprobar que sigue funcionando después de las modificaciones introducidas.
Hemos avanzado un paso más, ya que ahora, si alguien tiene que modificar el script, como mínimo tiene todas las variables al principio, lo que facilitará su tarea.
Añadiendo parámetros al script
Vamos a seguir adelante. La siguiente modificación es rodear las variables del script con el keyword param(...) (con las variables separadas por comas):
1 param(
2 [string]$Drive = 'C:',
3 [string]$ComputerName = 'localhost'
4 )
5 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Computer\
6 Name $ComputerName |
7 select pscomputername, DeviceID,
8 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
9 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
De nuevo, si ejecutamos el script, todo sigue funcionando. Si verificamos el funcionamiento desde la consola de ISE, observamos que IntelliSense nos muestra una lista de los parámetros disponibles para nuestro script:
Hasta ahora, la ejecución del script estaba utilizando los valores especificados en la declaración de las variables como valores por defecto.
Sin embargo, como PowerShell ha identificado ComputerName y Drive como parámetros, podemos proporcionar valores diferentes desde la línea de comandos.
En el siguiente ejemplo, cambiamos el nombre del equipo donde queremos evaluar el espacio libre, aunque dejamos la opción por defecto para el parámetro Drive:
1 PS> .\dinfo.ps1 -ComputerName dc
El siguiente paso es agregar una nueva línea al script: [CmdletBinding()]:
1 [CmdletBinding()]
2 param(
3 [string]$Drive = 'C:',
4 [string]$ComputerName = 'localhost'
5 )
6 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Computer\
7 Name $ComputerName |
8 select pscomputername, DeviceID,
9 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
10 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
Con esta única línea, hemos transformado un script en un cmdlet. Al añadir [CmdletBinding()] PowerShell agrega un montón de funcionalidad al script, como veremos a continuación.
Sin embargo, para ejecutar este nuevo cmdlet, sigue siendo necesario escribir .\dinfo.ps1, lo que lo diferencia de un cmdlet “normal”.
Funciones
El siguiente paso es convertir el script en una función; para ello, rodeamos el código con la palabra clave function:
1 function dinfo {
2 [CmdletBinding()]
3 param(
4 [string]$Drive = 'C:',
5 [string]$ComputerName = 'localhost'
6 )
7 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Comp\
8 uterName $ComputerName |
9 select pscomputername, DeviceID,
10 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
11 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
12 }
Si ejecutamos en ISE (mediante F5) la función, funciona perfectamente. En la consola de ISE ya podemos ejecutar dinfo para obtener la información del disco c: de la máquina local o de cualquier máquina remota proporcionando el nombre del equipo remoto.
Pero si intentamos repetir este proceso en desde una consola de PowerShell, obtenemos un error:
1 PS> .\dinfo.ps1
2 PS> dinfo
3 dinfo : The term 'dinfo' is not recognized as the name of a cmdlet, function,\
4 script file, or operable program. Check the spelling of the name, or if a pa\
5 th was included, verify that the path is correct and try again.
## Ejecutando tus scripts/funciones
El problema está en que, en la consola, al ejecutar una función, se carga en memoria. Pero al finalizar la ejecución, se descarga de la memoria y por tanto, no está disponible en la consola.
Por tanto, cuando estemos testeando funciones, lo que podemos hacer es utilizar la técnica del dot sourcing:
1 PS> . .\dinfo.ps1
2 PS> dinfo
En este caso, la función sí que estaría disponible, ya que con el dot sourcing de la función estamos indicando a PowerShell que no la descargue de la consola.
Es importante recordar que cada vez que modificamos la función o funciones en el fichero, debemos “dot sourcearlo” de nuevo (o estaremos usando la versión anterior, sin los últimos cambios).
Por tanto, ya estamos cerca de tener un cmdlet creado por nosotros mismos.
Antes de seguir, vamos a renombrar la función siguiendo el estilo recomendado para los cmdlets en la forma verbo-nombre:
1 function Get-DiskInfo {
2 [CmdletBinding()]
3 param(
4 [string]$Drive = 'C:',
5 [string]$ComputerName = 'localhost'
6 )
7 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Comp\
8 uterName $ComputerName |
9 select pscomputername, DeviceID,
10 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
11 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
12 }
Seguimos refinando el script y ahora, gracias a [CmdletBinding()] podemos hacer que alguno (o todos los parámetros) sean obligatorios. Para ello, precedemos el parámetro con [Parameter(Mandatory=$true)]:
1 function Get-DiskInfo {
2 [CmdletBinding()]
3 param(
4 [string]$Drive,
5 [Parameter(Mandatory=$true)]
6 [string]$ComputerName
7 )
8 Get-WmiObject -Class win32_logicaldisk -Filter "deviceid= '$Drive'" -Comp\
9 uterName $ComputerName |
10 select pscomputername, DeviceID,
11 @{Name='Size(GB)'; Expression={$_.size / 1gb -as [int]} },
12 @{n='Free(GB)'; e={$_.FreeSpace / 1gb -as [int]} }
13 }
Al indicar que el parámetro es obligatorio, al intentar ejecutar el script sin indicarlo, PowerShell pregunta por el valor del parámetro:
1 PS> Get-DiskInfo
2 cmdlet Get-DiskInfo at command pipeline position 1
3 Supply values for the following parameters:
4 ComputerName: localhost
5
6 PSComputerName DeviceID Size(GB) Free(GB)
7 -------------- -------- -------- --------
8 WIN-DFHSBDD3VL1 C: 25 11
[CmdletBinding] también proporciona otros parámetros comunes, como la posibilidad de que la salida del cmdlet se guarde en una variable:
1 PS> Get-DiskInfo -ComputerName localhost -OutVariable $resultado
2 PS> $resultado
3 PSComputerName DeviceID Size(GB) Free(GB)
4 -------------- -------- -------- --------
5 WIN-DFHSBDD3VL1 C: 25 11
Aunque hemos explicado cómo guardar el contenido de un script en memoria para disponer de las funciones definidas en él desde la consola, ¿cómo hacemos para eliminar estas funciones?.
La respuesta es accediendo al PSDrive de funciones y eliminarla.
1 PS>Get-PSDrive
2
3 Name Used (GB) Free (GB) Provider Root
4 ---- --------- --------- -------- ----
5 Alias Alias
6 C 13,36 11,14 FileSystem C:\
7 Cert Certificate \
8 D FileSystem D:\
9 Env Environment
10 Function Function
11 HKCU Registry HKEY_CURRENT_USER
12 HKLM Registry HKEY_LOCAL_MACHINE
13 Variable Variable
14 WSMan WSMan
Ejecutamos dir function sobre este PSDrive y obtenemos una lista de las funciones disponibles; entre ellas, observamos que tenemos la función Get-DiskInfo que hemos almacenado mediante el dot sourcing.
Para eliminar la función, usamos Remove-Item function:\Get-Diskinfo. El problema es que no podemos estar seguros de que se haya eliminado completamente, por lo que el dot sourcing sólo debe usarse para realizar pruebas; la manera correcta de exportar las funciones será mediante los módulos.
En cualquier caso, para limpiar la memoria de la consola, lo más sencillo es cerrar la ventana y abrir una nueva o, si estamos trabajando con ISE, crear una nueva pestaña (de hecho, un nuevo workspace).
Siguiendo adelante con el script, una vez estamos satisfechos con el cmdlet Get-DiskInfo, probablemente añadiremos nuevas funciones al fichero .\diskinfo.ps1.
Mediante la técnica del dot sourcing tendremos disponibles las funciones definidas en el fichero para utilizarlas en los scripts, construyendo un conjunto de herramientas que nos ayuden a solucionar los problemas del día a día.