Powershell:一劳永逸地正确着色 Get-Childitem 输出 [英] Powershell: Properly coloring Get-Childitem output once and for all

查看:34
本文介绍了Powershell:一劳永逸地正确着色 Get-Childitem 输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本文底部的原始解决方案.有关更新的解决方案,请参阅 Thraka 发布的已接受答案.

为 Get-Childitem 着色(换句话说,dirls)并不是一个新想法,但我一直无法找到任何理想的方法来实现Powershell 中的着色输出.编写 color-ls 函数有两种通用方法:

  • 拦截 Get-Childitem 的输出,并使用带有 -ForegroundColor 参数的 Write-Host 将其重新输出为文本.这种方法允许尽可能多的粒度,但将 Get-Childitem 的输出减少为文本.大多数 PowerShell 用户都知道,Get-Childitem 不输出文本,而是输出对象.具体来说,是 FileInfo 和 DirectoryInfo 对象的列表.这为处理 Get-Childitem 输出提供了很大的灵活性.

  • 通过 Invoke-Expression 将 Get-Childitem 的输出传送到 Foreach-Object,在输出每个对象之前更改控制台前景色.有点啰嗦,但更好的选择,因为它保留了 Get-Childitem 的输出类型.

这里是后一种方法的一个例子,由 .相关 cmdlet 添加到本文底部.所有这些代码都包含在您的个人资料中.

函数Write-Color-LS{param ([string]$color = "white", $file)写主机 ("{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 = 新对象 System.Text.RegularExpressions.Regex('.(zip|tar|gz|rar|jar|war)$', $regex_opts)$executable = 新对象 System.Text.RegularExpressions.Regex('.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)$text_files = 新对象 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)){写主机写主机目录:"-noNewLine写主机$(pwd)`n"-前景色洋红色"写主机Mode LastWriteTime Length Name"写主机---- ------------- ------ ----"$notfirst=$true}if ($_ -is [System.IO.DirectoryInfo]){Write-Color-LS洋红色"$_}elseif ($compressed.IsMatch($_.Name)){写颜色-LS深绿色"$_}elseif ($executable.IsMatch($_.Name)){写颜色-LS红色"$_}elseif ($text_files.IsMatch($_.Name)){Write-Color-LS黄色"$_}别的{写颜色-LS白色"$_}$_ = $null}} -结尾 {写主机"}

这会产生类似于以下屏幕截图的输出:

如果您想要底部的总文件大小行,只需添加以下代码:

Remove-Item alias:ls设置别名 ls LS 填充功能 LS-Padded{参数 ($dir)获取子项 $dir写主机获取目录大小 $dir}函数 getDirSize{参数 ($dir)$字节 = 0Get-Childitem $dir |foreach对象{if ($_ -is [System.IO.FileInfo]){$bytes += $_.Length}}if ($bytes -ge 1KB -and $bytes -lt 1MB){写入主机(总大小:"+ [数学]::Round(($bytes/1KB), 2) +KB")}elseif ($bytes -ge 1MB -和 $bytes -lt 1GB){写入主机(总大小:"+[数学]::Round(($bytes/1MB), 2) +MB")}elseif ($bytes -ge 1GB){写入主机(总大小:"+ [数学]::Round(($bytes/1GB), 2) + GB")}别的{写主机(总大小:"+ $bytes + bytes")}}

正如评论中指出的那样,PoshCode New-CommandWrapper 链接已经失效.以下是完整的相关 cmdlet:

############################################################################## 新命令包装器#### 来自 Windows PowerShell Cookbook (O'Reilly)## 作者:Lee Holmes (http://www.leeholmes.com/guide)############################################################################<#.概要向现有 cmdlet 和函数添加参数和功能..例子New-CommandWrapper Get-Process `-添加参数@{排序方式 = {$newPipeline = {__ORIGINAL_COMMAND__ |Sort-Object -Property $SortBy}}}此示例将SortBy"参数添加到 Get-Process.它实现了这是通过向管道添加 Sort-Object 命令来实现的..例子$parameterAttributes = @'[参数(强制= $true)][验证范围(50,75)][内部]'@New-CommandWrapper Clear-Host `-添加参数@{@{Name = 'MyMandatoryInt';属性 = $parameterAttributes} = {写主机 $MyMandatoryInt读取主机按回车"}}此示例添加了一个新的强制性MyMandatoryInt"参数清除主机.这个参数也被验证在范围内50 到 75.它不会改变管道,但会显示一些在处理原始管道之前屏幕上的信息.#>参数(##要扩展的命令的名称[参数(强制= $true)]$姓名,## 在命令开始之前调用的脚本[ScriptBlock] $Begin,## 为每个输入元素调用的脚本[ScriptBlock] $Process,## 在命令末尾调用的脚本[ScriptBlock] $End,## 要添加的参数及其功能.####哈希表的Key可以是简单的参数名,## 或更高级的参数描述.#### 如果你想添加额外的参数验证(比如一个## 参数类型,) 那么键本身可以是一个带有键的哈希表##名称"和属性".属性"是您将在以下情况下使用的文本## 将此参数定义为函数的一部分.#### 每个哈希表条目的值是一个要调用的脚本块## 选择此参数时.要自定义管道,## 为 $newPipeline 变量分配一个新的脚本块.使用## 特殊文字,__ORIGINAL_COMMAND__,代表原文## 命令.$targetParameters 变量表示一个哈希表## 包含将传递给原始的参数## 命令.[哈希表] $AddParameter)Set-StrictMode -Version 最新## 存储我们要包装的目标命令,以及它的命令类型$目标 = $名称$commandType = "Cmdlet";## 如果一个函数已经存在这个名字(也许它已经存在了##wrapped,) 重命名另一个函数并将链重命名为其新名称.if(测试路径函数:$Name){$target = "$Name";+-"+ [Guid]::NewGuid().ToString().Replace("-","")重命名项目函数:GLOBAL:$Name GLOBAL:$target$commandType = "函数";}##我们用来生成命令代理的模板$proxy = @'__CMDLET_BINDING_ATTRIBUTE__参数(__参数__)开始{尝试 {__自定义_开始__## 访问 REAL Foreach-Object 命令,以便该命令## 包装器不会干扰这个脚本$foreachObject = $executionContext.InvokeCommand.GetCmdlet(Microsoft.PowerShell.CoreForeach-Object")$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('__COMMAND_NAME__',[System.Management.Automation.CommandTypes]::__COMMAND_TYPE__)## TargetParameters 表示参数的哈希表## 我们将传递给包装好的命令$targetParameters = @{}$PSBoundParameters.GetEnumerator() |&$foreachObject {if($command.Parameters.ContainsKey($_.Key)){$targetParameters.Add($_.Key, $_.Value)}}## finalPipeline 代表我们最终将运行的管道$newPipeline = { &$wrappedCmd @targetParameters }$finalPipeline = $newPipeline.ToString()__CUSTOM_PARAMETER_PROCESSING__$steppablePipeline = [ScriptBlock]::Create($finalPipeline).GetSteppablePipeline()$steppablePipeline.Begin($PSCmdlet)} 抓住 {扔}}过程{尝试 {__自定义_过程__$steppablePipeline.Process($_)} 抓住 {扔}}结尾{尝试 {__自定义_END__$steppablePipeline.End()} 抓住 {扔}}动态参数{## 访问真正的 Get-Command、Foreach-Object 和 Where-Object## 命令,以便命令包装器不会干扰此脚本$getCommand = $executionContext.InvokeCommand.GetCmdlet(Microsoft.PowerShell.CoreGet-Command")$foreachObject = $executionContext.InvokeCommand.GetCmdlet(Microsoft.PowerShell.CoreForeach-Object")$whereObject = $executionContext.InvokeCommand.GetCmdlet(Microsoft.PowerShell.CoreWhere-Object")## 找到原命令的参数,去掉一切## else 来自绑定参数列表,因此我们隐藏包装的参数## 命令无法识别.$命令 = &$getCommand __COMMAND_NAME__ - 类型 __COMMAND_TYPE__$targetParameters = @{}$PSBoundParameters.GetEnumerator() |&$foreachObject {if($command.Parameters.ContainsKey($_.Key)){$targetParameters.Add($_.Key, $_.Value)}}## 获取参数列表,因为它将传递给目标命令$argList = @($targetParameters.GetEnumerator() |Foreach-Object { "-$($_.Key)";$_.值})## 获取包裹命令的动态参数,基于## 这个命令的参数$command = $null尝试{$命令 = &$getCommand __COMMAND_NAME__ - 类型 __COMMAND_TYPE__ `-ArgumentList $argList}抓住{}$dynamicParams = @($command.Parameters.GetEnumerator() |&$whereObject { $_.Value.IsDynamic })##对于每个动态参数,将它们添加到动态##我们返回的参数.如果($dynamicParams.Length -gt 0){$paramDictionary = `新对象 Management.Automation.RuntimeDefinedParameterDictionaryforeach ($dynamicParams 中的 $param){$param = $param.Value$arguments = $param.Name, $param.ParameterType, $param.Attributes$newParameter = `新对象 Management.Automation.RuntimeDefinedParameter `$参数$paramDictionary.Add($param.Name, $newParameter)}返回 $paramDictionary}}<#.ForwardHelpTargetName __COMMAND_NAME__.ForwardHelpCategory __COMMAND_TYPE__#>'@## 获取原命令的信息$originalCommand = Get-Command $target$metaData = 新对象 System.Management.Automation.CommandMetaData `$原始命令$proxyCommandType = [System.Management.Automation.ProxyCommand]##生成cmdlet绑定属性,并替换信息##关于目标$proxy = $proxy.Replace("__CMDLET_BINDING_ATTRIBUTE__",$proxyCommandType::GetCmdletBindingAttribute($metaData))$proxy = $proxy.Replace("__COMMAND_NAME__", $target)$proxy = $proxy.Replace("__COMMAND_TYPE__", $commandType)## 存储我们将放入 param() 块中的新文本$newParamBlockCode = "";## 存储我们将在开始块中放入的新文本##(主要是因为参数处理)$beginAdditions = "";##如果用户想添加参数$currentParameter = $originalCommand.Parameters.Count如果($AddParameter){foreach($AddParameter.Keys 中的 $parameter){##获取与该参数关联的代码$parameterCode = $AddParameter[$parameter]##如果是高级参数声明,哈希表## 持有验证和/或类型限制if($parameter -is [Hashtable]){## 将他们的属性和其他信息添加到## 保存参数块添加的变量如果($currentParameter -gt 0){$newParamBlockCode += ",";}$newParamBlockCode += "`n`n "+$parameter.Attributes + "`n";+'$' + $parameter.Name$parameter = $parameter.Name}别的{##如果这是一个简单的参数名称,将其添加到列表中## 参数.代理生成 API 将负责## 将其添加到 param() 块中.$新参数=新对象 System.Management.Automation.ParameterMetadata `$参数$metaData.Parameters.Add($parameter, $newParameter)}$parameterCode = $parameterCode.ToString()## 创建调用其参数代码的模板代码 if##参数被选中.$templateCode = @"if(`$PSBoundParameters['$parameter']){$参数代码## 用代码替换 __ORIGINAL_COMMAND__ 标签##代表原始命令`$alteredPipeline = `$newPipeline.ToString()`$finalPipeline = `$alteredPipeline.Replace('__ORIGINAL_COMMAND__', `$finalPipeline)}"@## 将模板代码添加到我们正在进行的更改列表中## 到 begin() 部分.$beginAdditions += $templateCode$currentParameter++}}## 生成 param() 块$parameters = $proxyCommandType::GetParamBlock($metaData)if($newParamBlockCode) { $parameters += $newParamBlockCode }$proxy = $proxy.Replace('__PARAMETERS__', $parameters)## 更新开始、处理和结束部分$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)## 保存函数包装器写详细 $proxy设置内容函数:GLOBAL:$NAME $proxy## 如果我们要包装一个 cmdlet,请将其隐藏,以免与## 获取帮助和获取命令if($commandType -eqCmdlet"){$originalCommand.Visibility = 私人"}

解决方案

我刚刚安装并使用了 https://github.com/Davlind/PSColor 这是无痛的.它支持 PSGet,因此您可以使用 Install-Module PSColor 轻松安装以获取它.

注意PSColor 的更新分支可用作 Color:https://www.powershellgallery.com/packages/Color/2.1.0(感谢@HackSlash)

对象没有被转换,所以它们仍然支持管道.(它使用了上面提到的 New-CommandWrapper)

它还支持 select-string 等其他东西.

Edit: Original solution at the bottom of this post. For a more up-to-date solution, see the accepted answer, posted by Thraka.

Colorizing Get-Childitem (dir or ls, in other words) isn't a new idea exactly, but I have not been able to locate any ideal approaches to colorizing output in Powershell. There are two general approaches for writing color-ls functions:

  • Intercepting output of Get-Childitem, and re-outputting it as text using Write-Host with the -ForegroundColor parameter. This approach allows as much granularity as possible, but reduces the output of Get-Childitem to text. As most powershell users are aware, Get-Childitem does not output text, rather, it outputs objects. Specifically, a list of FileInfo and DirectoryInfo objects. This allows a great deal of flexibility in handling Get-Childitem output.

  • Pipe the output of Get-Childitem via Invoke-Expression to Foreach-Object, changing the console foreground color before outputting each object. Kind of a mouthful, but the better option because it preserves the type of Get-Childitem's output.

Here is an example of the latter approach, provided by Tim Johnson's 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 $_
        }
    }
}

This code assigns different colors based purely on file extension, but nearly any metric could be substituted to differentiate file types. The above code produces the following output:

This is nearly perfect, but there is one little flaw: the first 3 lines output (Directory path, Column Headers, and horizontal separators) take on the color of the first item in the list. Tim Johnson commented in his blog:

I would rather if the header at the top wasn't always the same color as the first item, but I can't think of any way around that.

Neither can I, unfortunately. That's where Stack Overflow and its powershell gurus come in: I'm looking for a way to colorize Get-Childitem output while preserving the cmdlet's output type, without messing up the color of the header. I've done some experimentation and fiddling with this approach, but have not had any success just yet, as the first single echo call outputs the entire header and first item.

Any questions, comments, or, even better, solutions are welcome.

The Solution With Thanks to jon Z and the others who provided ideas:

Jon Z provided the perfect solution to this problem, which I have polished up a bit to match the scheme in my original question. Here it is, for anyone who is interested. Note that this requires the New-CommandWrapper cmdlet from the Powershell Cookbook. Relevant cmdlet added to bottom of this post. All of this code goes in your profile.

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 ""
}

This produces output that looks like the following screenshot:

If you would like the total file size line at the bottom, simply add the following code:

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")
    }
}

As has been pointed out in the comments, the PoshCode New-CommandWrapper link has died. Here is the relevant cmdlet in full:

##############################################################################
##
## 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.CoreForeach-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.CoreGet-Command")
    $foreachObject = $executionContext.InvokeCommand.GetCmdlet(
        "Microsoft.PowerShell.CoreForeach-Object")
    $whereObject = $executionContext.InvokeCommand.GetCmdlet(
        "Microsoft.PowerShell.CoreWhere-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"
}

解决方案

I just installed and used https://github.com/Davlind/PSColor which was painless. It supports PSGet so you can install easily with Install-Module PSColor to get it.

Note There is an updated fork of PSColor available as Color: https://www.powershellgallery.com/packages/Color/2.1.0 (Thanks @HackSlash)

The objects aren't transformed so they still support piping. (It's using the New-CommandWrapper mentioned above)

It also supports other things like select-string.

这篇关于Powershell:一劳永逸地正确着色 Get-Childitem 输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆