PowerShell Custom Object Formatting
On this article I'm going to touch a field of PowerShell development that most are not aware of. That is formatting. Every PowerShell user has used the "Format-Table" and "Format-List" cmdlets or even the "Format-Custom" to display data in a manner that suits them. What happens though when you have objects that you'll like to format a bit differently or set the default view for? Then its time to use formatting.
To start off, we're going to create a custom module. Pick a name for the module - mine is going to be "TestModule" - and create a directory with that name. Then create the module manifest using the "New-ModuleManifest" cmdlet and a script file with the ".psm1" extension. You should end up with a structure like the following:
At this point, there are no functions in this module, to add one we have to define it in the script file and then configure the manifest accordingly.
Below is a function that returns a custom object:
Now that the function is defined , we have to update the module manifest to include the script file and export the function. Open the file, locate the "NestedModules" directive, uncomment it and add the "TestModule.psm1". Don't forget to add the name of the function to the "FunctionsToExport".
The module is not located in any of the predefined module locations so we have to import it using the path of the manifest file:
As shown above, the function is available in the module and when called, returns a custom object.
Before starting with the formatting, let's see a couple of ways to create custom objects. The first way is by using a hashtable with the properties of the object just like we did in the test function above. Although this is pretty straight forward, the type of the object is not well defined, thus we cannot link formatting to the object.
To create a custom object type, we have to define a class. PowerShell 5 and later provides the ability to create classes directly in the scripts:
In previous versions of PowerShell, you can define a custom type using the "Add-Type" cmdlet:
Add the above to the script file of the module to make the types available.
Now let's create a couple of functions that return objects based on the above types. To create the objects, use the New-PSObject cmdlet just like below:
The objects are empty but the access level of the properties is public so you can update them. I've added two functions in the module, one for each type:
Don't forget to update the "FunctionsToExport" in the manifest file to make them available!
We're done with the types and the functions in the module, I'ts time to start working on the formatting. The output of the above cmdlets will be formatted in a table view:
To update the formatting of objects in the module, you have to create a formatting file. Formatting files are xml files (with the .ps1.xml extension) that contain information on how to format the objects, the default view type and the properties to display by default. The syntax is not too difficult, let's take a look at an example.
A table view for the "CustomObjectType1" type is defined here, with the columns ID, FirstName and LastName. I've chosen to omit the Email property from the view just to make the output simpler:
Adding a list view is equally easy:
The Email property is also omitted in this view, and labels are defined for the properties. The output when selecting the list view is going to be:
Formatting objects can greatly enhance the experience for your modules and their functions, since it will make it easier for the users to get the information they need by presenting them the right set of properties in the right view.
Views are not limited to selecting properties and labels, but can also do things like setting maximum width on columns, grouping and displaying calculated properties. More information is available on the PowerShell docs!
To start off, we're going to create a custom module. Pick a name for the module - mine is going to be "TestModule" - and create a directory with that name. Then create the module manifest using the "New-ModuleManifest" cmdlet and a script file with the ".psm1" extension. You should end up with a structure like the following:
PS C:\> New-Item -ItemType dir -Name TestModule
Directory: C:\Users\cpolydorou\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 19/7/2018 11:26 πμ TestModule
PS C:\> New-ModuleManifest -Path .\TestModule\TestModule.psd1
PS C:\> New-Item -ItemType File -Path .\TestModule\TestModule.psm1
Directory: C:\Users\cpolydorou\Desktop\TestModule
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 19/7/2018 11:26 πμ 0 TestModule.psm1
PS C:\> Get-ChildItem -Path .\TestModule
Directory: C:\Users\cpolydorou\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 19/7/2018 11:26 πμ TestModule
PS C:\> New-ModuleManifest -Path .\TestModule\TestModule.psd1
PS C:\> New-Item -ItemType File -Path .\TestModule\TestModule.psm1
Directory: C:\Users\cpolydorou\Desktop\TestModule
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 19/7/2018 11:26 πμ 0 TestModule.psm1
PS C:\> Get-ChildItem -Path .\TestModule
Directory: C:\Users\cpolydorou\Desktop\TestModule
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 19/7/2018 11:26 πμ 7740 TestModule.psd1
-a---- 19/7/2018 11:26 πμ 0 TestModule.psm1
PS C:\>
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 19/7/2018 11:26 πμ 7740 TestModule.psd1
-a---- 19/7/2018 11:26 πμ 0 TestModule.psm1
PS C:\>
At this point, there are no functions in this module, to add one we have to define it in the script file and then configure the manifest accordingly.
Below is a function that returns a custom object:
Function Test-Function { $props = @{ ID = "0001" FirstName = "John" LastName = "Smith" Email = "jsmith@domain.com" } New-Object -TypeName PSObject -Property $props }
Now that the function is defined , we have to update the module manifest to include the script file and export the function. Open the file, locate the "NestedModules" directive, uncomment it and add the "TestModule.psm1". Don't forget to add the name of the function to the "FunctionsToExport".
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess NestedModules = @('TestModule.psm1') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @("Test-Function")
The module is not located in any of the predefined module locations so we have to import it using the path of the manifest file:
PS C:\> Import-Module .\TestModule\TestModule.psd1
PS C:\> get-module
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 3.1.0.0 Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Con...
Manifest 3.1.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...}
Script 1.2 PSReadline {Get-PSReadlineKeyHandler, Get-PSReadlineOption, Remove-PS...
Manifest 1.0 TestModule Test-Function
PS C:\> Test-Function
Email : jsmith@domain.com
ID : 0001
FirstName : John
LastName : Smith
PS C:\>
PS C:\> get-module
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 3.1.0.0 Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Con...
Manifest 3.1.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...}
Script 1.2 PSReadline {Get-PSReadlineKeyHandler, Get-PSReadlineOption, Remove-PS...
Manifest 1.0 TestModule Test-Function
PS C:\> Test-Function
Email : jsmith@domain.com
ID : 0001
FirstName : John
LastName : Smith
PS C:\>
As shown above, the function is available in the module and when called, returns a custom object.
Before starting with the formatting, let's see a couple of ways to create custom objects. The first way is by using a hashtable with the properties of the object just like we did in the test function above. Although this is pretty straight forward, the type of the object is not well defined, thus we cannot link formatting to the object.
To create a custom object type, we have to define a class. PowerShell 5 and later provides the ability to create classes directly in the scripts:
class CustomObjectType1 { [string]$ID [string]$FirstName [string]$LastName [string]$Email }
In previous versions of PowerShell, you can define a custom type using the "Add-Type" cmdlet:
Add-Type @' namespace TestModule { public class CustomObjectType2 { public string ID; public string FirstName; public string LastName; public string Email; } } '@
Add the above to the script file of the module to make the types available.
Now let's create a couple of functions that return objects based on the above types. To create the objects, use the New-PSObject cmdlet just like below:
PS C:\> New-Object -TypeName CustomObjectType1
ID FirstName LastName Email
-- --------- -------- -----
PS C:\> New-Object -TypeName TestModule.CustomObjectType2
ID FirstName LastName Email
-- --------- -------- -----
PS C:\>
ID FirstName LastName Email
-- --------- -------- -----
PS C:\> New-Object -TypeName TestModule.CustomObjectType2
ID FirstName LastName Email
-- --------- -------- -----
PS C:\>
The objects are empty but the access level of the properties is public so you can update them. I've added two functions in the module, one for each type:
Function Test-Function1 { $obj = New-Object -TypeName CustomObjectType1 $obj.ID = "0002" $obj.FirstName = "Nick" $obj.LastName = "Johnson" $obj.Email = "N.Johnson@domain.com" Write-Output $obj } Function Test-Function2 { $obj = New-Object -TypeName TestModule.CustomObjectType2 $obj.ID = "0003" $obj.FirstName = "Claire" $obj.LastName = "Williams" $obj.Email = "C.Williams@domain.com" Write-Output $obj }
Don't forget to update the "FunctionsToExport" in the manifest file to make them available!
We're done with the types and the functions in the module, I'ts time to start working on the formatting. The output of the above cmdlets will be formatted in a table view:
PS C:\> Test-Function1
ID FirstName LastName Email
-- --------- -------- -----
0002 Nick Johnson N.Johnson@domain.com
PS C:\> Test-Function2
ID FirstName LastName Email
-- --------- -------- -----
0003 Claire Williams C.Williams@domain.com
PS C:\>
ID FirstName LastName Email
-- --------- -------- -----
0002 Nick Johnson N.Johnson@domain.com
PS C:\> Test-Function2
ID FirstName LastName Email
-- --------- -------- -----
0003 Claire Williams C.Williams@domain.com
PS C:\>
To update the formatting of objects in the module, you have to create a formatting file. Formatting files are xml files (with the .ps1.xml extension) that contain information on how to format the objects, the default view type and the properties to display by default. The syntax is not too difficult, let's take a look at an example.
<?xml version="1.0" encoding="utf-16"?> <Configuration> <ViewDefinitions> <View> <Name>CustomObjectType1-View1</Name> <ViewSelectedBy> <TypeName>CustomObjectType1</TypeName> </ViewSelectedBy> <TableControl> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>ID</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>FirstName</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>LastName</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> <AutoSize/> </TableControl> </View> </ViewDefinitions> </Configuration>
A table view for the "CustomObjectType1" type is defined here, with the columns ID, FirstName and LastName. I've chosen to omit the Email property from the view just to make the output simpler:
PS C:\> Test-Function1
ID FirstName LastName
-- --------- --------
0002 Nick Johnson
PS C:\>
ID FirstName LastName
-- --------- --------
0002 Nick Johnson
PS C:\>
Adding a list view is equally easy:
<View> <Name>CustomObjectType1-View2</Name> <ViewSelectedBy> <TypeName>CustomObjectType1</TypeName> </ViewSelectedBy> <ListControl> <ListEntries> <ListEntry> <ListItems> <ListItem> <Label>ID</Label> <PropertyName>ID</PropertyName> </ListItem> <ListItem> <Label>First Name</Label> <PropertyName>FirstName</PropertyName> </ListItem> <ListItem> <Label>Surname</Label> <PropertyName>LastName</PropertyName> </ListItem> </ListItems> </ListEntry> </ListEntries> </ListControl> </View>
The Email property is also omitted in this view, and labels are defined for the properties. The output when selecting the list view is going to be:
PS C:\> Test-Function1 | Format-List
ID : 0002
First Name : Nick
Surname : Johnson
PS C:\>
ID : 0002
First Name : Nick
Surname : Johnson
PS C:\>
Formatting objects can greatly enhance the experience for your modules and their functions, since it will make it easier for the users to get the information they need by presenting them the right set of properties in the right view.
Views are not limited to selecting properties and labels, but can also do things like setting maximum width on columns, grouping and displaying calculated properties. More information is available on the PowerShell docs!