functions/Get-ASTScriptProfile.ps1


Function Get-ASTProfile {
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, HelpMessage = "Enter the path of a PowerShell script")]
        [ValidateScript( {Test-Path $_})]
        [ValidatePattern( "\.(ps1|psm1|txt)$")]
        [String]$Path = $(Read-Host "Enter the filename and path to a PowerShell script"),
        [ValidateScript( {Test-Path $_})]
        [Alias("fp", "out")]
        [String]$FilePath = "$env:userprofile\Documents\WindowsPowerShell"
    )

    Write-Verbose "Starting $($MyInvocation.MyCommand)"

    #region setup profiling
    #need to resolve full path and convert it
    $Path = (Resolve-Path -Path $Path).Path | Convert-Path
    Write-Verbose "Analyzing $Path"
    Write-Verbose "Parsing File for AST"
    New-Variable $AstTokens -force
    New-Variable astErr -force

    $AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$AstTokens, [ref]$astErr)

    #endregion

    #region generate AST data

    #include PowerShell version information
    Write-Verbose "PSVersionTable"
    Write-Verbose ($PSVersionTable | Out-String)

    if ($AST.ScriptRequirements) {
        $requirements = ($AST.ScriptRequirements | Out-String).Trim()
    }
    else {
        $requirements = "-->None detected"
    }

    if ($AST.ParamBlock.Parameters ) {
        Write-Verbose "Parameters detected"
        $FoundParams = $(($AST.ParamBlock.Parameters |
                    Select-Object Name, DefaultValue, StaticType, Attributes |
                    Format-List | Out-String).Trim()
        )
    }
    else {
        $FoundParams = "-->None detected. Parameters for nested commands not tested."
    }


    #define the report text
    $report = @"
This is an analysis of a PowerShell script or module. Analysis will most likely NOT be 100% thorough.
 
"@


    Write-Verbose "Getting requirements and parameters"
    $report += @"
 
REQUIREMENTS
$requirements
 
PARAMETERS
$FoundParams
 
"@


    Write-Verbose "Getting all command elements"

    $commands = @()
    $unresolved = @()

    $genericCommands = $AstTokens |
        Where-Object {$_.TokenFlags -eq 'commandname' -AND $_.kind -eq 'generic'}

    $aliases = $AstTokens |
        Where-Object {$_.TokenFlags -eq 'commandname' -AND $_.kind -eq 'identifier'}

    Write-Verbose "Parsing commands"
    foreach ($command in $genericCommands) {
        Try {
            $commands += Get-Command -Name $command.text -ErrorAction Stop
        }
        Catch {
            $unresolved += $command.Text
        }
    }

    foreach ($command in $aliases) {
        Try {
            $commands += Get-Command -Name $command.text -ErrorAction Stop |
                ForEach-Object {
                #get the resolved command
                Get-Command -Name $_.Definition
            }
        }
        Catch {
            $unresolved += $command.Text
        }
    }

    Write-Verbose "All commands"
    $report += @"
 
ALL COMMANDS
All possible PowerShell commands. This list may not be complete or even correct.
 
$(($Commands | Sort-Object -Unique | Format-Table -AutoSize | Out-String).Trim())
 
"@


    Write-Verbose "Unresolved commands"
    if ($unresolved) {
        $UnresolvedText = $Unresolved | Sort-Object -Unique | Format-Table -AutoSize | Out-String
    }
    else {
        $UnresolvedText = "-->None detected"
    }

    $report += @"
 
UNRESOLVED
These commands may be called from nested commands or unknown modules.
 
$UnresolvedText
"@


    Write-Verbose "Potentially dangerous commands"
    #identify dangerous commands
    $danger = "Remove", "Stop", "Disconnect", "Suspend", "Block",
    "Disable", "Deny", "Unpublish", "Dismount", "Reset", "Resize",
    "Rename", "Redo", "Lock", "Hide", "Clear"

    $danger = $commands | Where-Object {$danger -contains $_.verb} | Sort-Object Name | Get-Unique

    if ($danger) {
        $DangerCommands = $($danger | Format-Table -AutoSize | Out-String).Trim()
    }
    else {
        $DangerCommands = "-->None detected"
    }

    #get type names, some of which may come from parameters
    Write-Verbose "TypeNames"

    $TypeTokens = $AstTokens | Where-Object {$_.TokenFlags -eq 'TypeName'}
    if ($TypeTokens ) {
        $foundTypes = $TypeTokens |
            Sort-Object @{expression = {$_.text.ToUpper()}} -unique |
            Select-Object -ExpandProperty Text | ForEach-Object { "[$_]"} | Out-String
    }
    else {
        $foundTypes = "-->None detected"
    }

    $report += @"
 
TYPENAMES
These are identified .NET type names that might be used as accelerators.
 
$foundTypes
"@


    $report += @"
 
WARNING
These are potentially dangerous commands.
 
$DangerCommands
"@


    #endregion

    Write-Verbose "Display results"
    #region create and display the result

    #create a help topic file using the script basename
    $basename = (Get-Item $Path).basename
    #stored in the Documents folder
    $reportFile = Join-Path -Path $FilePath -ChildPath "ABOUT_$basename.help.txt"

    Write-Verbose "Saving report to $reportFile"
    #insert the Topic line so help recognizes it
    @"
TOPIC
about $basename profile
 
"@
 |Out-File -FilePath $reportFile -Encoding ascii

    #create the report
    @"
SHORT DESCRIPTION
Script Profile report for: $Path
 
"@
 | Out-File -FilePath $reportFile -Encoding ascii -Append

    @"
LONG DESCRIPTION
$report
"@
  | Out-File -FilePath $reportFile -Encoding ascii -Append

    #view the report with Notepad

    Notepad $reportFile

    #endregion

    Write-Verbose "Profiling complete."
} #end of function