当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

作者:精品下载站 日期:2024-12-14 08:05:53 浏览:12 分类:玩电脑

星期五乐趣 - 用 PowerShell 画一幅漂亮的图画


几个月前,我写了一篇关于我的 PSClock 模块的文章。这是一组 PowerShell 命令,用于使用透明 WPF 表单创建数字时钟。您可以从 PowerShell 库安装该模块。该存储库可以在 https://github.com/jdhitsolutions/PSClock 找到。由于我的 Windows 壁纸全天都会变化,有时我需要更改时钟颜色以使其更易于阅读。挑战在于颜色选项来自 [System.Drawing.Color]。我可以使用 PapayaWhip、Cornsilk 和 Moccasin 等值。但这些颜色是什么样子的呢?让我们玩得开心并找出答案。

第一步是确保我可以在 PowerShell 会话中使用 [System.Drawing.Color],这更像是一个安全网。我将使用 Add-Type 加载程序集。

Try {
    Add-Type -AssemblyName system.drawing -ErrorAction Stop
}
Catch {
    Throw "These functions require the [System.Drawing.Color] .NET Class"
}

据我所知,PowerShell 无法直接从此类渲染颜色值。然而,事实上,PowerShell 可以使用 ANSI 转义序列渲染颜色,但在 PowerShell ISE 中则不行。我们将朝那个方向前进。

获取颜色值

我可以使用这样的语法直接从类中获取颜色。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

属性 R、G 和 B 是红色、绿色和蓝色值。我稍后会需要这些——首先,简单介绍一下这门课。

我怎么知道颜色名称?我使用 PSReadline 询问 PowerShell。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

在 :: 运算符之后,我按 Ctrl+Space 并在出现提示时回答 y。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

对于那些熟悉使用 .NET 类的人来说,“问题”是这不是一个枚举。你不能使用这样的代码:

[enum]::GetNames([System.ConsoleColor])

但是,我可以使用 GetProperties() 方法,选择名称属性并过滤掉与颜色名称不匹配的属性。

[system.drawing.color].GetProperties().name | Where-Object { $_ -notmatch "^\bIs|Name|[RGBA]\b" }

一旦知道名称,我就可以获取上面所做的值或使用 FromName() 方法。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

转换为 ANSI

正如我之前提到的,如果我有 RBG 值,我可以构造一个 256 色 ANSI 转义序列。最简单的方法是在 PowerShell 7.2 中对 $PSStyle.Foreground 使用 FromRGB() 方法。

$psstyle.Foreground.fromrgb(127,255,0)

或者您可以使用实际的 ANSI 序列。

"$([char]27)[38;2;{0};{1};{2}m" -f 127,255,0

如果您使用 Chartreuse RBG 值尝试此操作,除了提示颜色的部分变化之外,您不会看到任何其他内容。那是因为这只是序列的开头部分。要使用它,请构建一个如下所示的字符串:

#PS 7.2
$ansi = $PSStyle.Foreground.FromRgb(127,255,0)
"$($ansi)Chartreuse$($psstyle.Reset)"

#Other
$ansi = "$([char]27)[38;2;{0};{1};{2}m" -f 127,255,0
"$($ansi)Chartreuse$([char]27)[0m"

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

工具制造

此时,我应该已经拥有了我需要的所有部件。我可以获得 [System.Drawing.Color.RGB 值]我可以使用这些值来创建 ANSI 序列。最后,我可以构建一个示例字符串。

$name = "Turquoise"
$color = [System.Drawing.Color]::FromName($Name)
$ansi = $PSStyle.Foreground.FromRgb($color.R,$color.G,$color.B)
"$($ansi)$Name$($psstyle.Reset)"

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

您几乎可以看到 PowerShell 函数自行编写。我本可以构建一个整体函数来完成所有事情,但我决定将其分解为更细粒度的级别。具有独立的功能使它们易于重复使用。这也使它们更容易进行 Pester 测试。特别是因为我正在调用几个您无法正确模拟的 .NET 方法。但是,我可以模拟这样的函数。

function Get-RGB {
    [cmdletbinding()]
    [OutputType("RGB")]
    Param(
        [Parameter(Mandatory, HelpMessage = "Enter the name of a system color like Tomato")]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )
    Try {
        $Color = [System.Drawing.Color]::FromName($Name)
        [PSCustomObject]@{
            PSTypeName = "RGB"
            Name       = $Name
            Red        = $color.R
            Green      = $color.G
            Blue       = $color.B
        }
    }
    Catch {
        Throw $_
    }
}

该函数将一个简单的对象写入管道。我可以使用该输出来创建 ANSI 序列。

function Convert-RGBtoAnsi {
    #This will write an opening ANSI escape sequence to the pipeline
    [cmdletbinding()]
    [OutputType("String")]
    Param(
        [parameter(Position = 0, ValueFromPipelineByPropertyName)]
        [int]$Red,
        [parameter(Position = 1, ValueFromPipelineByPropertyName)]
        [int]$Green,
        [parameter(Position = 2, ValueFromPipelineByPropertyName)]
        [int]$Blue
    )
    Process {
        <#
        For legacy powershell session you could create a string like this:
        "$([char]27)[38;2;{0};{1};{2}m" -f $red,$green,$blue
        #>
        $psstyle.Foreground.FromRgb($Red, $Green, $Blue)
    }
}

您会注意到参数的定义名称与 RGB 对象相同,并采用管道输入。那是因为我知道我想要运行这样的命令。

$name ="DeepSkyBlue"
$ansi = Get-RGB $name | Convert-RGBtoAnsi
"$($ansi)$Name$($psstyle.Reset)"

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

最后一个命令使运行这些命令变得更加容易。

Function Get-DrawingColor {
    [cmdletbinding()]
    [alias("gdc")]
    [OutputType("PSColorSample")]
    Param(
        [Parameter(Position = 0, HelpMessage = "Specify a color by name. Wildcards are allowed.")]
        [ValidateNotNullOrEmpty()]
        [string[]]$Name
    )

    Write-Verbose "Starting $($MyInvocation.MyCommand)"

    if ($PSBoundParameters.ContainsKey("Name")) {
        if ($Name[0] -match "\*") {
            Write-Verbose "Finding drawing color names that match $name"
            $colors = [system.drawing.color].GetProperties().name | Where-Object { $_ -like $name[0] }
        }
        else {
            $colors = @()
            foreach ($n in $name) {
                if ($n -as [system.drawing.color]) {
                    $colors += $n
                }
                else {
                    Write-Warning "The name $n does not appear to be a valid System.Drawing.Color value. Skipping this name."
                }
                Write-Verbose "Using parameter values: $($colors -join ',')"

            } #foreach name
        } #else
    } #if PSBoundParameters contains Name
    else {
        Write-Verbose "Geting all drawing color names"
        $colors = [system.drawing.color].GetProperties().name | Where-Object { $_ -notmatch "^\bIs|Name|[RGBA]\b" }
    }
    Write-Verbose "Processing $($colors.count) colors"
    if ($colors.count -gt 0) {
        foreach ($c in $colors) {
            Write-Verbose "...$c"
            $ansi = Get-RGB $c -OutVariable rgb | Convert-RGBtoAnsi
            #display an ANSI formatted sample string
            $sample = "$ansi$c$($psstyle.reset)"

            #write a custom object to the pipeline
            [PSCustomObject]@{
                PSTypeName = "PSColorSample"
                Name       = $c
                RGB        = $rgb
                ANSIString = $ansi.replace("`e", "``e")
                ANSI       = $ansi
                Sample     = $sample
            }
        }
    } #if colors.count > 0
    else {
        Write-Warning "No valid colors found."
    }
    Write-Verbose "Ending $($MyInvocation.MyCommand)"
}

我可以通过名称指定颜色。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

我还不知道使用此输出的所有方式,因此我正在创建一个丰富的对象。 ANSI 属性有一个值,但它不显示,因为它是实际的 ANSI 序列。这就是为什么我包含一个 ANSIString 属性,该属性将值显示为我可以在脚本中使用的字符串。

默认行为是显示所有内容。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

并非每种颜色都使用 ANSI 显示,您的背景颜色也可能会影响显示,但这对于我的工作来说已经足够接近了。顺便说一句,该函数接受通配符。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

此屏幕截图来自 VS Code。

自定义格式

您可能注意到我经常使用 Format-Table 来获得更好看的输出。事实上,我可能喜欢只显示示例的输出。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

但让我们让这变得简单。我从 Get-DrawingColor 为自定义对象创建了一个格式 ps1xml 文件。如果您返回代码,您会看到我正在分配一个类型名称。这允许我创建该文件。

<!--
Format type data generated 02/08/2022 17:32:56 by PROSPERO\Jeff

This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.

https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 02/08/2022 17:32:56 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>PSColorSample</TypeName>
      </ViewSelectedBy>
      <WideControl>
        <!--Delete the AutoSize node if you want to use PowerShell defaults.
        <AutoSize />-->
        <WideEntries>
          <WideEntry>
            <WideItem>
              <PropertyName>Sample</PropertyName>
            </WideItem>
          </WideEntry>
        </WideEntries>
      </WideControl>
    </View>
    <View>
      <!--Created 02/08/2022 17:35:40 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>PSColorSample</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>14</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>RGB</Label>
            <Width>31</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>ANSIString</Label>
            <Width>22</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Sample</Label>
            <Width>37</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <!--
            By default the entries use property names, but you can replace them with scriptblocks.
            <ScriptBlock>$_.foo /1mb -as [int]</ScriptBlock>
-->
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>RGB</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ANSIString</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Sample</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

在包含所有函数的脚本文件中,我添加此行来加载格式化文件。

Update-FormatData $PSScriptRoot\pscolorsample.format.ps1xml

我将宽布局设置为默认布局。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

我还创建了一个自定义表视图,它省略了 ANSI 属性,因为没有什么可看的。

[玩转系统] 星期五乐趣 - 用 PowerShell 画一幅漂亮的图画

概括

我承认您可能对这些功能没有迫切的需求。但我希望您遵循我的开发流程。考虑一下您的职能如何协同工作。将丰富的对象写入管道并使用格式化文件提供默认输出。

周末愉快。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯