Powershell: Correctement coloriage Get-Childitem de sortie une fois pour toutes

Edit: Solution au bas de ce post.

Colorisation Get-Childitem (dir ou ls, en d'autres termes) n'est pas une idée nouvelle, mais je n'ai pas été en mesure de localiser un idéal approches de la colorisation de sortie en Powershell. Il y a deux approches pour la couleur d'écriture-ls fonctions:

  • De l'interception de la sortie de Get-Childitem, et re-sortie en tant que texte à l'aide de Write-Host avec l'-ForegroundColor paramètre. Cette approche permet de granularité que possible, mais réduit la sortie de Get-Childitem-texte. Comme la plupart des powershell utilisateurs sont conscients, Get-Childitem n'a pas de texte de sortie, plutôt, il produit des objets. Plus précisément, une liste de FileInfo et les objets DirectoryInfo. Cela permet une grande souplesse dans la manipulation des Get-Childitem de sortie.

  • Tuyau de la sortie de Get-Childitem par Invoke-Expression de Foreach-Object, la modification de la console de couleur de premier plan avant la sortie de chaque objet. Une sorte de bouchée, mais la meilleure option, car elle préserve le type de Get-Childitem de sortie.

Voici un exemple de cette dernière approche, fournis par Tim Johnson Powershell Blog.

function color-ls
{
    $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase `
          -bor [System.Text.RegularExpressions.RegexOptions]::Compiled)
    $fore = $Host.UI.RawUI.ForegroundColor
    $compressed = New-Object System.Text.RegularExpressions.Regex(
          '\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
    $executable = New-Object System.Text.RegularExpressions.Regex(
          '\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
    $text_files = New-Object System.Text.RegularExpressions.Regex(
          '\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)

    Invoke-Expression ("Get-ChildItem $args") | ForEach-Object {
        if ($_.GetType().Name -eq 'DirectoryInfo') 
        {
            $Host.UI.RawUI.ForegroundColor = 'Magenta'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($compressed.IsMatch($_.Name)) 
        {
            $Host.UI.RawUI.ForegroundColor = 'darkgreen'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($executable.IsMatch($_.Name))
        {
            $Host.UI.RawUI.ForegroundColor = 'Red'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($text_files.IsMatch($_.Name))
        {
            $Host.UI.RawUI.ForegroundColor = 'Yellow'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        else
        {
            echo $_
        }
    }
}

Ce code attribue différentes couleurs basé purement sur l'extension de fichier, mais à peu près n'importe quelle mesure il pourrait être remplacé à différencier les types de fichiers. Le code ci-dessus produit la sortie suivante:

Powershell: Correctement coloriage Get-Childitem de sortie une fois pour toutes

C'est près de parfait, mais il y a un petit défaut: les 3 premières lignes de sortie (chemin d'accès au Répertoire, en-Têtes de Colonne, et de séparateurs horizontaux) prennent la couleur du premier élément dans la liste. Tim Johnson, a commenté sur son blog:

Je préfère si l'en-tête en haut n'était pas toujours de la même couleur que le premier élément, mais je ne peux pas penser à un moyen de contourner cela.

Ne peux, malheureusement. C'est là un Débordement de Pile et de ses powershell gourous de venir: je suis à la recherche d'une façon de coloriser Get-Childitem de sortie tout en préservant l'applet de commande type de sortie, sans perturber le fonctionnement de la couleur de l'en-tête. J'ai fait de l'expérimentation et de jongler avec cette approche, mais n'ont pas eu de succès pour l'instant, comme le premier single echo appel sorties de l'ensemble de l'en-tête et le premier élément.

Des questions, des commentaires, ou, mieux encore, les solutions sont les bienvenues.

La Solution Avec Grâce à jon Z et les autres, qui a donné des idées:

Jon Z la solution idéale à ce problème, j'ai peaufiné un peu pour correspondre au schéma dans ma question initiale. Ici, il est, pour toute personne qui est intéressé. À noter que cela nécessite la New-CommandWrapper applet de commande de l'Powershell livre de cuisine. Pertinentes de l'applet de commande ajouté en bas de ce post. Tout ce code va dans votre profil.

function Write-Color-LS
{
param ([string]$color = "white", $file)
Write-host ("{0,-7} {1,25} {2,10} {3}" -f $file.mode, ([String]::Format("{0,10}  {1,8}", $file.LastWriteTime.ToString("d"), $file.LastWriteTime.ToString("t"))), $file.length, $file.name) -foregroundcolor $color 
}
New-CommandWrapper Out-Default -Process {
$regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
$compressed = New-Object System.Text.RegularExpressions.Regex(
'\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
$executable = New-Object System.Text.RegularExpressions.Regex(
'\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
$text_files = New-Object System.Text.RegularExpressions.Regex(
'\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)
if(($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]))
{
if(-not ($notfirst)) 
{
Write-Host
Write-Host "    Directory: " -noNewLine
Write-Host " $(pwd)`n" -foregroundcolor "Magenta"           
Write-Host "Mode                LastWriteTime     Length Name"
Write-Host "----                -------------     ------ ----"
$notfirst=$true
}
if ($_ -is [System.IO.DirectoryInfo]) 
{
Write-Color-LS "Magenta" $_                
}
elseif ($compressed.IsMatch($_.Name))
{
Write-Color-LS "DarkGreen" $_
}
elseif ($executable.IsMatch($_.Name))
{
Write-Color-LS "Red" $_
}
elseif ($text_files.IsMatch($_.Name))
{
Write-Color-LS "Yellow" $_
}
else
{
Write-Color-LS "White" $_
}
$_ = $null
}
} -end {
write-host ""
}

Ce produit une sortie qui ressemble à la capture d'écran suivante:
Powershell: Correctement coloriage Get-Childitem de sortie une fois pour toutes

Si vous voulez que la taille totale du fichier à la ligne de fond, il suffit d'ajouter le code suivant:

Remove-Item alias:ls
Set-Alias ls LS-Padded
function LS-Padded
{
param ($dir)
Get-Childitem $dir
Write-Host
getDirSize $dir
}
function getDirSize
{
param ($dir)
$bytes = 0
Get-Childitem $dir | foreach-object {
if ($_ -is [System.IO.FileInfo])
{
$bytes += $_.Length
}
}
if ($bytes -ge 1KB -and $bytes -lt 1MB)
{
Write-Host ("Total Size: " + [Math]::Round(($bytes / 1KB), 2) + " KB")   
}
elseif ($bytes -ge 1MB -and $bytes -lt 1GB)
{
Write-Host ("Total Size: " + [Math]::Round(($bytes / 1MB), 2) + " MB")
}
elseif ($bytes -ge 1GB)
{
Write-Host ("Total Size: " + [Math]::Round(($bytes / 1GB), 2) + " GB")
}    
else
{
Write-Host ("Total Size: " + $bytes + " bytes")
}
}

Comme cela a été souligné dans les commentaires, le PoshCode Nouvelle-CommandWrapper lien est mort. Voici la cmdlet complète:

##############################################################################
##
## New-CommandWrapper
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Adds parameters and functionality to existing cmdlets and functions.
.EXAMPLE
New-CommandWrapper Get-Process `
-AddParameter @{
SortBy = {
$newPipeline = {
__ORIGINAL_COMMAND__ | Sort-Object -Property $SortBy
}
}
}
This example adds a 'SortBy' parameter to Get-Process. It accomplishes
this by adding a Sort-Object command to the pipeline.
.EXAMPLE
$parameterAttributes = @'
[Parameter(Mandatory = $true)]
[ValidateRange(50,75)]
[Int]
'@
New-CommandWrapper Clear-Host `
-AddParameter @{
@{
Name = 'MyMandatoryInt';
Attributes = $parameterAttributes
} = {
Write-Host $MyMandatoryInt
Read-Host "Press ENTER"
}
}
This example adds a new mandatory 'MyMandatoryInt' parameter to
Clear-Host. This parameter is also validated to fall within the range
of 50 to 75. It doesn't alter the pipeline, but does display some
information on the screen before processing the original pipeline.
#>
param(
## The name of the command to extend
[Parameter(Mandatory = $true)]
$Name,
## Script to invoke before the command begins
[ScriptBlock] $Begin,
## Script to invoke for each input element
[ScriptBlock] $Process,
## Script to invoke at the end of the command
[ScriptBlock] $End,
## Parameters to add, and their functionality.
##
## The Key of the hashtable can be either a simple parameter name,
## or a more advanced parameter description.
##
## If you want to add additional parameter validation (such as a
## parameter type,) then the key can itself be a hashtable with the keys
## 'Name' and 'Attributes'. 'Attributes' is the text you would use when
## defining this parameter as part of a function.
##
## The Value of each hashtable entry is a scriptblock to invoke
## when this parameter is selected. To customize the pipeline,
## assign a new scriptblock to the $newPipeline variable. Use the
## special text, __ORIGINAL_COMMAND__, to represent the original
## command. The $targetParameters variable represents a hashtable
## containing the parameters that will be passed to the original
## command.
[HashTable] $AddParameter
)
Set-StrictMode -Version Latest
## Store the target command we are wrapping, and its command type
$target = $Name
$commandType = "Cmdlet"
## If a function already exists with this name (perhaps it's already been
## wrapped,) rename the other function and chain to its new name.
if(Test-Path function:\$Name)
{
$target = "$Name" + "-" + [Guid]::NewGuid().ToString().Replace("-","")
Rename-Item function:\GLOBAL:$Name GLOBAL:$target
$commandType = "Function"
}
## The template we use for generating a command proxy
$proxy = @'
__CMDLET_BINDING_ATTRIBUTE__
param(
__PARAMETERS__
)
begin
{
try {
__CUSTOM_BEGIN__
## Access the REAL Foreach-Object command, so that command
## wrappers do not interfere with this script
$foreachObject = $executionContext.InvokeCommand.GetCmdlet(
"Microsoft.PowerShell.Core\Foreach-Object")
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(
'__COMMAND_NAME__',
[System.Management.Automation.CommandTypes]::__COMMAND_TYPE__)
## TargetParameters represents the hashtable of parameters that
## we will pass along to the wrapped command
$targetParameters = @{}
$PSBoundParameters.GetEnumerator() |
& $foreachObject {
if($command.Parameters.ContainsKey($_.Key))
{
$targetParameters.Add($_.Key, $_.Value)
}
}
## finalPipeline represents the pipeline we wil ultimately run
$newPipeline = { & $wrappedCmd @targetParameters }
$finalPipeline = $newPipeline.ToString()
__CUSTOM_PARAMETER_PROCESSING__
$steppablePipeline = [ScriptBlock]::Create(
$finalPipeline).GetSteppablePipeline()
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
__CUSTOM_PROCESS__
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
__CUSTOM_END__
$steppablePipeline.End()
} catch {
throw
}
}
dynamicparam
{
## Access the REAL Get-Command, Foreach-Object, and Where-Object
## commands, so that command wrappers do not interfere with this script
$getCommand = $executionContext.InvokeCommand.GetCmdlet(
"Microsoft.PowerShell.Core\Get-Command")
$foreachObject = $executionContext.InvokeCommand.GetCmdlet(
"Microsoft.PowerShell.Core\Foreach-Object")
$whereObject = $executionContext.InvokeCommand.GetCmdlet(
"Microsoft.PowerShell.Core\Where-Object")
## Find the parameters of the original command, and remove everything
## else from the bound parameter list so we hide parameters the wrapped
## command does not recognize.
$command = & $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__
$targetParameters = @{}
$PSBoundParameters.GetEnumerator() |
& $foreachObject {
if($command.Parameters.ContainsKey($_.Key))
{
$targetParameters.Add($_.Key, $_.Value)
}
}
## Get the argumment list as it would be passed to the target command
$argList = @($targetParameters.GetEnumerator() |
Foreach-Object { "-$($_.Key)"; $_.Value })
## Get the dynamic parameters of the wrapped command, based on the
## arguments to this command
$command = $null
try
{
$command = & $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__ `
-ArgumentList $argList
}
catch
{
}
$dynamicParams = @($command.Parameters.GetEnumerator() |
& $whereObject { $_.Value.IsDynamic })
## For each of the dynamic parameters, add them to the dynamic
## parameters that we return.
if ($dynamicParams.Length -gt 0)
{
$paramDictionary = `
New-Object Management.Automation.RuntimeDefinedParameterDictionary
foreach ($param in $dynamicParams)
{
$param = $param.Value
$arguments = $param.Name, $param.ParameterType, $param.Attributes
$newParameter = `
New-Object Management.Automation.RuntimeDefinedParameter `
$arguments
$paramDictionary.Add($param.Name, $newParameter)
}
return $paramDictionary
}
}
<#
.ForwardHelpTargetName __COMMAND_NAME__
.ForwardHelpCategory __COMMAND_TYPE__
#>
'@
## Get the information about the original command
$originalCommand = Get-Command $target
$metaData = New-Object System.Management.Automation.CommandMetaData `
$originalCommand
$proxyCommandType = [System.Management.Automation.ProxyCommand]
## Generate the cmdlet binding attribute, and replace information
## about the target
$proxy = $proxy.Replace("__CMDLET_BINDING_ATTRIBUTE__",
$proxyCommandType::GetCmdletBindingAttribute($metaData))
$proxy = $proxy.Replace("__COMMAND_NAME__", $target)
$proxy = $proxy.Replace("__COMMAND_TYPE__", $commandType)
## Stores new text we'll be putting in the param() block
$newParamBlockCode = ""
## Stores new text we'll be putting in the begin block
## (mostly due to parameter processing)
$beginAdditions = ""
## If the user wants to add a parameter
$currentParameter = $originalCommand.Parameters.Count
if($AddParameter)
{
foreach($parameter in $AddParameter.Keys)
{
## Get the code associated with this parameter
$parameterCode = $AddParameter[$parameter]
## If it's an advanced parameter declaration, the hashtable
## holds the validation and /or type restrictions
if($parameter -is [Hashtable])
{
## Add their attributes and other information to
## the variable holding the parameter block additions
if($currentParameter -gt 0)
{
$newParamBlockCode += ","
}
$newParamBlockCode += "`n`n    " +
$parameter.Attributes + "`n" +
'    $' + $parameter.Name
$parameter = $parameter.Name
}
else
{
## If this is a simple parameter name, add it to the list of
## parameters. The proxy generation APIs will take care of
## adding it to the param() block.
$newParameter =
New-Object System.Management.Automation.ParameterMetadata `
$parameter
$metaData.Parameters.Add($parameter, $newParameter)
}
$parameterCode = $parameterCode.ToString()
## Create the template code that invokes their parameter code if
## the parameter is selected.
$templateCode = @"
if(`$PSBoundParameters['$parameter'])
{
$parameterCode
## Replace the __ORIGINAL_COMMAND__ tag with the code
## that represents the original command
`$alteredPipeline = `$newPipeline.ToString()
`$finalPipeline = `$alteredPipeline.Replace(
'__ORIGINAL_COMMAND__', `$finalPipeline)
}
"@
## Add the template code to the list of changes we're making
## to the begin() section.
$beginAdditions += $templateCode
$currentParameter++
}
}
## Generate the param() block
$parameters = $proxyCommandType::GetParamBlock($metaData)
if($newParamBlockCode) { $parameters += $newParamBlockCode }
$proxy = $proxy.Replace('__PARAMETERS__', $parameters)
## Update the begin, process, and end sections
$proxy = $proxy.Replace('__CUSTOM_BEGIN__', $Begin)
$proxy = $proxy.Replace('__CUSTOM_PARAMETER_PROCESSING__', $beginAdditions)
$proxy = $proxy.Replace('__CUSTOM_PROCESS__', $Process)
$proxy = $proxy.Replace('__CUSTOM_END__', $End)
## Save the function wrapper
Write-Verbose $proxy
Set-Content function:\GLOBAL:$NAME $proxy
## If we were wrapping a cmdlet, hide it so that it doesn't conflict with
## Get-Help and Get-Command
if($commandType -eq "Cmdlet")
{
$originalCommand.Visibility = "Private"
}
  • Bonjour. Voici quelques liens utiles: stackoverflow.com/a/8088587/539542
  • L'en-tête et le séparateur sont pas une sortie de get-childitem. C'est juste un complément d'info quand un filesystemobject est affiché. Donc, vous avez probablement de modifier le système de fichiers.format.ps1xml...si c'est encore possible
  • Jon solution est idéale. Une fois que j'aurai bien testé et poli à la nouvelle version écrite, je vais le mettre dans l'OP pour référence future.
  • Ce blog a quelques modifications à cette approche: avinmathew.com/coloured-directory-listings-in-powershell
  • Malheureusement, la poshcode.org le site semble avoir disparu.
  • Ajout de la source complet de la Nouvelle-CommandWrapper applet de commande à la poste.

InformationsquelleAutor Fopedush | 2012-02-23