有没有办法让 Powershell 对函数的输出使用特定格式?

问题描述

我希望建议(也许强制,但我对语义还不是很确定)PowerShell 函数输出的特定格式。

about_Format.ps1xml(针对 PowerShell 7.1 版本)说:“从 PowerShell 6 开始,认视图在 PowerShell 源代码中定义。 PowerShell 5.1 及更早版本中的 Format.ps1xml 文件在 PowerShell 6 及更高版本中不存在。'。然后文章继续解释如何使用 Format.ps1xml 文件来更改对象的显示等。这不是很明确:'不存在' -ne '不能存在'...

这引出了几个问题:

  1. 虽然它们“不存在”,但可以在 5.1 以上的 PowerShell 版本中创建/使用 Format.ps1xml 文件吗?
  2. 不管他们能不能,是否有更好的做法来向 PowerShell 建议某个函数应该如何格式化返回的数据?请注意,“建议”中固有的是必须保留 PowerShell 输出的管道性质用户仍然必须能够将函数输出通过管道传输到 Format-List 或 ForEach-Object 等。

例如,Get-ADUser cmdlet 返回由 Format-List 格式化的对象。如果我编写一个名为 Search-ADUser函数,该函数在内部调用 Get-ADUser 并返回其中一些对象,则输出也将格式化为列表。在返回之前将输出传送到 Format-Table 不满足我的要求,因为输出将不会被视为管道中的单独对象。

示例代码

function Search-ADUser {
  param (
    $Name,[ValidateNotNullOrEmpty()][string[]]$Properties = @('Enabled','SamAccountName','Name','emailAddress','proxyAddresses')
  )
  return Get-ADUser -Filter ('name -like "*{0}*"' -F $Name) -Properties $Properties | Select-Object $Properties
}

最佳答案应解决这两个问题,尽管第二个更为突出。

不可接受的答案包括建议函数不应该强制执行格式,和/或用户应该将函数输出传送到他们选择的格式化程序。这是一个非常主观的立场,是否为多数人所持与问题无关。

我在发帖前搜索force function format #powershell-7.0,但似乎没有任何搜索结果相关。

解决方法

尽管它们“不存在”,但可以在 PowerShell 5.1 以上的版本中创建/使用 Format.ps1xml 文件吗?

  • 是的;事实上,任何第三方代码都必须使用它们来定义自定义格式。

    • 不幸的是,这些定义总是需要 *.ps1xml 文件GitHub issue #7845 请求内存中的、基于 API 的替代方案(对于 type 数据已经存在,通过 Update-TypeData cmdlet)。
  • 现在只有 PowerShell 附带的格式化数据被硬编码到 PowerShell(核心)可执行文件中,大概是出于性能原因。

是否有一些更好的做法可以向 PowerShell 建议某个函数应该如何格式化返回的数据?

缺乏基于 API 的方法来定义格式化数据需要以下方法:

  • 确定格式应适用的 .NET 类型的全名。

    • 如果格式应该应用到 [pscustomobject] 个实例,您需要 (a) 选择 一个唯一的(虚拟)类型名称,并且 (b) 将其分配给[pscustomobject] 实例通过 PowerShell 的 ETS (Extended Type System);例如:

      • 对于由 Select-Object cmdlet 创建的 [pscustomobject] 实例:

        # Assign virtual type name "MyVirtualType" to the objects output
        # by Select-Object
        Get-ChildItem *.txt | Select-Object Name,Length | ForEach-Object {
          $_.pstypenames.Insert(0,'MyVirtualType'); $_
        }
        
      • 对于 [pscustomobject] literals,通过 PSTypeName 条目指定类型名称:

        [pscustomobject] @{
          PSTypeName = 'MyVirtualType'
          foo = 1
          bar = 2
        }
        
  • 为该类型创建一个 *.ps1mxl 文件并将其加载到每个会话中。

    • 如果依赖此格式数据的命令在模块中定义,您可以将文件合并到您的模块中,以便在导入模块时自动执行。 >

    • 有关创作此类文件的帮助,请参阅:

GitHub proposal #10463 要求大大简化体验,同时支持指定所需格式的扩展 [OutputType()] 属性。


应用于您的示例函数

  • 以下函数在会话中的第一次调用时为其输出类型按需创建一个(临时)*.ps1xml 文件,以确保(隐式) Format-Table 格式应用于所有 5 个属性(默认情况下,5 个或更多属性导致(隐式)Format-List 格式)。

    • 如您所见,即使没有其他设置(例如列宽和对齐方式),为格式定义创建 XML 也是冗长而繁琐的。

    • 更好但更复杂的解决方案是将您的函数打包在 module 中,您可以将 *.ps1mxl 文件(例如,SearchAdUserResult.Format.ps1xml)放入其文件夹中,然后通过 module manifest (FormatsToProcess) 中的 *.psd1 键,指示 PowerShell 在模块导入时加载文件 - 例如,FormatsToProcess = 'SearchAdUserResult.Format.ps1xml'

  • 请注意,您也可以直接为 Get-ADUser 输出的 *.ps1mxl 实例创建 Microsoft.ActiveDirectory.Management.ADUser 文件,但这样做会将会话范围内的格式应用于任何命令发出这样的对象。

function Search-ADUser {
  param (
    $Name,[ValidateNotNullOrEmpty()][string[]]$Properties = @('Enabled','SamAccountName','Name','emailAddress','proxyAddresses')
  )

  # The self-chosen ETS type name.
  $etsTypeName = 'SearchAdUserResult'

  # Create the formatting data on demand.
  if (-not (Get-FormatData -ErrorAction Ignore $etsTypeName)) {

    # Create a temporary file with formatting definitions to pass to 
    # Update-FormatData below.
    $tempFile = Join-Path ([IO.Path]::GetTempPath()) "$etsTypeName.Format.ps1xml"

    # Define a table view with all 5 properties.
    @"
<Configuration>
<ViewDefinitions>
    <View>
      <Name>$etsTypeName</Name>
      <ViewSelectedBy>
        <TypeName>$etsTypeName</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Enabled</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>SamAccountName</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>emailAddress</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>proxyAddresses</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>
"@ > $tempFile

    # Load the formatting data into the current session.
    Update-FormatData -AppendPath $tempFile

    # Clean up.
    Remove-Item $tempFile
  }

  # Call Get-ADUser and assign the self-chosen ETS type name to the the output.
  # Note: To test this with a custom-object literal,use the following instead of the Get-ADUser call:
  #      [pscustomobject] @{ Enabled = $true; SamAccountName = 'jdoe'; Name = 'Jane Doe'; emailAddress = 'jdoe@example.org'; proxyAddresses = 'janedoe@example.org' }
  Get-ADUser -Filter ('name -like "*{0}*"' -F $Name) -Properties $Properties | Select-Object $Properties | ForEach-Object {
     $_.pstypenames.Insert(0,$etsTypeName); $_
  }

}

然后您将看到基于格式数据的所需表格输出;例如:

Enabled SamAccountName Name     emailAddress     proxyAddresses
------- -------------- ----     ------------     --------------
True    jdoe           Jane Doe jdoe@example.org janedoe@example.org

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...