Búsqueda de sitios web

Secuencias de comandos del controlador PowerShell


Cuando se trata de secuencias de comandos de PowerShell, tendemos a centrarnos mucho en funciones y módulos. Ponemos énfasis en la construcción de herramientas reutilizables. La idea es que luego podamos usar estas herramientas en un mensaje de PowerShell para realizar una tarea determinada. Lo más probable es que estas tareas sean repetitivas. En estas situaciones, tiene sentido tomar los comandos que normalmente escribiría de forma interactiva en un símbolo del sistema de PowerShell y colocarlos en un script del controlador de PowerShell. Este script, a su vez, utiliza las funciones y módulos que usted ha creado. He estado creando un conjunto de herramientas PowerShell para realizar copias de seguridad de carpetas importantes. También creé algunos scripts de controlador de administración que quiero compartir con ustedes.

Diseño de scripts de PowerShell

La idea básica con un script de PowerShell es que se trata de una sesión de PowerShell "enlatada". Debería poder tomar el contenido de un archivo ps1, pegarlo en una sesión de PowerShell y debería ejecutarse. En lugar de escribir los comandos de forma interactiva, el script lo hace por usted. Sin embargo, los scripts de PowerShell también pueden tomar parámetros, usar enlaces de cmdlets y aprovechar construcciones de scripts como declaraciones If/Else. Los scripts de PowerShell no tienen que seguir las mismas reglas que enfatizamos al crear funciones. El script facilita la ejecución de una serie de comandos que controlan las funciones o cmdlets. He aquí un ejemplo.

#requires -version 5.1

[cmdletbinding()]

Param(
    [parameter(position = 0)]
    [ValidateNotNullOrEmpty()]
    [string]$Backup = "*",
    [Parameter(HelpMessage = "Show raw objects, not a formatted report.")]
    [switch]$Raw
)

$f = Get-ChildItem -path "D:\backup\$backup-log.csv" | ForEach-Object {
 $p = $_.fullname
 Import-Csv $_.fullname | Add-Member -MemberType NoteProperty -Name Log -value $p -PassThru
 } | Where-Object { ([int32]$_.Size -gt 0) -AND ($_.IsFolder -eq 'False') }

$files = ($f.name | Select-Object -Unique).Foreach({ $n = $_; $f.where({ $_.name -eq $n }) |
Sort-Object -Property { $_.date -as [datetime] } | Select-Object -last 1 })

if ($raw) {
    $files
}
else {
    $grouped = $files | Group-Object Log
    #display formatted results
    $files | Sort-Object -Property Log, Directory, Name | Format-Table -GroupBy log -Property Date, Name, Size, Directory
    $summary = foreach ($item in $grouped) {
        [PSCustomObject]@{
            Backup = (Get-Item $item.Name).basename.split("-")[0]
            Files  = $item.Count
            Size   = ($item.group | Measure-Object -Property size -sum).sum
        }
    } #foreach item

    $total = [PSCustomObject]@{
        TotalFiles = ($grouped | Measure-Object -property count -sum).sum
        TotalSizeMB = [math]::round(($summary.size | Measure-Object -sum).sum/1MB,4)
    }
    Write-Host "Incremental Pending Backup Summary" -ForegroundColor cyan
    ($summary | Format-Table | Out-String).TrimEnd() | Write-Host -ForegroundColor cyan

    $total | Format-Table  | Out-String | Write-Host -ForegroundColor cyan
}

Este script, MyBackupPending.ps1, está diseñado para mostrarme qué archivos se respaldarán como parte de mi trabajo de respaldo incremental diario. El archivo de secuencia de comandos revisa mi lista de archivos csv de respaldo y, de forma predeterminada, muestra un informe formateado.

Normalmente, no usarías Format-Table o Write-Host en una función de PowerShell. Pero este script de control no está diseñado para escribir objetos en la canalización. Está diseñado para darme un informe formateado. Sin embargo, eso no significa que no pueda tener las cosas en ambos sentidos.

Los scripts pueden tener parámetros. El mío incluye parámetros para indicar el conjunto de copia de seguridad o una opción para mostrar datos sin formato y sin formato.

Puede utilizar las mismas técnicas de secuencias de comandos en sus secuencias de comandos que utiliza en sus funciones. Aquí está mi script de control que me brinda un informe sobre los archivos de respaldo.

# requires -version 5.1

[cmdletbinding()]
Param(
    [Parameter(Position = 0, HelpMessage = "Enter the path with extension of the backup files.")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript( { Test-Path $_ })]
    [string]$Path = "\\ds416\backup",
    [Parameter(HelpMessage = "Get backup files only with no formatted output.")]
    [Switch]$Raw
)

<#
 A regular expression pattern to match on backup file name with named captures
 to be used in adding some custom properties. My backup names are like:

  20191101_Scripts-FULL.rar
  20191107_Scripts-INCREMENTAL.rar

  #>
[regex]$rx = "^20\d{6}_(?<set>\w+)-(?<type>\w+)\.rar$"

<#
I am doing so 'pre-filtering' on the file extension and then using the regular
expression filter to fine tune the results
#>
$files = Get-ChildItem -path $Path -filter *.rar | Where-Object { $rx.IsMatch($_.name) }

#add some custom properties to be used with formatted results based on named captures
foreach ($item in $files) {
    $setpath = $rx.matches($item.name).groups[1].value
    $settype = $rx.matches($item.name).groups[2].value

    $item | Add-Member -MemberType NoteProperty -Name SetPath -Value $setpath
    $item | Add-Member -MemberType NoteProperty -Name SetType -Value $setType
}

if ($raw) {
    $Files
}
else {
    $files | Sort-Object SetPath, SetType, LastWriteTime | Format-Table -GroupBy SetPath -Property LastWriteTime, Length, Name
    $grouped = $files | Group-Object SetPath
    $summary = foreach ($item in $grouped) {
        [pscustomobject]@{
            BackupSet = $item.name
            Files  = $item.Count
            Size   = ($item.group | Measure-Object -Property size -sum).sum
        }
    }

    $total = [PSCustomObject]@{
        TotalFiles = ($grouped | Measure-Object -property count -sum).sum
        TotalSizeMB = [math]::round(($summary.size | Measure-Object -sum).sum/1MB,4)
    }
    Write-Host "Backup Summary" -ForegroundColor cyan
    ($summary | sort-object Size -Descending| Format-Table | Out-String).TrimEnd() | Write-Host -ForegroundColor cyan

    $total | Format-Table  | Out-String | Write-Host -ForegroundColor cyan
}

Notarás que los parámetros tienen atributos y validación. Pero ahora tengo un comando muy fácil de ejecutar que muestra mis archivos de respaldo formateados y ordenados, incluido un resumen.

Ejecute scripts de PowerShell desde cualquier lugar

Un inconveniente de los scripts de control de PowerShell es que debe especificar la ruta completa al archivo ps1. Si sus scripts están en una ruta simple como C:\Scripts, tal vez no sea mucho más para escribir. Pero siempre estoy buscando formas de ser "creativamente vago". Aquí tienes un enfoque que te puede gustar.

PowerShellGet, que facilita la búsqueda e instalación de módulos desde la Galería de PowerShell, también tiene un conjunto de comandos relacionados para buscar e instalar scripts de PowerShell. Puede instalar un archivo de secuencia de comandos de PowerShell desde la galería de PowerShell y ejecutar el archivo .ps1 desde cualquier lugar. No es necesario especificar la ruta. ¡Quiero eso!

Voy a tomar el camino fácil y usaré la ubicación que PowerShell ya conoce: $env:ProgramFiles\WindowsPowerShell\Scripts. Si copias un archivo ps1 en esta carpeta, deberías poder ejecutarlo simplemente escribiendo el nombre del script. Esto es perfecto. Casi.

Me gusta mantener todo mi trabajo de secuencias de comandos en C:\Scripts y sabía que estaría modificando mis secuencias de comandos de control de respaldo por un tiempo. No quería tener que lidiar con la edición del archivo y mantener sincronizada la versión en Scripts instalados. Entonces los vinculé. Aproveché los enlaces simbólicos para crear una copia vinculada en Installed Scripts. Ahora puedo ejecutar el script del controlador solo por su nombre y todo lo que necesito hacer es editar el archivo en C:\Scripts.

Para simplificar el proceso, naturalmente creé un script de controlador.

#requires -version 5.1

#create linked script copies in Installed Scripts

[cmdletbinding(SupportsShouldProcess)]
Param(

    [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [ValidateScript({Test-Path $_})]
    [String[]]$Path
)

Begin {
    $installPath = "$env:ProgramFiles\WindowsPowerShell\Scripts"
    Write-Verbose "Creating links to $installpath"
}
Process {
    if (Test-Path -Path $installPath) {

        foreach ($file in $Path) {

            Try {
                $cfile = Convert-Path $File -ErrorAction Stop
            }
            Catch {
                Throw "Failed to find or convert $file"
            }
            if ($cfile) {
                $name = Split-Path -Path $cfile -Leaf
                $target = Join-Path -path $installPath -ChildPath $name
                Write-Verbose "Creating a link from $cfile to $target. A 0 byte file is normal."

                #overwrite the target if it already exists
                New-Item -Path $target -value $cfile -ItemType SymbolicLink -Force
            }
        } #foreach file
    }
    else {
        Write-Warning "Can't find $installPath. This script might require a Windows platform."
    }
}
End {
    Write-Verbose "Finished linking script files."
}

# Get-command -CommandType ExternalScript

Tengo un archivo de script que ejecuto para mostrarme una vista personalizada de la ejecución de máquinas virtuales Hyper-V, C:\Scripts\HyperVStatus.ps1. Crearé un enlace a los scripts instalados.

Como puede ver, esto crea un enlace de 0 bytes al script de destino. Ahora puedo ejecutar este archivo de script desde cualquier lugar.

Si actualizo el script, no tengo que hacer nada con el enlace. De hecho, puedo ir aún más lejos y definir un alias.

Creé enlaces a mis scripts de control de respaldo. Utilice Get-Command para descubrir scripts externos.

No tenga miedo de crear scripts de controlador de PowerShell que aprovechen. Estas son herramientas que usted puede utilizar para facilitar su trabajo. Siempre y cuando entiendas su propósito.

Artículos relacionados