Búsqueda de sitios web

Secuencias de comandos de PowerShell • Obtención del tamaño de Git con PowerShell renovado


Hace unos días escribí sobre mis experiencias al diseñar una función de PowerShell que informa sobre el tamaño de la carpeta .git oculta. En esa versión de la función decidí incluir un parámetro que permitiría al usuario obtener el tamaño preformateado como KB, MB o GB. Lo pensé mucho porque este realmente no es el mejor patrón de diseño. Aún así, no soy el único al que le gusta la posibilidad de formatear los valores en una forma más fácil de usar. Permítanme mostrarles una revisión que creo que se adhiere mejor a las mejores prácticas de secuencias de comandos de PowerShell, pero que también brinda flexibilidad de formato de datos.

Extender un objeto de PowerShell

Una de las cosas que aprecio de PowerShell es lo fácil que es ampliar o modificar un tipo de objeto. Esto se debe a que PowerShell tiene un sistema de tipos extensible. Con el cmdlet Update-TypeData, es muy fácil agregar nuevas propiedades a un tipo de objeto. Ése es el enfoque que decidí adoptar. Decidí mantener mi objeto "base" muy simple y mostrar el tamaño del directorio en bytes. Esto significa que eliminé todo el código relacionado con el parámetro -As.

#get the total size of all files in the .git folder
$stat = Get-Childitem -path $git -Recurse -File | Measure-Object -Property length -sum

#create the output
$obj = [PSCustomObject]@{
    Path  = $full
    Files = $stat.count
    Size  = $stat.sum
} #customobject

Para extender un tipo necesita saber el nombre del tipo. Al crear un PSCustomobject como lo estoy haciendo aquí, esto requerirá algunos pasos adicionales para definir un nombre de tipo adicional para el objeto personalizado. Normalmente esto se hace con un código como este:

#insert a new typename
$obj.psobject.typenames.insert(0, "gitSize")
#write object to the pipeline
$obj

Actualizar

Gracias a un comentario de Chris Warwick, hay una manera aún mejor de insertar un nombre de tipo que se introdujo en PowerShell en algún momento y me lo perdí. Defina una propiedad llamada PSTypeName en el propio objeto.

$obj = [PSCustomObject]@{
    PSTypeName = "gitSize"
    Path       = $full
    Files      = $stat.count
    Size       = $stat.sum
} #customobject

No se mostrará en la salida del objeto, pero se usará para el nombre del tipo. Puede verificarlo canalizando el objeto a Get-Member.

También podría haber creado una clase de PowerShell que habría incluido el nombre del tipo desde el principio. Pero eso es un poco más complicado de lo que quiero abordar aquí. Ahora viene lo divertido.

Explotación de Update-TypeData

Mi función ahora escribirá un objeto en la canalización con un nombre de tipo gitSize. Decidí que todas las demás propiedades que quisiera ver eran completamente opcionales. Para lograr esto, usaré el comando Update-TypeData fuera de la función. Como de todos modos necesitaría agregar puntos al archivo de secuencia de comandos con la función, agregué estas líneas después de la definición de la función.

Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeKB -Value {$this.size / 1kb} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeMB -Value {$this.size / 1mb} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeGB -Value {$this.size / 1gb} -force
Update-TypeData -TypeName gitSize -MemberType NoteProperty -MemberName Computername -Value $env:COMPUTERNAME -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName Date -Value {Get-Date} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName Name -Value {Split-Path -path $this.path -leaf} -force

También agregué algunas otras propiedades en las que había estado pensando, como Nombre de computadora y Fecha. Sí, podría haber definido muchos de estos como parte del objeto personalizado, en cuyo caso habría usado un código como este:

$obj = [PSCustomObject]@{
    Path  = $full
    Files = $stat.count
    Size  = $stat.sum
    SizeKB = $stat.sum/1kb
    Computername = $env:computername
} #customobject

De todos modos, sabía que, de forma predeterminada, solo quería ver la ruta, la cantidad de archivos y el tamaño en bytes. Tal como están las cosas actualmente, cuando ejecuto mi función obtendré un objeto que muestra todo. La solución es utilizar Update-TypeData y definir un conjunto predeterminado de propiedades.

#set default properties
Update-TypeData -TypeName gitSize -DefaultDisplayPropertySet "Path", "Files", "Size" -Force

Cuando ejecuto la función, que escribirá un objeto gitSize en la canalización, obtendré los valores predeterminados.

Pero todas las propiedades están ahí si las quiero.

La función extendida

Dejé la función original en Github. Esta versión se puede encontrar aquí.

Get-GitSize2.ps1:

Function Get-GitSize {
 <#
.SYNOPSIS
Get the size of .git folder
.DESCRIPTION
When using git, it creates a hidden folder for change tracking. Because the file is hidden it is easy to overlook how large it might become. Specify the parent folder path. The result displays the size value in bytes but there are additional properties that show the value formatted in KB, MB or GB.
.PARAMETER Path
The path to the parent folder, not the .git folder.
.EXAMPLE
PS C:\scripts\PiedPiperBox> Get-GitSize

Path                     Files  Size
----                     -----  ----
C:\scripts\PiedPiperBox\    30 18456

Get the size of the .git folder from the current path. The size is in bytes.

.EXAMPLE
PS C:\> get-gitsize C:\scripts\DevOps-Courses\ | Select Path,Files,SizeMB

Path                       Files           SizeMB
----                       -----           ------
C:\scripts\DevOps-Courses\    29 50.8291053771973

Get the size of the git folder under C:\Scripts\Devops-Courses selecting a different set of properties.
.EXAMPLE
PS C:\> dir c:\scripts -Directory | Get-GitSize | Sort Size -descending | Select -first 5 -property Path,Files,SizeMB

Path                      Files           SizeMB
----                      -----           ------
C:\scripts\PS-AutoLab-Env    46 183.732244491577
C:\scripts\DevOps-Courses    29 50.8291053771973
C:\scripts\PSGUI             32 6.39523887634277
C:\scripts\Lab              132 1.67190265655518
C:\scripts\tips             530 1.66895771026611

Get the directories under C:\Scripts that have a .git folder and sort on the Size property in descending order. Then select the first 5 directories and use the specified properties.
.INPUTS
[System.String]
.NOTES
Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/

This is a variation of code posted at https://gist.github.com/jdhitsolutions/cbdc7118f24ba551a0bb325664415649
.LINK
Get-ChildItem
.LINK
Measure-Object
#>

    [cmdletbinding()]
    [Outputtype("gitSize")]
    Param (
        [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelinebyPropertyName)]
        [alias("pspath")]
        [ValidateScript( {Test-Path $_})]
        [string]$Path = "."
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        $full = Convert-Path -Path $Path
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing path $full"
        $git = Join-Path -Path $full -childpath ".git"
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Testing $git"
        if (Test-Path -path $git) {

            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Measuring $git"
            #get the total size of all files in the .git folder
            $stat = Get-Childitem -path $git -Recurse -File | Measure-Object -Property length -sum

  #create the output
            <#
             Thanks to Chris Warwick for the tip on a better way to insert a typename. This must have
             been introduced in some previous version of PowerShell and I missed it. Definitely
             a cleaner solution.
            #>
            $obj = [PSCustomObject]@{
                PSTypeName = "gitSize"
                Path       = $full
                Files      = $stat.count
                Size       = $stat.sum
            } #customobject

            <# this is the 'old-school' way to  insert a new typename or if you can't define it at runtime
               $obj.psobject.typenames.insert(0, "gitSize")
            #>

            <#
            write object to the pipeline. As currently written I didn't have to save the custom object
            to a variable. I would only need to do that if I were going to do something else with it
            before writing it to the pipeline.
            #>
            $obj
        } #if test-path
        else {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Did not find $git"
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

#add custom properties
#these properties could also be added to the original object
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeKB -Value {$this.size / 1kb} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeMB -Value {$this.size / 1mb} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName SizeGB -Value {$this.size / 1gb} -force
Update-TypeData -TypeName gitSize -MemberType NoteProperty -MemberName Computername -Value $env:COMPUTERNAME -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName Date -Value {Get-Date} -force
Update-TypeData -TypeName gitSize -MemberType ScriptProperty -MemberName Name -Value {Split-Path -path $this.path -leaf} -force

#set default properties
Update-TypeData -TypeName gitSize -DefaultDisplayPropertySet "Path", "Files", "Size" -Force

Como antes, deberá guardar el archivo localmente y agregarlo a su sesión de PowerShell. Ahora tengo cierta flexibilidad. Puedo obtener el resultado predeterminado como mostré antes o puedo ejecutar código como este para utilizar las propiedades adicionales.

Otro enfoque en el que pensé, pero que no seguí, sería el uso de un archivo format.ps1xml. Esto también requeriría que defina un nombre de tipo. Con este enfoque, habría tenido que crear un archivo xml con formato especial que defina las vistas de tabla o lista (generalmente estas dos). Las vistas podrían incluir código para mostrar dinámicamente datos formateados como KB o MB. Este es el tipo de cosas que se hacen con los objetos de proceso que ves cuando ejecutas Get-Process. Cuando ejecuta Get-Process solo, PowerShell usa una vista de tabla definida para objetos de proceso que da formato a los valores. Pero no cambia el objeto subyacente. Crear el archivo xml puede ser un poco tedioso y no quería caer en esa madriguera hoy. Ciertamente puede haber situaciones en las que desee aprovechar las extensiones de tipo y formato y, con suerte, podremos volver a abordar este tema en un artículo futuro.

Las secuencias de comandos de PowerShell son un proceso

Mientras tanto, eche un vistazo al código. Considere el proceso de diseño por el que pasé. Escribo estos artículos tanto para enseñarte conceptos y técnicas como para mostrarte un producto terminado. Aunque si lo encuentra útil, es una ventaja. Abraza y disfruta.

Artículos relacionados