When I do "Get-Module -ListAvailable", powershell will print 169 modules. For example:
Directory: C:\Program Files (x86)\Microsoft SQL Server\150\Tools\PowerShell\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 15.0 SQLPS {Backup-SqlDatabase, Save-SqlMigrationReport, Invoke-PolicyEvaluation, Resto...
Directory: C:\Users\user\Documents\WindowsPowerShell\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 3.0.1 DotNetVersionLister Get-STDotNetVersion
Script 1.4.7 PackageManagement {Find-Package, Get-Package, Get-PackageProvider, Get-PackageSource...}
Script 2.2.5 PowerShellGet {Find-Command, Find-DSCResource, Find-Module, Find-RoleCapability...}
Script 2.2.16 VSSetup {Get-VSSetupInstance, Select-VSSetupInstance}
Directory: C:\Program Files\WindowsPowerShell\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 1.3.1 Configuration {Import-Configuration, Export-Configuration, Get-StoragePath, Add-MetadataCo...
When I capture this in an array: "$m = Get-Module -ListAvailable" It seems like just a simple array, yet it also prints in these sections.
How is this done?
There doesn't even seem to be a "Directory" property on the PSModuleInfo objects.
CodePudding user response:
Powershell have its own formatting engine. Whenever you use that cmdlet, you output a list of System.Management.Automation.PSModuleInfo
objects.
Before printing the object "raw", Powershell check if there is a predefined formatting available for the type and if so, apply it. What you see is the result of that transformation.
Up to PS 5.1, this was done through formatting configuration file, defined as *.ps1xml files. From PS6.0 and newer, predefined formats are now included directly in the source code but you can still create additional format files as needed.
You can view the loaded format type using the Get-FormatData
cmdlet.
If you are interested in the Get-Module
cmdlet specifically, check out (Get-FormatData -TypeName System.Management.Automation.PSModuleInfo).FormatViewDefinition
. You will see something like this:
Name Control
---- -------
Module System.Management.Automation.TableControl
Module System.Management.Automation.WideControl
Module System.Management.Automation.ListControl
This mean that any objects of that type have special instructions concerning the way that it should output its object. In that case, it includes grouping by path and displaying the specific columns (ModuleType, Version, Name, ExportedCommands). Powershell did not chose to display those properties by itself, it got its instructions from the predefined type on what to display.
In the case of PSModuleInfo
type, we can see that there is 3 custom views for the type. One for table view (which is the default shown), one for list and wide, which instruct what to show when you use Format-List
& Format-Wide
.
From MS doc
The display format for the objects that are returned by commands (cmdlets, functions, and scripts) are defined by using formatting files (format.ps1xml files). Several of these files are provided by PowerShell to define the display format for those objects returned by PowerShell-provided commands, such as the System.Diagnostics.Process object returned by the Get-Process cmdlet. However, you can also create your own custom formatting files to overwrite the default display formats or you can write a custom formatting file to define the display of objects returned by your own commands.
PowerShell uses the data in these formatting files to determine what is displayed and how the displayed data is formatted. The displayed data can include the properties of an object or the value of a script.
You can create your own files (*.ps1xml) and include them in your modules or load them in your sessions to modify the way the output is displayed.
You can also add formatting to the output of your functions by defining a default display set (aka what properties should be displayed).
For instance, take this simple function:
Function Get-EmployeesInfos() {
$Output = @(
[PSCustomObject]@{
FirstName = 'RObert'
LastName = 'Samson'
SocialSecurityNumber = '123 344 555'
Age = '32'
Salary = '100000'
},
[PSCustomObject]@{
FirstName = 'Pablo'
LastName = 'Morrison'
SocialSecurityNumber = '123 345 555'
Age = '22'
Salary = '10000'
}
)
# Default display set
$defaultDisplaySet = 'FirstName', 'LastName'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
$Output | Add-Member MemberSet PSStandardMembers ([System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)) -Force
return $Output
return $Output
}
Without any default display set, you would get your standard output with all the properties listed.
With the default display set added, here is the new output.
Both outputs do contains the same information, but the console have a special formatting applied to it to show only what is most important, useful, etc...
You can use formatting views to:
- Colorize output
- Create trees
- Change output based on condition
- Add virtual properties
- define column width
- define displayed column title
- etc...
References:
4Sysops - Formatting object output in Powershell with Format.ps1xml files
CodePudding user response:
The reason that Get-Module
is showing the result in groups is because that is the default format for Module
objects whenever PowerShell shows them to the user. It's not a specific feature of the Get-Module
cmdlet as such.
This is convenient facility in general because you can then use cmdlets such as Sort-Object
and Where-Object
to sort and filter the results and then have the results shown in groups afterwards.
In the following example, the results are filtered and then shown in groups. The significance is that neither Get-Module
nor Where-Object
is aware that the end result will be shown in groups; they just deal with objects.
PS> Get-Module -ListAvailable | Where-Object Name -Match Read
Directory: C:\program files\powershell\7\Modules
ModuleType Version PreRelease Name
---------- ------- ---------- ----
Script 2.1.0 PSReadLine ...
Binary 2.0.3 ThreadJob ...
Directory: C:\Program Files\WindowsPowerShell\Modules
ModuleType Version PreRelease Name
---------- ------- ---------- ----
Script 2.0.0 beta2 PSReadline ...
You can see what PowerShell is doing in this specific case by looking at the default formatting code for modules on GitHub. The relevant part is the GroupByScriptBlock
call (with minor reformatting to reduce line length):
yield return new FormatViewDefinition("Module",
TableControl.Create()
.GroupByScriptBlock(@"
Split-Path -Parent $_.Path | ForEach-Object {
if([Version]::TryParse((Split-Path $_ -Leaf), [ref]$null)) {
Split-Path -Parent $_
} else {
$_
}
} | Split-Path -Parent", customControl: sharedControls[0])
.AddHeader(Alignment.Left, width: 10)
...
When PowerShell shows an array of module objects to the user using the default format, it will run the script block in GroupByScriptBlock
on each object first to work out the grouping.