Public/Find-DLLInPSModulePath.ps1

function Find-DLLInPSModulePath {
    <#
    .SYNOPSIS
        Show a list of all DLLs in PowerShell module paths that contain the specified product name in their FileInfo property.
 
    .DESCRIPTION
        Check all installed PowerShell module locations for DLL files that have the specified product name
        (e.g., 'Microsoft Identity') in their file's ProductName attribute. By default, searches all paths
        in the PSModulePath environment variable. Can optionally check custom locations using the -Path parameter.
 
    .PARAMETER ProductName
        The product name to search for in DLL ProductName properties. Supports wildcards. Defaults to 'Microsoft Identity'.
 
    .PARAMETER FileName
        The file name pattern to search for. Supports wildcards. Defaults to '*.dll' to search all DLL files.
        Use a specific pattern like 'Microsoft.IdentityModel*.dll' to narrow the search.
 
    .PARAMETER NewestVersion
        If specified, only the newest version of each matching DLL will be returned.
 
    .PARAMETER ShowDetails
        Display formatted output to host in addition to returning objects to the pipeline.
 
    .EXAMPLE
        Find-DLLInPSModulePath -ProductName "Microsoft Identity"
 
        Find all DLLs with 'Microsoft Identity' in their ProductName property within installed PowerShell module locations.
 
    .EXAMPLE
        Find-DLLInPSModulePath -FileName "Microsoft.IdentityModel*.dll"
 
        Find all DLL files matching the pattern 'Microsoft.IdentityModel*.dll' that also have 'Microsoft Identity' in their ProductName.
 
        Example Output:
 
        InternalName ProductVersion Module
        ------------ -------------- ------
        Microsoft.Identity.Abstractions.dll 9.5.0.0 DLLPickle
        Microsoft.IdentityModel.Abstractions.dll 0.0.0.0 Az.Accounts
        Microsoft.IdentityModel.JsonWebTokens.dll 8.6.0.0 ExchangeOnlineManagement
        Microsoft.IdentityModel.Logging.dll 8.6.0.0 ExchangeOnlineManagement
        Microsoft.IdentityModel.Protocols.dll 8.6.1.0 WinTuner
        Microsoft.IdentityModel.Protocols.OpenIdConnect.dll 8.6.1.0 WinTuner
        Microsoft.IdentityModel.Tokens.dll 8.6.0.0 ExchangeOnlineManagement
        Microsoft.IdentityModel.Validators.dll 8.6.1.0 WinTuner
        System.IdentityModel.Tokens.Jwt.dll 8.6.0.0 ExchangeOnlineManagement
    #>


    [CmdletBinding()]
    [OutputType([System.Diagnostics.FileVersionInfo])]
    param (
        # The product name to search for in DLL ProductName properties. Supports wildcards. Defaults to 'Microsoft Identity'.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ProductName = 'Microsoft Identity',

        # The file name pattern to search for. Supports wildcards. Defaults to '*.dll'.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$FileName = '*.dll',

        # Locations to search for DLLs. Defaults to all valid directories in the PSModulePath environment variable.
        [Parameter()]
        [string[]]$Path = @( $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container -ErrorAction SilentlyContinue) } ),

        # Directories to exclude from inspection so the process goes faster.
        [Parameter()]
        [string[]]$ExcludeDirectories = @('en-US', '.git'),

        # The module installation scope to search. Valid options are AllUsers, CurrentUser, or Both (default).
        [Parameter()]
        [ValidateSet('CurrentUser', 'AllUsers', 'Both')]
        [string]$Scope = 'Both',

        # If specified, only the newest version of each matching DLL will be returned.
        [switch]$NewestVersion,

        # Display formatted output to host in addition to returning objects to the pipeline.
        [switch]$ShowDetails
    )

    # Validate that all provided paths exist
    foreach ($pathItem in $Path) {
        if (-not (Test-Path -LiteralPath $pathItem -PathType Container)) {
            Write-Warning "Path does not exist or is not accessible: $pathItem"
        }
    }

    # Determine the scoped paths to inspect. Defaults to all scopes.
    if ($Scope -eq 'CurrentUser') {
        $ScopedPath = @( $Path | Where-Object { $_ -match '\bUser(s)?\b' } )
    } elseif ($Scope -eq 'AllUsers') {
        $ScopedPath = @( $Path | Where-Object { $_ -notmatch '\bUser(s)?\b' } )
    } else {
        $ScopedPath = $Path
    }

    # Write an error and exit if none of the specified paths are found in the specified scope.
    if (-not $ScopedPath -or $ScopedPath.Count -eq 0) {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.Exception]::new("Scope '$Scope' produced no valid paths."), 'ScopePathsNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $Scope
        )
        $PSCmdlet.WriteError($ErrorRecord)
        return
    }

    Write-Verbose "Enumerating DLLs matching file pattern '$FileName' with ProductName containing '$ProductName' under:`n - $($ScopedPath -join "`n - ")"
    $DLLs = @(
        Get-ChildItem -Path $ScopedPath -Filter $FileName -File -Recurse | Where-Object { $_.Directory.Name -notin $ExcludeDirectories } | ForEach-Object {
            $VersionInfo = $_.VersionInfo
            if ($VersionInfo.ProductName -like "*$ProductName*") {
                $VersionInfo.PSObject.TypeNames.Insert(0, 'DLLPickle.FileVersionInfo')
                $VersionInfo
            }
        }
    )

    if ($DLLs.Count -eq 0) {
        Write-Warning "No DLLs found matching file pattern '$FileName' with ProductName containing '*$ProductName*'."
    }

    # If the NewestVersion switch is specified, filter to only the newest version of each DLL.
    if ($NewestVersion) {
        $DLLs = $DLLs | Group-Object -Property OriginalFilename | ForEach-Object {
            $_.Group | Sort-Object -Property FileVersion -Descending | Select-Object -First 1
        } | Sort-Object -Property InternalName
    }

    # Display detailed output to host if the ShowDetails switch is specified.
    if ($PSBoundParameters.ContainsKey('ShowDetails')) {
        # Show the results as a table to the host in addition to returning to the pipeline.
        $DLLs | Out-Host
    }

    $DLLs
}