Public/Get-PSWinOpsFunction.ps1

#Requires -Version 5.1

function Get-PSWinOpsFunction {
    <#
    .SYNOPSIS
        Lists the module's public functions by domain with their synopsis
 
    .DESCRIPTION
        Enumerates every exported function of the PSWinOps module and returns one
        structured object per function with its name, the thematic domain it belongs
        to, its short alias, and its one-line synopsis taken from the comment-based
        help. The listing is sorted by domain then name, so it reads as a grouped
        index of the module's surface and doubles as a discovery aid when the exact
        command name is not known.
 
        The domain is resolved from the function's source file under Public\<domain>\.
        In the published (flattened) module build, the domain is recovered from the
        per-function markers that build.ps1 writes into the assembled PSM1.
 
        This meta-function itself lives at the root of Public\ and belongs to no
        domain, so it is listed with an empty Domain.
 
    .PARAMETER Domain
        One or more domain (thematic folder) names to restrict the listing to, e.g.
        'ntp' or 'network'. Accepts pipeline input by value and by property name.
 
    .EXAMPLE
        Get-PSWinOpsFunction
 
        Lists every public function grouped by domain, each with its alias and synopsis.
 
    .EXAMPLE
        Get-PSWinOpsFunction -Domain 'ntp'
 
        Lists only the NTP-domain functions.
 
    .EXAMPLE
        'ntp', 'network' | Get-PSWinOpsFunction
 
        Lists the NTP and network functions via pipeline input.
 
    .OUTPUTS
        PSWinOps.ModuleFunction
        One object per public function, with Name, Domain, Alias and Synopsis.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-09-21
        Requires: PowerShell 5.1+ / Windows only
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-command
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.ModuleFunction')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('activedirectory', 'certificate', 'disk', 'eventlog', 'healthcheck', 'iis',
            'network', 'ntp', 'proxy', 'rdp', 'security', 'system', 'utils', 'vss', 'windowsupdate')]
        [string[]]$Domain
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Enumerating module functions"

        # Reverse the module's short-alias map so each function name resolves to its alias.
        $aliasByFunction = @{}
        foreach ($entry in $script:AliasMap.GetEnumerator()) {
            $aliasByFunction[$entry.Value] = $entry.Key
        }

        # Enumerate the module's exported functions once, sorted by name. Use the
        # module's own ExportedFunctions collection rather than Get-Command -Module,
        # which can resolve a derived module name and pull in Private\ helpers.
        $module = $MyInvocation.MyCommand.Module
        if ($null -eq $module) {
            $module = Get-Module -Name 'PSWinOps' -ErrorAction Stop
        }
        $commands = @($module.ExportedFunctions.Values | Sort-Object -Property Name)

        # Resolve each function to its domain (thematic folder). In the source layout the
        # defining file lives under Public\<domain>\, so the domain is the folder name. The
        # published module is a single flattened PSM1 with no Public\ tree, so fall back to
        # the '# --- Public: <domain>/<name>.ps1 ---' markers that build.ps1 writes.
        $domainByFunction = @{}
        $flatModuleFile = $null
        foreach ($cmd in $commands) {
            $file = $null
            if ($null -ne $cmd.ScriptBlock) {
                $file = $cmd.ScriptBlock.File
            }
            $resolvedDomain = $null
            if ($file -and $file -match '[\\/]Public[\\/]([^\\/]+)[\\/]') {
                $resolvedDomain = $Matches[1]
            } elseif ($file -and $file -like '*.psm1') {
                $flatModuleFile = $file
            }
            $domainByFunction[$cmd.Name] = $resolvedDomain
        }

        if ($flatModuleFile) {
            $flatContent = Get-Content -Path $flatModuleFile -Raw -ErrorAction SilentlyContinue
            if ($flatContent) {
                foreach ($marker in [regex]::Matches($flatContent, '(?m)^# --- Public: ([A-Za-z0-9-]+)[/\\]([A-Za-z0-9-]+)\.ps1 ---[ \t]*$')) {
                    $domainByFunction[$marker.Groups[2].Value] = $marker.Groups[1].Value
                }
            }
        }

        $results = [System.Collections.Generic.List[object]]::new()
    }

    process {
        foreach ($cmd in $commands) {
            $functionDomain = $domainByFunction[$cmd.Name]
            if ($PSBoundParameters.ContainsKey('Domain') -and ($functionDomain -notin $Domain)) {
                continue
            }

            $synopsis = ''
            $help = Get-Help -Name $cmd.Name -ErrorAction SilentlyContinue
            if ($null -ne $help) {
                $synopsis = (($help.Synopsis -join ' ') -replace '\s+', ' ').Trim()
            }

            $null = $results.Add([PSCustomObject]@{
                PSTypeName = 'PSWinOps.ModuleFunction'
                Name       = $cmd.Name
                Domain     = $functionDomain
                Alias      = if ($aliasByFunction.ContainsKey($cmd.Name)) { $aliasByFunction[$cmd.Name] } else { '' }
                Synopsis   = $synopsis
            })
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed function listing"
        $results | Sort-Object -Property Domain, Name
    }
}