Editar: Solution originale en bas de cet article. Pour une solution plus à jour, voir la réponse acceptée, postée par Thraka.
Coloriser Get-Childitem ( dir o ls (en d'autres termes) n'est pas exactement une idée nouvelle, mais je n'ai pas été en mesure de trouver une approche idéale pour colorer la sortie dans Powershell. Il existe deux approches générales pour écrire des fonctions color-ls :
-
Intercepter la sortie de Get-Childitem, et la ré-afficher sous forme de texte en utilisant Write-Host avec le paramètre -ForegroundColor. Cette approche permet d'obtenir la plus grande granularité possible, mais réduit la sortie de Get-Childitem au format texte. Comme la plupart des utilisateurs de powershell le savent, Get-Childitem ne produit pas de texte, mais des objets. Plus précisément, une liste d'objets FileInfo et DirectoryInfo. Cela permet une grande flexibilité dans la gestion de la sortie de Get-Childitem.
-
Transférer la sortie de Get-Childitem via Invoke-Expression vers Foreach-Object, en changeant la couleur de premier plan de la console avant de sortir chaque objet. C'est un peu long, mais c'est la meilleure option car elle préserve le type de la sortie de Get-Childitem.
Voici un exemple de cette dernière approche, fourni par Blog Powershell de Tim Johnson .
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 des couleurs différentes en se basant uniquement sur l'extension du fichier, mais presque n'importe quelle métrique pourrait être substituée pour différencier les types de fichiers. Le code ci-dessus produit la sortie suivante :
C'est presque parfait, mais il y a un petit défaut : les 3 premières lignes de sortie (chemin du répertoire, en-têtes de colonne et séparateurs horizontaux) prennent la couleur du premier élément de la liste. Tim Johnson a commenté dans son blog :
Je préférerais que l'en-tête en haut ne soit pas toujours de la même couleur que le premier élément, mais je ne vois pas comment faire autrement.
Moi non plus, malheureusement. C'est là que Stack Overflow et ses gourous de powershell interviennent : Je cherche un moyen de coloriser la sortie de Get-Childitem tout en préservant le type de sortie de la cmdlet, sans altérer la couleur de l'en-tête. J'ai fait quelques essais et manipulations avec cette approche, mais je n'ai pas encore eu de succès, car le premier appel unique à echo produit l'intégralité de l'en-tête et du premier élément.
Toute question, tout commentaire ou, mieux encore, toute solution sont les bienvenus.
La solution Merci à jon Z et aux autres qui ont fourni des idées :
Jon Z a fourni la solution parfaite à ce problème, que j'ai un peu peaufinée pour qu'elle corresponde au schéma de ma question initiale. La voici, pour tous ceux qui sont intéressés. Notez que cela nécessite le La cmdlet New-CommandWrapper du livre de cuisine Powershell. La cmdlet correspondante a été ajoutée au bas de cet article. . Tout ce code se trouve 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 ""
}
Cela produit un résultat qui ressemble à la capture d'écran suivante :
Si vous souhaitez que la ligne de la taille totale du fichier figure en bas, 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é signalé dans les commentaires, le lien PoshCode New-CommandWrapper est mort. Voici la cmdlet concernée dans son intégralité :
##############################################################################
##
## 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"
}
0 votes
Bonjour. Voici quelques liens pertinents : stackoverflow.com/a/8088587/539542
0 votes
L'en-tête et le séparateur ne sont pas une sortie de get-childitem. C'est juste une information supplémentaire lorsqu'un objet de système de fichiers est affiché. Vous devez donc probablement modifier FileSystem.format.ps1xml... si c'est possible...
0 votes
La solution de Jon est idéale. Une fois que j'aurai écrit une nouvelle version bien testée et polie, je la mettrai dans le PO pour référence future.
0 votes
Ce blog présente quelques modifications de cette approche : avinmathew.com/coloured-directory-listings-in-powershell
1 votes
Malheureusement, le site poshcode.org semble être mort.
0 votes
@KERR : Ajout de la source complète de la cmdlet New-CommandWrapper dans le post.
0 votes
Avez-vous vu et évalué les modules de MS powershellgallery.com ?
Find-Module -Name '*color*'
. On dirait que vous essayez de faire ce qu'ils ont déjà fait ?0 votes
Avez-vous vu et évalué la date à laquelle cette question a été posée ? Ce site n'existait pas à l'époque, pas plus que la cmdlet Find-Module. Peut-être pourriez-vous y chercher une solution et la poster comme réponse ?