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" } |