Showing results for 
Search instead for 
Did you mean: 
Cisco Employee
Cisco Employee

Since this is my first blog of 2022, I would like to wish you a prosperous New Year.

As you might recall, I published a blog on Risk Meter Reports using Python in November 2021.  That blog discussed two Risk Meter or Asset Group reports, "Historical Open Vulnerability Counts by Risk Level" and "Risk Accepted Over Time."  In this blog, I will be discussing two reports, "Historical Open Vulnerability Counts by Risk Level" and "Total Vulnerabilities Past Due by Risk Level," but this time I'll be using PowerShell.

The report compares high-risk, past-due vulnerability counts with the total vulnerability counts in a historical fashion.  This will allow you to see how you're keeping up with your vulnerability due dates (SLA) over time as compared to the total number of vulnerabilities.  The report is by month from the start date.

Note: A risk meter score is the vulnerability score of an asset group. The terms risk meter and asset group are used interchangeably.

I haven't coded PowerShell in about five years, so I'm a little rusty.  Also, I coded this on a Mac without .NET.  I used Export-CSV instead of using a COM module interface to Excel.  Hopefully, next time when I have a Windows box to work on.  Let's get started.  The code is in ShowHistoricalVulnCounts.ps1.

The cmdlet, ShowHistoricalVulnCounts, has two mandatory command line input parameters: start date and the output CSV file.

 23 # Command line parameters.
 24 [cmdletbinding()]
 25 param(
 26     [Parameter(Mandatory=$true, ValueFromPipeline=$false)]
 27     [String]$startDate,
 29     [Parameter(Mandatory=$true, ValueFromPipeline=$false)]
 30     [String]$csvFileName
 31 )

Both startDate and csvFileName cannot be obtained from the pipeline.

Using the API Key Token

The Kenna API key token is picked up from the environment variable $KENNA_API_KEY.  Then the API key is stored in the HTTP header.

216 # Obtain the Kenna Security API key from an environment variable.
217 $KennaApiKey = ""
218 Try {
219     $KennaApiKey = (Get-ChildItem -Path Env:\KENNA_API_KEY -ErrorAction Stop).Value
220 }
221 Catch {
222     Write-Host "API key is non-existent"
223     Exit
224 }
226 # HTTP headers.
227 $Headers = @{
228     "Accept" = "application/json"
229     "Content-Type" = "application/json; charset=utf-8"
230     "X-Risk-Token" = $KennaApiKey
231 }

Line 219 obtains the API key and it is stored in line 230.

How to Invoke an API in PowerShell

Some of you readers might already know how to invoke APIs in PowerShell; but for those who don't, I used the cmdlet Invoke-RestMethod.  Now I know that our API documentation uses Invoke-WebRequest, but I used Invoke-RestMethod because it efficiently deals with JSON content.  Invoke-WebRequest is better for HTML content.  The reference section contains discussion links.  Let's look at the code for Invoke-List-Risk-Meters.

 99 function Invoke-List-Risk-Meters
100 {
101     [CmdletBinding()]
102     param(
103         [Parameter(Mandatory)]
104         [String]$BaseUrl,
106         [Parameter(Mandatory)]
107         [Object]$Headers
108     )
110     $Resp = {}
111     $ListAssetGroupsApiUrl = "$($BaseUrl)asset_groups"
112     Try {
113         $Resp = Invoke-RestMethod -Headers $Headers -Method Get -Uri $ListAssetGroupsApiUrl
114     }
115     Catch {
116         $ErrorMessage = $_.Exception.Message
117         $Line = $_.InvocationInfo.ScriptLineNumber
118         $RequestUrl = $_.Exception.Response.RequestMessage.RequestUri
119         Write-Host "List Asset Group API failed." -ForegroundColor Red
120         Write-Host "Line $($Line): $($ErrorMessage)" -ForegroundColor Red
121         Write-Host "URL: $($RequestUrl)" -ForegroundColor Red
122         Exit
123     }
125     Return $Resp.asset_groups
126 }

As you can see, this function is similar to the Python one.  On line 111, the List Asset Group API URL is created.  The API is invoked on line 113 in a Try-Catch block. This Catch block is an amalgamation of Catch blocks from past code.  The list of risk meters is returned in line 125.

All the other APIs are invoked in a similar fashion.  There is a function, Invoke-Risk-Meter-Report that invokes historical risk meter report APIs with the report name and start date.  It is called by Invoke-Historical-Vuln-Counts and Invoke-Historical-Past-Due-Vuln-Counts.

Processing Each Risk Meter

After all the risk meters are obtained, each risk meter is processed by obtaining the historical vulnerability counts and the historical past-due vulnerability counts.  The Risk Meter Reports require an asset group ID and the start date from the command line.

246 # For each asset group, obtain the total number of vulnerabilities (vulns) and the number of vulns past the due date.
247 ForEach ($AssetGroup in $AssetGroups) {
248     # Invoke APIs to obtain the historical vuln counts.
249     $VulnCountsResp = Invoke-Historical-Vuln-Counts -BaseUrl $BaseUrl -Headers $Headers -Id $ -startDate $startDate
250     $PastDueVulnCountsResp = Invoke-Historical-Past-Due-Vuln-Counts -BaseUrl $BaseUrl -Headers $Headers `
251                                                                     -Id $ -startDate $startDate

Next, we pull out the vulnerability counts in lines 261 and 261.

260     # Acquire the vuln counts by dates.
261     $HistoricalVulnCounts = $VulnCountsResp.historical_vulnerability_count_by_risk
262     $HistoricalPastDueVulnCounts = $PastDueVulnCountsResp.historical_past_due_vulnerabilities_by_risk_level
264     # Keep only the first day of the months for total vuln counts.
265     $keyHash = [ordered] @{}
266     $HistoricalVulnCounts.PsObject.Properties | ForEach-Object {
267         if ($_.Name -match '^\d\d\d\d-\d\d-01$') {
268             $keyHash[$_.Name] = $_.Value.high
269         }
270     }
272     # Keep only the first day of the months for past due vuln counts.
273     $pastDueKeyHash = [ordered] @{}
274     $HistoricalPastDueVulnCounts.PsObject.Properties | ForEach-Object {
275         if ($_.Name -match '\d\d\d\d-\d\d-01') {
276             $pastDueKeyHash[$_.Name] = $_.Value.high
277         }
278     }

Here is a snippet of the response so the code is more understandable.

  "historical_vulnerability_count_by_risk": {
    "2017-06-30": {
      "low": 440,
      "medium": 330,
      "high": 100
    "2017-07-01": {
      "low": 440,
      "medium": 320,
      "high": 50
    "2017-07-02": {
      "low": 438,
      "medium": 300,
      "high": 42

In lines 266-270, the code extracts the high-risk score total vulnerability counts for the first day of each month from the start date.  Lines 273-278 do the same for past-due vulnerability counts.  An ordered hash is used to keep the CSV columns organized for a consistent display.  Hashes can be easily exported to a CSV file via Export-Csv.

The next step for each risk meter is to build a CSV row and write it to the CSV file.  This is done in lines 282-289.

282     $riskMeterRow = New-Risk-Meter-Row -AssetGroup $AssetGroup -dateKeys $keyHash.Keys
283     $riskMeterRow | Export-Csv -Path $csvFileName -Append
285     $pastDueVulnCountDataRow = New-Data-Row -Title "Past Due Vuln Count" -dateHash $pastDueKeyHash
286     $pastDueVulnCountDataRow | Export-Csv -Path $csvFileName -Append
288     $vulnCountDataRow = New-Data-Row -Title "Total Vuln Count" -dateHash $keyHash
289     $vulnCountDataRow | Export-Csv -Path $csvFileName -Append

A risk meter row consists of the risk meter's name, ID, score, asset count, and last updated date.  The data rows consist of the vulnerability title and the monthly data.  There is one data row for past due vulnerabilities and another for total vulnerabilities.

Row Building

For Export-Csv to work properly, each row has to have the same columns.  This is done with an ordered hash.  The hash keys are the columns for the CSV. Both New-Risk-Meter-Row and New-Data-Row contain all the same hash keys and are based on New-Blank-Row.  By the time I realized a row class would be useful, it was too close to the blog deadline.  If you take the time to peruse the row code, you will see that New-Blank-Row creates the order hash and initializes the non-date keys to "".  New-Risk-Meter-Row calls New-Blank-Row, sets the non-date keys to the asset group values, and creates the date keys with "" values.  On the other hand, New-Data-Row calls New-Blank-Row, sets the "Vuln Type" column, and creates date keys with values.  Depending on the "Vuln Type, past due or total vulnerability counts, the date data is set with the appropriate high-risk vulnerability counts.


Here is a snippet of the report.


It looks like the admin has not been attending to the vulnerabilities since November 2021.  You can tell this because the past vulnerability count is the same as the total vulnerability count.  Remember that these vulnerability counts are for the high-risk vulnerabilities, and you want the past due counts to be zero.  It always looks good to your management when you meet your SLAs.

As always, this code is in Kenna Security’s GitHub repository.  I also created ListRiskMeters.ps1 as a small contained example.

Until next time,

Rick Ehrhart

API Evangelist


This blog was originally written for Kenna Security, which has been acquired by Cisco Systems.
Learn more about Cisco Vulnerability Management.

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community:

Recognize Your Peers