Functions/GenXdev.Coding.PowerShell.Modules/Measure-UseFullyQualifiedCmdletNames.psm1

using namespace System.Management.Automation.Language
using namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic

<#
.SYNOPSIS
Measure-UseFullyQualifiedCmdletNames
 
.DESCRIPTION
Ensures all cmdlets and functions are called with their fully qualified module name
 
.EXAMPLE
Invoke-ScriptAnalyzer -Path script.ps1 -CustomRulePath ./CustomPSScriptAnalyzerRules
 
.INPUTS
[System.Management.Automation.Language.ScriptBlockAst]
 
.OUTPUTS
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
.NOTES
PSScriptAnalyzer custom rules must follow specific patterns to be recognized
#>

function Measure-UseFullyQualifiedCmdletNames {
    [Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer.RuleInfoAttribute(
        "UseFullyQualifiedCmdletNames",
        "Use fully qualified cmdlet names to improve clarity and avoid namespace collisions",
        "Commands should be referenced with their fully qualified module name (e.g., 'ModuleName\\CommandName') to improve script clarity and avoid command name conflicts.",
        "Error",
        $true
    )]
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]

    Param (
        [ScriptBlockAst]
        $ScriptBlockAst
    )

    Process {
        $results = @()
        try {
            Write-Verbose "Starting analysis of script block for unqualified cmdlet names"

            if (-not $ScriptBlockAst) {
                Write-Verbose "No ScriptBlockAst provided - exiting"
                return $results
            }

            Write-Verbose "Searching for command invocations in the script block"
            $commands = $ScriptBlockAst.FindAll({
                    $args[0] -is [CommandAst]
                }, $true)

            Write-Verbose "Found $($commands.Count) command invocations to analyze"

            foreach ($command in $commands) {
                $commandName = $command.GetCommandName()
                Write-Verbose "Processing command: $commandName"

                if (-not $commandName) {
                    Write-Verbose "Skipping: Unable to get command name"
                    continue
                }

                if ($commandName -notmatch '\\') {
                    Write-Verbose "Command '$commandName' is not fully qualified - attempting to resolve"

                    Write-Verbose "Attempting to get command information for '$commandName'"
                    $resolvedCommand = Get-Command -Name $commandName -ErrorAction SilentlyContinue
                    if ($null -eq $resolvedCommand) {
                        Write-Verbose "Get-Command failed, trying ExecutionContext"
                        $resolvedCommand = $ExecutionContext.InvokeCommand.GetCommand($commandName, [CommandTypes]::All)
                    }

                    if ($resolvedCommand) {
                        Write-Verbose "Command resolved as $($resolvedCommand.CommandType) from module $($resolvedCommand.ModuleName)"

                        if (($resolvedCommand.CommandType -eq 'Cmdlet' -or $resolvedCommand.CommandType -eq 'Function') -and
                            $resolvedCommand.ModuleName) {
                            $fullyQualifiedName = "$($resolvedCommand.ModuleName)\$commandName"
                            Write-Verbose "Suggested fully qualified name: $fullyQualifiedName"

                            Write-Verbose "Creating diagnostic record for $commandName"
                            $result = [DiagnosticRecord]@{
                                Message              = "Command '$commandName' should be fully qualified with its module name (e.g., '$fullyQualifiedName')."
                                Extent               = $command.CommandElements[0].Extent
                                RuleName             = $PSCmdlet.MyInvocation.InvocationName
                                Severity             = 'Error'
                                SuggestedCorrections = @(
                                    [CorrectionExtent]@{
                                        Text              = $fullyQualifiedName
                                        StartLineNumber   = $command.CommandElements[0].Extent.StartLineNumber
                                        EndLineNumber     = $command.CommandElements[0].Extent.EndLineNumber
                                        StartColumnNumber = $command.CommandElements[0].Extent.StartColumnNumber
                                        EndColumnNumber   = $command.CommandElements[0].Extent.EndColumnNumber
                                        Description       = "Use fully qualified name: $fullyQualifiedName"
                                    }
                                )
                            }
                            $results += $result
                        }
                        else {
                            Write-Verbose "Skipping: Command is not a cmdlet or function, or has no module name"
                        }
                    }
                    else {
                        Write-Verbose "Skipping: Unable to resolve command '$commandName'"
                    }
                }
                else {
                    Write-Verbose "Skipping: Command '$commandName' is already fully qualified"
                }
            }

            Write-Verbose "Completed analysis of script block with $($results.Count) issues found"
            return $results
        }
        catch {
            Write-Verbose "Error occurred: $($_.Exception.Message)"
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

Export-ModuleMember -Function Measure-UseFullyQualifiedCmdletNames