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

Is There a Way to Cause Powershell to Use a Particular Format for a Function's Output?

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

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

这引出了几个问题:

  1. 虽然它们“不存在”,但 Format.ps1xml 文件可以 created/used 在高于 5.1 的 PowerShell 版本中吗?
  2. 无论是否可以,是否有更好的做法向 PowerShell 建议某个函数应如何格式化返回的数据?请注意,'suggest' 固有的是 必须保留 PowerShell 输出的管道性质:用户必须仍然能够将函数的输出通过管道传递给 Format-List 或 ForEach -对象等..

例如,Get-ADUser cmdlet returns 对象由 Format-List 格式化。如果我编写一个名为 Search-ADUser 的函数在内部调用 Get-ADUser 和 returns 其中一些对象,输出也将被格式化为列表。在返回之前将输出通过管道传输到 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
}

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

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

我在发帖前搜索了 force function format #powershell-7.0,但 none 的搜索结果似乎是相关的。

Although they 'don't exist', can Format.ps1xml files be created/used in versions of PowerShell greater than 5.1?

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

    • *.ps1xml文件总是需要这样的定义是不幸的; GitHub issue #7845 asks for an in-memory, API-based alternative (which for type data already exists, via the Update-TypeData cmdlet).
  • 现在只有 PowerShell 附带的格式化数据被硬编码到 PowerShell(核心)可执行文件中,大概是出于性能原因。

is there some better practice for suggesting to PowerShell how a certain function should format returned data?

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

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

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

      • 对于 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] 文字,通过PSTypeName条目指定类型名称:

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

    • 如果在 模块 中定义了依赖此格式化数据的命令,您可以将该文件合并到您的模块中,以便在模块已导入。

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

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


应用于您的示例函数:

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

    • 如您所见,为格式定义创建 XML 既冗长又麻烦,即使没有额外的设置,例如列宽和对齐方式。

    • 更好但更精细的解决方案是将您的函数打包在 module into whose folder you can place the *.ps1mxl file (e.g., SearchAdUserResult.Format.ps1xml) and then instruct PowerShell to load the file on module import, via the FormatsToProcess key in the module manifest (*.psd1) 中 - 例如,FormatsToProcess = 'SearchAdUserResult.Format.ps1xml'

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

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