I wish to suggest (perhaps enforce, but I am not firm on the semantics yet) a particular format for the output of a PowerShell function.
about_Format.ps1xml (versioned for PowerShell 7.1) says this: 'Beginning in PowerShell 6, the default views are defined in PowerShell source code. The Format.ps1xml files from PowerShell 5.1 and earlier versions don't exist in PowerShell 6 and later versions.'. The article then goes on to explain how Format.ps1xml files can be used to change the display of objects, etc etc. This is not very explicit: 'don't exist' -ne 'cannot exist'...
This begs several questions:
For example, the Get-ADUser
cmdlet returns objects formatted by Format-List
. If I write a function called Search-ADUser
that calls Get-ADUser
internally and returns some of those objects, the output will also be formatted as a list. Piping the output to Format-Table
before returning it does not satisfy my requirements, because the output will then not be treated as separate objects in a pipeline.
Example code:
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
}
The best answers should address both questions, although the second is more salient.
Unacceptable answers include suggestions that the function should not enforce a format, and/or that the user should pipe the output of the function to their formatter of choice. That is a very subjective stance, and whether it is held by the majority or not is irrelevant to the question.
I searched force function format #powershell-7.0
before posting, but none of the search results appeared to be relevant.
Although they 'don't exist', can
Format.ps1xml
files be created/used in versions of PowerShell greater than 5.1?
Yes; in fact any third-party code must use them to define custom formatting.
That *.ps1xml
files are invariably needed for such definitions is unfortunate; GitHub issue #7845 asks for an in-memory, API-based alternative (which for type data already exists, via the Update-TypeData
cmdlet).
However, in the context of modules, the need for files is less inconvenient, as they can be bundled with the module and are automatically loaded when referenced from the module manifest's FormatsToProcess
entry.
It is only the formatting data that ships with PowerShell that is now hardcoded into the PowerShell (Core) executable, presumably for performance reasons.
is there some better practice for suggesting to PowerShell how a certain function should format returned data?
The lack of an API-based way to define formatting data requires the following approach:
Determine the full name of the .NET type(s) to which the formatting should apply.
If it is [pscustomobject]
instances that the formatting should apply to, you need to (a) choose a unique (virtual) type name and (b) assign it to the [pscustomobject]
instances via PowerShell's ETS (Extended Type System); e.g.:
For [pscustomobject]
instances created by the Select-Object
cmdlet:
# 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'); $_
}
For [pscustomobject]
literals, specify the type name via a PSTypeName
entry:
[pscustomobject] @{
PSTypeName = 'MyVirtualType'
foo = 1
bar = 2
}
Create a *.ps1mxl
file for that type and load it into every session.
If the commands that rely on this formatting data are defined in a module, you can incorporate the file into your module so that it is automatically automatically when the module is imported.
For help on authoring such files, see:
GitHub proposal #10463 asks for a greatly simplified experience, along the lines of supporting extended [OutputType()]
attributes that specify the desired formatting.
Applied to your sample function:
The following function creates a (temporary) *.ps1xml
file for its output type on demand, on the first call in the session, so as to ensure that (implicit) Format-Table
formatting is applied, for all 5 properties (by default, 5 or more properties result in (implicit) Format-List
formatting).
As you can see, creating the XML for the formatting definitions is verbose and cumbersome, even without additional settings, such as column width and alignment.
A better, but more elaborate solution would be to package your function in a 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
) - e.g., FormatsToProcess = 'SearchAdUserResult.Format.ps1xml'
Note that you could alternatively create the *.ps1mxl
file directly for the Microsoft.ActiveDirectory.Management.ADUser
instances that Get-ADUser
outputs, but doing so would apply the formatting session-wide, to any command that emits such objects.
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 = '[email protected]'; proxyAddresses = '[email protected]' }
Get-ADUser -Filter ('name -like "*{0}*"' -F $Name) -Properties $Properties | Select-Object $Properties | ForEach-Object {
$_.pstypenames.Insert(0, $etsTypeName); $_
}
}
You'll then see the desired tabular output based on the format data; e.g.:
Enabled SamAccountName Name emailAddress proxyAddresses
------- -------------- ---- ------------ --------------
True jdoe Jane Doe [email protected] [email protected]