Public/Get-ModuleDetails.ps1

function Get-ModuleDetails {
  # .SYNOPSIS
  # Displays detailed information about a specified module.
  # .DESCRIPTION
  # This function provides comprehensive detailed information about a PowerShell module,
  # including manifest data, file structure, dependencies, and exported functions.
  # It returns structured data that can be used programmatically or displayed in detail.
  # .PARAMETER Name
  # The name of the module to get details for.
  # .PARAMETER Version
  # Optional specific version of the module. If not specified, uses the latest version.
  # .PARAMETER Scope
  # The installation scope to search in. Defaults to LocalMachine if not specified.
  # .PARAMETER IncludeFiles
  # Include detailed file information in the output.
  # .PARAMETER IncludeFunctions
  # Include detailed function information in the output.
  # .NOTES
  # This function leverages the LocalPsModule class and PsModuleBase utilities
  # to provide comprehensive module analysis.
  # .LINK
  # Show-ModuleInfo: Provides a more user-friendly view of module details.
  # .EXAMPLE
  # Get-ModuleDetails -Name "PsModuleBase"
  # Gets detailed information for the PsModuleBase module.
  # .EXAMPLE
  # Get-ModuleDetails -Name "MyModule" -Version "1.0.0" -IncludeFiles -IncludeFunctions
  # Gets comprehensive details including files and functions for version 1.0.0 of MyModule.
  # .OUTPUTS
  # PSCustomObject with detailed module information
  [CmdletBinding()]
  # [OutputType([PSCustomObject])]
  param (
    [Parameter(Mandatory = $true, Position = 0)]
    [ValidateNotNullOrWhiteSpace()]
    [ArgumentCompleter({
        [OutputType([System.Management.Automation.CompletionResult])]
        param(
          [string] $CommandName,
          [string] $ParameterName,
          [string] $WordToComplete,
          [System.Management.Automation.Language.CommandAst] $CommandAst,
          [System.Collections.IDictionary] $FakeBoundParameters
        )
        $CompletionResults = [System.Collections.Generic.List[CompletionResult]]::new()
        $matchingNames = [LocalPsModule]::new().GetValidValues().Where({ $_ -like "$WordToComplete*" })
        foreach ($n in $matchingNames) { $CompletionResults.Add([System.Management.Automation.CompletionResult]::new($n)) }
        return $CompletionResults
      })]
    [string]$Name,

    [Parameter(Mandatory = $false, Position = 1)]
    [ValidateNotNullOrEmpty()]
    [version]$Version,

    [Parameter(Mandatory = $false, Position = 2)]
    [ValidateSet('CurrentUser', 'LocalMachine')]
    [string]$Scope = 'LocalMachine',

    [Parameter(Mandatory = $false)]
    [switch]$IncludeFiles,

    [Parameter(Mandatory = $false)]
    [switch]$IncludeFunctions
  )

  begin {
    Write-Verbose "Starting Get-ModuleDetails for module: $Name"
  }

  process {
    try {
      # Create LocalPsModule object based on provided parameters
      $moduleObj = if ($PSBoundParameters.ContainsKey('Version') -and $PSBoundParameters.ContainsKey('Scope')) {
        [LocalPsModule]::new($Name, $Scope, $Version)
      } elseif ($PSBoundParameters.ContainsKey('Version')) {
        [LocalPsModule]::new($Name, $Version)
      } elseif ($PSBoundParameters.ContainsKey('Scope')) {
        [LocalPsModule]::new($Name, $Scope)
      } else {
        [LocalPsModule]::new($Name)
      }

      if (-not $moduleObj.Exists) {
        $errorMessage = "Module '$Name' not found"
        if ($PSBoundParameters.ContainsKey('Version')) {
          $errorMessage += " with version '$Version'"
        }
        if ($PSBoundParameters.ContainsKey('Scope')) {
          $errorMessage += " in scope '$Scope'"
        }
        Write-Error $errorMessage -ErrorAction Stop
      }

      # Build detailed module information object
      $moduleDetails = [PSCustomObject]@{
        Name             = $moduleObj.Name
        Version          = $moduleObj.Version
        Path             = $moduleObj.Path.FullName
        ManifestPath     = $moduleObj.Psd1.FullName
        Scope            = $moduleObj.Scope
        Exists           = $moduleObj.Exists
        IsReadOnly       = $moduleObj.IsReadOnly
        HasVersionDirs   = $moduleObj.HasVersiondirs
        ManifestInfo     = $null
        Files            = $null
        Functions        = $null
        Dependencies     = $null
        ExportedCommands = $null
        Size             = $null
        LastModified     = $null
      }

      # Add manifest information if available
      if ($null -ne $moduleObj.Info) {
        $moduleDetails.ManifestInfo = $moduleObj.Info

        # Extract dependencies
        if ($moduleObj.Info.PSObject.Properties.Name -contains 'RequiredModules' -and $null -ne $moduleObj.Info.RequiredModules) {
          $moduleDetails.Dependencies = $moduleObj.Info.RequiredModules
        }

        # Extract exported commands
        $exportedCommands = @{}
        if ($moduleObj.Info.PSObject.Properties.Name -contains 'FunctionsToExport' -and $null -ne $moduleObj.Info.FunctionsToExport) {
          $exportedCommands.Functions = $moduleObj.Info.FunctionsToExport
        }
        if ($moduleObj.Info.PSObject.Properties.Name -contains 'CmdletsToExport' -and $null -ne $moduleObj.Info.CmdletsToExport) {
          $exportedCommands.Cmdlets = $moduleObj.Info.CmdletsToExport
        }
        if ($moduleObj.Info.PSObject.Properties.Name -contains 'AliasesToExport' -and $null -ne $moduleObj.Info.AliasesToExport) {
          $exportedCommands.Aliases = $moduleObj.Info.AliasesToExport
        }
        $moduleDetails.ExportedCommands = $exportedCommands
      }

      # Add file information if requested
      if ($IncludeFiles -and $moduleObj.Path.Exists) {
        try {
          $files = Get-ChildItem -Path $moduleObj.Path.FullName -Recurse -File -ErrorAction SilentlyContinue
          $moduleDetails.Files = $files | Select-Object Name, FullName, Length, LastWriteTime, Extension | Sort-Object FullName

          # Calculate total size
          $totalSize = ($files | Measure-Object -Property Length -Sum).Sum
          $moduleDetails.Size = if ($totalSize -gt 1MB) {
            "{0:N2} MB" -f ($totalSize / 1MB)
          } elseif ($totalSize -gt 1KB) {
            "{0:N2} KB" -f ($totalSize / 1KB)
          } else {
            "$totalSize bytes"
          }

          # Get last modified date
          $moduleDetails.LastModified = ($files | Sort-Object LastWriteTime -Descending | Select-Object -First 1).LastWriteTime
        } catch {
          Write-Warning "Could not retrieve file information: $($_.Exception.Message)"
        }
      }

      # Add function information if requested
      if ($IncludeFunctions -and $moduleObj.Path.Exists) {
        try {
          $functionFiles = Get-ChildItem -Path $moduleObj.Path.FullName -Recurse -Filter "*.ps1" -ErrorAction SilentlyContinue
          $functions = @()

          foreach ($file in $functionFiles) {
            try {
              $content = Get-Content -Path $file.FullName -Raw -ErrorAction SilentlyContinue
              if ($content -match 'function\s+([^\s\{]+)') {
                $functionName = $matches[1]

                # Extract synopsis if available
                $synopsis = ""
                if ($content -match '\.SYNOPSIS\s*\n\s*#\s*(.+)') {
                  $synopsis = $matches[1].Trim()
                }

                $functions += [PSCustomObject]@{
                  Name     = $functionName
                  File     = $file.Name
                  FilePath = $file.FullName
                  Synopsis = $synopsis
                }
              }
            } catch {
              Write-Verbose "Could not parse function from file: $($file.FullName)"
            }
          }

          $moduleDetails.Functions = $functions | Sort-Object Name
        } catch {
          Write-Warning "Could not retrieve function information: $($_.Exception.Message)"
        }
      }

      Write-Verbose "Successfully retrieved module details for: $Name"
      return $moduleDetails
    } catch {
      Write-Error "Failed to retrieve module details for '$Name': $($_.Exception.Message)" -ErrorAction Stop
    }
  }

  end {
    Write-Verbose "Completed Get-ModuleDetails for module: $Name"
  }
}