Get-APICoverage.ps1

[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
[CmdletBinding(DefaultParameterSetName = 'Everything')]
Param (
    [Parameter(ParameterSetName = 'Everything')]
    [switch]$ShowEverything,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowCompleted,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowTodo,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowSkipped,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowBugs,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowRequiresWork,

    [Parameter(ParameterSetName = 'NotAll')]
    [switch]$ShowCounts,

    [Parameter(ParameterSetName = 'Everything')]
    [switch]$OutputStats
)

Remove-Variable * -Exclude ('ShowEverything','ShowCompleted','ShowTodo','ShowSkipped','ShowBugs','ShowRequiresWork','ShowCounts','OutputStats') -ErrorAction SilentlyContinue
Clear-Host

[string]  $path     = '/home/mike/Development/PowerShell/Rapid7/Rapid7Nexpose/public/'    # Module Path
[object[]]$scripts  = (Get-ChildItem -Path $path -Filter '*.ps1')

[int]$script:Done = 0;
[int]$script:Todo = 0;
[int]$script:Bugs = 0;
[int]$script:Skip = 0;
[int]$script:ReqW = 0;

[string]$D = '[X]'    # Done
[string]$T = '[ ]'    # Todo
[string]$A = '[@]'    # ApiBug
[string]$S = '[~]'    # Skipped
[string]$R = '[!]'    # RequiresWork

[psobject]$statDone      = @{ Get=0; Put=0; Post=0; Delete=0 }
[psobject]$statTotals    = @{ Get=0; Put=0; Post=0; Delete=0 }
[psobject]$statPercent   = '' | Select-Object -Property ('Get','Put','Post','Delete','Url','Command')
[psobject]$statSeperator = '' | Select-Object -Property ('Get','Put','Post','Delete','Url','Command')
$statSeperator.Get     = '----'
$statSeperator.Put     = '----'
$statSeperator.Post    = '----'
$statSeperator.Delete  = '------'
$statSeperator.Url     = '-------------------'
$statSeperator.Command = '-------'
[System.Collections.ArrayList]$results = @()

Function Get-ScriptData {
    [int]$cnt = 0
    [hashtable]$functionality = @{}
    ForEach ($item In $scripts) {
        [string]$name = $item.BaseName
        Write-Progress -Id 0 -Activity 'Loading Functions' -CurrentOperation $name -PercentComplete ((100 / ($scripts.Count)) * $cnt++)
        Write-Verbose -Message $name
        . "$path$name.ps1"    # Dot-Source Function

        [object]$Help = (Get-Help -Name $name -ErrorAction SilentlyContinue -Full)
        If (([string]::IsNullOrEmpty($Help.Functionality) -eq $false) -and (($Help.Functionality) -ne 'None')) {
            ForEach ($func In ($Help.Functionality -split '\n')) {
                [string]$act =  $func.Split(':')[0].Trim()
                [string]$url = ($func.Split(':')[1]).Split('#')[0].Trim()
                [string]$new =  $item

                [boolean]$issue = $false
                If ($url.StartsWith('APIBUG' )) { $url = ($url.Split('-')[1].Trim()); $new = "$A $item"; $script:Bugs++; $issue = $true }
                If ($url.StartsWith('SKIPPED')) { $url = ($url.Split('-')[1].Trim()); $new = "$S $item"; $script:Skip++; $issue = $true }
                If ($url.StartsWith('REQWORK')) { $url = ($url.Split('-')[1].Trim()); $new = "$R $item"; $script:ReqW++; $issue = $true }
                If ($issue -eq $false)          { $script:Done++ }

                If (-not $functionality.$url)      { $functionality.Add($url, @{})      }
                If (-not $functionality.$url.$act) { $functionality.$url.Add($act, @()) }
                $functionality.$url.$act += "$new ($($act.ToUpper()))"
            }
        }
    }

    Write-Progress -Id 0 -Completed -Activity 'Loading Functions'
    Return $functionality
}

[int]   $port     = 3780
[string]$Hostname = '192.168.42.108'
[string]$Username = 'APIUser'
[string]$Password = 'Passw0rd'
[pscredential]$Credential = (New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList $Username, ($Password | ConvertTo-SecureString -AsPlainText -Force))
$Status = (Connect-NexposeAPI -HostName $Hostname -Port $port -Credential $Credential -SkipSSLCheck)
Write-Host "$($status.StatusCode) $($Status.StatusDescription)"
If ($Status.StatusDescription -ne 'OK') {
    Throw "Failed to connect to the Nexpose API - $($Status.StatusCode) $($Status.StatusDescription)"
}

[psobject]$API = (Invoke-RestMethod -WebSession $Global:NexposeSession -Method Get -Uri "https://$($hostname):$($port)/api/3/json" -SkipCertificateCheck -ErrorAction Stop -Verbose)
$API.paths | Out-File -FilePath ".\APIOutput-$((Get-Date).ToString('yyyy-MM-dd')).json" -Encoding ascii -Force
[hashtable]$funcScriptData = Get-ScriptData
[hashtable]$funcApiData    = @{}

[int]$cnt = 0
[int]$tot = ($API.paths | Get-Member -MemberType *Property).Count

ForEach ($func In $(($API.paths | Get-Member -MemberType NoteProperty).Name)) {
    If ($func -eq '/api/3') { Continue }
    $func = $func.Replace('/api/3/', '')
    Write-Progress -Id 1 -Activity 'Parsing Data' -CurrentOperation $func -PercentComplete ((100 / $tot) * $cnt++)

    ForEach ($method In ($api.paths."/api/3/$func" | Get-Member -MemberType NoteProperty)) {
        $statTotals.$($method.Name)++

        If (-not $funcApiData.$func) { $funcApiData.Add($func, @{}) }
        If (-not $funcApiData.$func.$($method.Name)) { $funcApiData.$func.Add($($method.Name), @()) }

        If ($funcScriptData.$func.$($method.Name)) {
            $funcApiData.$func.$($method.Name) += ($funcScriptData.$func.$($method.Name))
        }
        Else {
            $funcApiData.$func.$($method.Name) += 'MISSING'
            $script:Todo++
        }
    }
}

ForEach ($func In ($funcApiData.Keys | Sort-Object)) {
    [psobject]$result = '' | Select-Object -Property ('Get','Put','Post','Delete','Url','Command')
    [System.Collections.ArrayList]$result.Command = @()

    $result.Url = $func
    ForEach ($method In ('Get','Put','Post','Delete')) {
        If ($funcApiData.$func.$method) {
            ForEach ($eachMethod In ($funcApiData.$func.$method)) {

                If ($eachMethod -eq 'MISSING') {
                    $funcApiData.$func.$method = $null
                    $result.$method += $T
                }
                ElseIf (-not $eachMethod.StartsWith('[')) {
                    If ([string]::IsNullOrEmpty($result.$method)) { $statDone.$method++ }    # Add up all methods completed
                    If ($($result.$method) -notlike "*$D*") { $result.$method += $D }
                }
                Else {
                    If (([string]::IsNullOrEmpty($result.$method)) -and ($eachMethod.StartsWith($S))) { $statDone.$method++ }
                    $val = $eachMethod.Substring(0,3)
                    If ($($result.$method) -notlike "*$val*") { $result.$method += $val }
                }
                $($funcApiData.$func.$method) | ForEach-Object { [void]$result.Command.Add($_) }
                $result.$method = $($result.$method).Replace('][', '')
            }
        }
    }
    $result.Command = @($result.Command | Sort-Object -Unique)

    If ($ShowCompleted.IsPresent)    { If ($result.PSObject.Properties.Value -match [regex]::Escape($D)) { [void]$results.Add($result) } }
    If ($ShowSkipped.IsPresent)      { If ($result.PSObject.Properties.Value -match [regex]::Escape($S)) { [void]$results.Add($result) } }
    If ($ShowBugs.IsPresent)         { If ($result.PSObject.Properties.Value -match [regex]::Escape($A)) { [void]$results.Add($result) } }
    If ($ShowTodo.IsPresent)         { If ($result.PSObject.Properties.Value -match [regex]::Escape($T)) { [void]$results.Add($result) } }
    If ($ShowRequiresWork.IsPresent) { If ($result.PSObject.Properties.Value -match [regex]::Escape($R)) { [void]$results.Add($result) } }
    If ($PSCmdlet.ParameterSetName -eq 'Everything') { [void]$results.Add($result) }
}

# Add Stats
$statPercent.Get    = "$([int]((100 / $statTotals.Get) * $statDone.Get))%"
$statPercent.Put    = "$([int]((100 / $statTotals.Put) * $statDone.Put))%"
$statPercent.Post   = "$([int]((100 / $statTotals.Post) * $statDone.Post))%"
$statPercent.Delete = "$([int]((100 / $statTotals.Delete) * $statDone.Delete))%"
$statPercent.Url    = 'Percentage Complete'

[void]$results.Add($statSeperator)
[void]$results.Add($statPercent)

Write-Progress -Id 1 -Completed -Activity 'Parsing Data'
$results

If ($OutputStats.IsPresent) {
    Remove-Item -Path '.\apistats.txt' -Force -ErrorAction SilentlyContinue
    $results | Format-Table -AutoSize | Out-File -FilePath '.\apistats.txt' -Encoding ascii -Append -Width ([int]::MaxValue)
}

If (($ShowCounts.IsPresent) -or ($PSCmdlet.ParameterSetName -eq 'Everything')) {
    Write-Output "`n"
    Write-Output "$D $($script:Done.ToString().PadLeft(3, ' ')) Done"
    Write-Output "$T $($script:Todo.ToString().PadLeft(3, ' ')) Todo"
    Write-Output "$R $($script:ReqW.ToString().PadLeft(3, ' ')) RequiresWork"
    Write-Output "$S $($script:Skip.ToString().PadLeft(3, ' ')) Skipped"
    Write-Output "$A $($script:Bugs.ToString().PadLeft(3, ' ')) Bugs"
}