Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los comandos de una línea de PowerShell y los scripts que tienen que modificarse a menudo son buenos candidatos para convertirlos en funciones reutilizables.
Escriba funciones siempre que sea posible porque están más orientadas a herramientas. Puede agregar las funciones a un módulo de script, colocar ese módulo en una ubicación definida en la $env:PSModulePath
y llamar a las funciones sin necesidad de localizar dónde guardó las funciones. Con el módulo PowerShellGet, es fácil compartir sus módulos de PowerShell en un repositorio de NuGet.
PowerShellGet viene incluido con PowerShell versión 5.0 y posteriores. También está disponible como descarga independiente de la versión 3.0 de PowerShell y de versiones posteriores.
No compliques demasiado las cosas. Elija la manera más directa de hacer una tarea y simplifique los procesos. Evite los alias y los parámetros posicionales en todo código que reutilice. Dé formato al código para facilitar la lectura. No codifique los valores; use parámetros y variables. No escriba código innecesario, aunque no perjudique a nada. Agrega una complejidad innecesaria. La atención a los detalles es crucial al escribir cualquier código en PowerShell.
Nomenclatura
Al asignar nombres a las funciones de PowerShell, use un nombre en Pascal case con un verbo aprobado y un sustantivo en singular. Para obtener una lista de verbos aprobados en PowerShell, ejecute Get-Verb
. El siguiente ejemplo ordena los resultados de Get-Verb
por la propiedad Verbo.
Get-Verb | Sort-Object -Property Verb
La propiedad Group le da una idea de cómo se deben usar los verbos.
Verb Group
---- -----
Add Common
Approve Lifecycle
Assert Lifecycle
Backup Data
Block Security
Checkpoint Data
Clear Common
Close Common
Compare Data
Complete Lifecycle
Compress Data
Confirm Lifecycle
Connect Communications
Convert Data
ConvertFrom Data
ConvertTo Data
Copy Common
Debug Diagnostic
Deny Lifecycle
Disable Lifecycle
Disconnect Communications
Dismount Data
Edit Data
Enable Lifecycle
Enter Common
Exit Common
Expand Data
Export Data
Find Common
Format Common
Get Common
Grant Security
Group Data
Hide Common
Import Data
Initialize Data
Install Lifecycle
Invoke Lifecycle
Join Common
Limit Data
Lock Common
Measure Diagnostic
Merge Data
Mount Data
Move Common
New Common
Open Common
Optimize Common
Out Data
Ping Diagnostic
Pop Common
Protect Security
Publish Data
Push Common
Read Communications
Receive Communications
Redo Common
Register Lifecycle
Remove Common
Rename Common
Repair Diagnostic
Request Lifecycle
Reset Common
Resize Common
Resolve Diagnostic
Restart Lifecycle
Restore Data
Resume Lifecycle
Revoke Security
Save Data
Search Common
Select Common
Send Communications
Set Common
Show Common
Skip Common
Split Common
Start Lifecycle
Step Common
Stop Lifecycle
Submit Lifecycle
Suspend Lifecycle
Switch Common
Sync Data
Test Diagnostic
Trace Diagnostic
Unblock Security
Undo Common
Uninstall Lifecycle
Unlock Common
Unprotect Security
Unpublish Data
Unregister Lifecycle
Update Data
Use Other
Wait Lifecycle
Watch Common
Write Communications
Es importante utilizar un verbo aprobado para sus funciones de PowerShell. Los módulos que contienen funciones con verbos no aprobados generan un mensaje de advertencia cuando se importan en una sesión de PowerShell. Ese mensaje de advertencia hará que las funciones no parezcan profesionales. Los verbos no aprobados también limitan la detectabilidad de las funciones.
Una función simple
Una función en PowerShell se declara con la palabra clave function seguida del nombre de la función y luego una llave de apertura y cierre ({ }
). El código ejecutado por la función está contenido dentro de esas llaves.
function Get-Version {
$PSVersionTable.PSVersion
}
La función que se muestra en el ejemplo siguiente es un ejemplo sencillo que devuelve la versión de PowerShell.
Get-Version
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
Cuando se usa un nombre genérico para las funciones, como Get-Version
, podría provocar conflictos de nomenclatura. Los comandos predeterminados agregados en el futuro o los comandos que otros podrían escribir podrían entrar en conflicto con ellos. Anteponga un prefijo a la parte del sustantivo de los nombres de las funciones para evitar conflictos de nombres. Por ejemplo: <ApprovedVerb>-<Prefix><SingularNoun>
.
En el ejemplo siguiente se usa el prefijo PS
.
function Get-PSVersion {
$PSVersionTable.PSVersion
}
Más allá del nombre, esta función es idéntica a la anterior.
Get-PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
Puede seguir teniendo un conflicto de nombres aunque haya agregado un prefijo al nombre. Me gusta añadir como prefijo a los nombres de mis funciones mis iniciales. Desarrolle un estándar y cúmplalo.
function Get-MrPSVersion {
$PSVersionTable.PSVersion
}
Esta función no es diferente de las dos anteriores, excepto por el uso de un nombre más único para intentar evitar conflictos de nomenclatura con otros comandos de PowerShell.
Get-MrPSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 693
Una vez cargadas en la memoria, puede ver las funciones en el PSDrive Función.
Get-ChildItem -Path Function:\Get-*Version
CommandType Name Version
----------- ---- -------
Function Get-Version
Function Get-PSVersion
Function Get-MrPSVersion
Si quiere quitar estas funciones de la sesión actual, tendrá que quitarlas del PSDrive Función o cerrar PowerShell y volver a abrirlo.
Get-ChildItem -Path Function:\Get-*Version | Remove-Item
Compruebe que las funciones se hayan quitado bien.
Get-ChildItem -Path Function:\Get-*Version
Si las funciones se cargaron como parte de un módulo, puede descargar dicho módulo para quitarlas.
Remove-Module -Name <ModuleName>
El cmdlet Remove-Module
quita los módulos de PowerShell de la memoria en la sesión actual de PowerShell. No los quita del sistema ni del disco.
Parámetros
No asigne valores de forma estática. En su lugar, use parámetros y variables. Al asignar nombres a los parámetros, use el mismo nombre que los cmdlets predeterminados para los nombres de parámetro siempre que sea posible.
En la función siguiente, observe que he usado ComputerName y no Computer, ServerName o Host para el nombre del parámetro. El uso de ComputerName estandariza el nombre del parámetro para que coincida con el nombre del parámetro y el caso como los cmdlets predeterminados.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
La siguiente función consulta todos los comandos del sistema y devuelve el número con nombres de parámetro específicos.
function Get-MrParameterCount {
param (
[string[]]$ParameterName
)
foreach ($Parameter in $ParameterName) {
$Results = Get-Command -ParameterName $Parameter -ErrorAction SilentlyContinue
[pscustomobject]@{
ParameterName = $Parameter
NumberOfCmdlets = $Results.Count
}
}
}
Como puede ver en los resultados siguientes, hay 39 comandos que tienen un parámetro ComputerName. No hay ningún comando que tenga parámetros como Computer, ServerName, Host o Machine.
Get-MrParameterCount -ParameterName ComputerName, Computer, ServerName,
Host, Machine
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName 39
Computer 0
ServerName 0
Host 0
Machine 0
Utilice las mismas mayúsculas y minúsculas para los nombres de parámetros que para los cmdlets predeterminados. Por ejemplo, use ComputerName
, no computername
. Este esquema de nomenclatura ayuda a las personas familiarizadas con PowerShell a descubrir tus funciones y a proporcionar una experiencia similar a los cmdlets predeterminados.
La instrucción param
permite definir uno o más parámetros. Una coma (,
) separa las definiciones de parámetros. Para obtener más información, consulte about_Functions_Advanced_Parameters.
Funciones avanzadas
Convertir una función en una avanzada en PowerShell es sencillo. Una de las diferencias entre una función y una función avanzada es que las avanzadas tienen parámetros comunes que se agregan automáticamente. Los parámetros comunes incluyen parámetros como Verbose y Debug.
Empiece con la función Test-MrParameter
, que se usó en la sección anterior.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
Hay un par de maneras diferentes de ver los parámetros comunes. Una es visualizando la sintaxis con Get-Command
.
Get-Command -Name Test-MrParameter -Syntax
Observe que la función Test-MrParameter
no tiene ningún parámetro común.
Test-MrParameter [[-ComputerName] <Object>]
Otra es profundizar en la propiedad parámetros de Get-Command
.
(Get-Command -Name Test-MrParameter).Parameters.Keys
ComputerName
Agregue el atributo CmdletBinding
para convertir la función en una avanzada.
function Test-MrCmdletBinding {
[CmdletBinding()] # Turns a regular function into an advanced function
param (
$ComputerName
)
Write-Output $ComputerName
}
Al especificar CmdletBinding
, los parámetros comunes se agregan automáticamente.
CmdletBinding
requiere un bloque param
, pero el bloque param
puede estar vacío.
Get-Command -Name Test-MrCmdletBinding -Syntax
Test-MrCmdletBinding [[-ComputerName] <Object>] [<CommonParameters>]
Al profundizar en la propiedad parámetros de Get-Command
se muestran los nombres reales de los parámetros, incluidos los comunes.
(Get-Command -Name Test-MrCmdletBinding).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
SoportaDebeProcesar
El atributo SupportsShouldProcess
agrega WhatIf y Confirm, que son los parámetros de mitigación de riesgos. Estos parámetros solo son necesarios para los comandos que hacen cambios.
function Test-MrSupportsShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param (
$ComputerName
)
Write-Output $ComputerName
}
Observe que ahora hay parámetros WhatIf y Confirm.
Get-Command -Name Test-MrSupportsShouldProcess -Syntax
Test-MrSupportsShouldProcess [[-ComputerName] <Object>] [-WhatIf] [-Confirm]
[<CommonParameters>]
Una vez más, también puede usar Get-Command
para devolver una lista de los nombres reales de los parámetros, incluidos los comunes, junto con WhatIf y Confirm.
(Get-Command -Name Test-MrSupportsShouldProcess).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
WhatIf
Confirm
Validación de parámetros
Valide la entrada bien al principio. No permita que su código continúe por un camino cuando no pueda completarse sin una entrada válida.
Especifique siempre un tipo de datos para las variables usadas para los parámetros. En el ejemplo siguiente, se especifica String como tipo de datos para el parámetro ComputerName. Esta validación lo limita a permitir que solo se especifique un solo nombre de equipo para el parámetro ComputerName.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[string]$ComputerName
)
Write-Output $ComputerName
}
Se genera un error si se especifica más de un nombre de equipo.
Test-MrParameterValidation -ComputerName Server01, Server02
Test-MrParameterValidation : Cannot process argument transformation on
parameter 'ComputerName'. Cannot convert value to type System.String.
At line:1 char:42
+ Test-MrParameterValidation -ComputerName Server01, Server02
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-MrParameterValidation]
, ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-MrP
arameterValidation
El problema con la definición actual es que es válido omitir el valor del parámetro ComputerName, pero se requiere un valor para que la función se complete correctamente. Este escenario es donde el atributo de parámetro Mandatory
es beneficioso.
La sintaxis usada en el ejemplo siguiente es compatible con la versión 3.0 de PowerShell y con versiones posteriores.
Podría especificarse [Parameter(Mandatory=$true)]
para que la función sea compatible con la versión 2.0 de PowerShell o posteriores.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ComputerName
)
Write-Output $ComputerName
}
Ahora que se requiere ComputerName, si no se especifica uno, la función solicita uno.
Test-MrParameterValidation
cmdlet Test-MrParameterValidation at command pipeline position 1
Supply values for the following parameters:
ComputerName:
Si desea permitir más de un valor para el parámetro ComputerName, use el tipo de datos String, pero agregue corchetes ([]
) al tipo de datos para permitir una matriz de cadenas.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string[]]$ComputerName
)
Write-Output $ComputerName
}
Es posible que quiera especificar un valor predeterminado para el parámetro ComputerName, si no se especifica uno.
El problema es que los valores predeterminados no se pueden usar con parámetros obligatorios. En su lugar, use el atributo de validación de parámetros ValidateNotNullOrEmpty
con un valor predeterminado.
Intente no usar valores estáticos, ni siquiera cuando establezca un valor predeterminado. En el ejemplo siguiente, se usa $env:COMPUTERNAME
como valor predeterminado, que se traduce automáticamente al nombre del equipo local si no se proporciona un valor.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Write-Output $ComputerName
}
Salida detallada
Los comentarios insertados son útiles si está escribiendo código complejo, pero los usuarios no los ven a menos que examinen el código.
La función del ejemplo siguiente tiene un comentario en línea en el bucle foreach
. Aunque es posible que este comentario concreto no sea tan difícil de encontrar, imagínese que la función incluyese cientos de líneas de código.
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
#Attempting to perform an action on $Computer <<-- Don't use
#inline comments like this, use write verbose instead.
Write-Output $Computer
}
}
Una mejor opción es usar Write-Verbose
en lugar de comentarios en línea.
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
Write-Verbose -Message "Attempting to perform an action on $Computer"
Write-Output $Computer
}
}
La salida detallada no se muestra cuando se llama a la función sin el parámetro Verbose.
Test-MrVerboseOutput -ComputerName Server01, Server02
La salida detallada se muestra cuando se llama a la función con el parámetro Verbose.
Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose
Entrada de pipeline
El código adicional es necesario cuando desea que la función acepte la entrada de canalización. Como se ha mencionado anteriormente en este libro, los comandos pueden aceptar la entrada de canalización por valor (por tipo) o por nombre de propiedad. Puede escribir sus funciones de la misma manera que los comandos nativos, para que acepten uno o ambos tipos de entrada.
Para aceptar la entrada de canalización por valor, especifique el atributo de parámetro ValueFromPipeline
para ese parámetro específico. Solo se puede aceptar la entrada por valor de un parámetro de cada tipo de datos. Si tiene dos parámetros que aceptan entradas de cadena, solo uno de ellos puede aceptar entradas de pipeline por valor. Si especificó como valor para ambos parámetros de cadena, la entrada no sabría a qué parámetro vincular. Este escenario es otra de las razones por las que llamo a este tipo de entrada por tipo en lugar de por valor.
La entrada de pipeline se recibe de elemento en elemento, de forma similar a como se administran los elementos en un bucle foreach
.
Se requiere un bloque process
para procesar cada elemento si la función acepta una matriz como entrada. Si la función solo acepta un valor único como entrada, no es necesario un bloque de process
, pero se recomienda para la coherencia.
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline)]
[string[]]$ComputerName
)
process {
Write-Output $ComputerName
}
}
Aceptar la entrada del pipeline por nombre de propiedad es similar, excepto que se especifica ValueFromPipelineByPropertyName
con el atributo de parámetro, y se puede especificar para cualquier número de parámetros independientemente del tipo de datos. La clave es que la salida del comando por el que se canaliza debe tener un nombre de propiedad que coincida con el nombre del parámetro o un alias de un parámetro de tu función.
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
Write-Output $ComputerName
}
}
los bloques begin
y end
son opcionales. Se especifica begin
antes que el bloque process
y se usa para realizar cualquier trabajo inicial antes de recibir los elementos de la canalización. Los valores que se canalizan no son accesibles en el bloque begin
. El bloque end
se especifica después del bloque process
y se usa para la limpieza tras procesar todos los elementos canalizados.
Control de errores
La función que se muestra en el siguiente ejemplo genera una excepción no controlada cuando no se puede establecer contacto con un equipo.
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
Test-WSMan -ComputerName $Computer
}
}
}
Hay un par de formas de controlar los errores en PowerShell.
Try/Catch
es la forma más moderna de controlar los errores.
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Computer"
}
}
}
}
Aunque la función que se muestra en el ejemplo anterior usa el control de errores, genera una excepción no controlada porque el comando no genera un error de terminación. Solo se detectan los errores de terminación. Especifique el parámetro ErrorAction con Stop como valor para convertir un error de no terminación en uno de terminación.
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
process {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer -ErrorAction Stop
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Computer"
}
}
}
}
No modifique la variable global $ErrorActionPreference
a menos que sea absolutamente necesario. Si lo cambia en un ámbito local, se revierte al valor anterior al salir de ese ámbito.
Si usa algo parecido a .NET directamente desde la función de PowerShell, no puede especificar el parámetro ErrorAction en el propio comando. Puede cambiar la variable $ErrorActionPreference
justo antes de llamar al método .NET.
Ayuda basada en comentarios
Agregar ayuda a las funciones se considera un procedimiento recomendado. La ayuda permite que las personas con las que los comparte sepan cómo utilizarlos.
function Get-MrAutoStoppedService {
<#
.SYNOPSIS
Returns a list of services that are set to start automatically, are not
currently running, excluding the services that are set to delayed start.
.DESCRIPTION
Get-MrAutoStoppedService is a function that returns a list of services
from the specified remote computer(s) that are set to start
automatically, are not currently running, and it excludes the services
that are set to start automatically with a delayed startup.
.PARAMETER ComputerName
The remote computer(s) to check the status of the services on.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The
default is the current user.
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'
.EXAMPLE
'Server1', 'Server2' | Get-MrAutoStoppedService
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credential)
.INPUTS
String
.OUTPUTS
PSCustomObject
.NOTES
Author: Mike F. Robbins
Website: https://mikefrobbins.com
Twitter: @mikefrobbins
#>
[CmdletBinding()]
param (
)
#Function Body
}
Al añadir ayuda basada en comentarios a tus funciones, se puede recuperar la ayuda para estas de manera similar a los comandos integrados por defecto.
Toda la sintaxis para escribir una función en PowerShell puede parecer abrumadora para alguien que se inicia. Si no puede recordar la sintaxis de algo, abra una segunda instancia del entorno de scripting integrado (ISE) de PowerShell en un monitor independiente y vea el fragmento de código "Cmdlet (función avanzada): completa" mientras escribe el código para las funciones. Se puede acceder a los fragmentos de código en el ISE de PowerShell mediante la combinación de teclas Ctrl + J.
Resumen
En este capítulo, ha aprendido los conceptos básicos de la escritura de funciones en PowerShell, incluido cómo:
- Crear funciones avanzadas
- Usar la validación de parámetros
- Utilizar salida detallada
- Admitir entrada de pipeline
- Manejo de errores
- Crear ayuda basada en comentarios
Revisar
- ¿Cómo se obtiene una lista de verbos aprobados en PowerShell?
- ¿Cómo se convierte una función de PowerShell en avanzada?
- ¿Cuándo se deben agregar los parámetros WhatIf y Confirm a las funciones de PowerShell?
- ¿Cómo conviertes un error no terminado en uno terminante?
- ¿Por qué debe agregar ayuda basada en comentarios a sus funciones?