问题描述
我希望建议(也许强制,但我对语义还不是很确定)PowerShell 函数输出的特定格式。
about_Format.ps1xml(针对 PowerShell 7.1 版本)说:“从 PowerShell 6 开始,默认视图在 PowerShell 源代码中定义。 PowerShell 5.1 及更早版本中的 Format.ps1xml 文件在 PowerShell 6 及更高版本中不存在。'。然后文章继续解释如何使用 Format.ps1xml 文件来更改对象的显示等。这不是很明确:'不存在' -ne '不能存在'...
这引出了几个问题:
- 虽然它们“不存在”,但可以在 5.1 以上的 PowerShell 版本中创建/使用 Format.ps1xml 文件吗?
- 不管他们能不能,是否有更好的做法来向 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