Public/Read-ModuleData.ps1

function Read-ModuleData {
  # .SYNOPSIS
  # Reads a specific value from a PowerShell metdata file (e.g. a module manifest)
  # .DESCRIPTION
  # By default Get-ModuleManifest gets the ModuleVersion, but it can read any key in the metadata file
  # .LINK
  # https://github.com/alainQtec/PsCraft/blob/main/Public/Read-ModuleData.ps1
  # .EXAMPLE
  # Read-ModuleData .
  # Reads the Moduledata from the current directory, assumes that the module name is the same as the directory name
  [CmdletBinding()]
  [OutputType([PsObject])]
  param (
    [Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)]
    [ValidateNotNullOrWhiteSpace()][Alias('f')][string]
    $File,

    [Parameter(Position = 1, Mandatory = $false)]
    [AllowNull()][string]
    $Property,

    [Parameter(Position = 2, Mandatory = $false)]
    [ValidateScript({
        $p = (Resolve-Path $_ -ea Ignore)
        if ((Test-Path -Path $p -PathType Container -ea Ignore)) {
          return $true
        } else {
          throw [System.ArgumentException]::new("directory '$_' does not exist.", 'Path')
        }
      }
    )][string]
    $Path = (Resolve-Path .).Path
  )
  begin {
    $Path = Resolve-Path $Path
    if (!$PSCmdlet.MyInvocation.BoundParameters.ContainsKey('File')) {
      $File = [IO.Path]::Combine($Path, (Get-Culture).Name, "$([IO.DirectoryInfo]::New($Path).BaseName).strings.psd1");
    }; $File = Resolve-Path $File;
    $IsValidPsd1file = (Test-Path -Path $File -PathType Leaf -ea Ignore) -and ([IO.Path]::GetExtension($File) -eq ".psd1")
    if (!$IsValidPsd1file) {
      throw [System.ArgumentException]::new("File '$File' is not valid. Please provide a valid path/to/<modulename>.Strings.psd1", 'Path')
    }
  }
  process {
    if (!(Test-Path $File)) {
      $Error_params = @{
        ExceptionName    = "ItemNotFoundException"
        ExceptionMessage = "Can't find file $File"
        ErrorId          = "PathNotFound,Metadata\Import-Metadata"
        Caller           = $PSCmdlet
        ErrorCategory    = "ObjectNotFound"
      }
      Write-TerminatingError @Error_params
    }
    if ([string]::IsNullOrWhiteSpace($Property)) {
      $null = Get-Item -Path $File -ErrorAction Stop
      $data = New-Object PsObject; $text = [IO.File]::ReadAllText("$File")
      $data = [scriptblock]::Create("$text").Invoke()
      return $data
    }
    $Tokens = $Null; $ParseErrors = $Null
    # Search the Manifest root properties, and also the nested hashtable properties.
    if ([IO.Path]::GetExtension($_) -ne ".psd1") { throw "Path must point to a .psd1 file" }
    $AST = [Parser]::ParseFile($File, [ref]$Tokens, [ref]$ParseErrors)
    $KeyValue = $Ast.EndBlock.Statements
    $KeyValue = @([PsCraft]::FindHashKeyValue($Property, $KeyValue))
    if ($KeyValue.Count -eq 0) {
      $Error_params = @{
        ExceptionName    = "ItemNotFoundException"
        ExceptionMessage = "Can't find '$Property' in $File"
        ErrorId          = "PropertyNotFound,Metadata\Get-Metadata"
        Caller           = $PSCmdlet
        ErrorCategory    = "ObjectNotFound"
      }
      Write-TerminatingError @Error_params
    }
    if ($KeyValue.Count -gt 1) {
      $SingleKey = @($KeyValue | Where-Object { $_.HashKeyPath -eq $Property })
      if ($SingleKey.Count -gt 1) {
        $Error_params = @{
          ExceptionName    = "System.Reflection.AmbiguousMatchException"
          ExceptionMessage = "Found more than one '$Property' in $File. Please specify a dotted path instead. Matching paths include: '{0}'" -f ($KeyValue.HashKeyPath -join "', '")
          ErrorId          = "AmbiguousMatch,Metadata\Get-Metadata"
          Caller           = $PSCmdlet
          ErrorCategory    = "InvalidArgument"
        }
        Write-TerminatingError @Error_params
      } else {
        $KeyValue = $SingleKey
      }
    }
    $KeyValue = $KeyValue[0]
    # $KeyValue.SafeGetValue()
    return $KeyValue
  }
}