Tenable.Tools.psm1
# Private Function Example - Replace With Your Function function Add-PrivateFunction { [CmdletBinding()] Param ( # Your parameters go here... ) # Your function code goes here... Write-Output "Your private function ran!" } function Get-TioAgent { <# .SYNOPSIS Get Tenable Agent Information .DESCRIPTION This function returns information about available Tenable Agents .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Limit Maximum number of Agents to return per query .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param( [Parameter(Mandatory = $false, HelpMessage = 'Base URI for the Tenable Environment, defaults to Tenable Cloud')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory = $true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory = $false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id of Scanner for which to retrieve details')] [Alias("Uuid")] [string] $Id, [Parameter(Mandatory = $false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'Maximum number of entries to return per API Call')] [int] $Size = 1000, [Parameter(Mandatory = $false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'The page number of the result set')] [ValidateRange(0, 10000)] [int32] $Page = 0 ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Entering function' -f $Me) $Uri.Path = [io.path]::combine($Uri.Path, 'scanners', 'null', 'agents') $Method = 'GET' } Process { # Intitialise result set $ResCount = 0 # Parse exisint query parameters $QueryParam = [System.Web.HttpUtility]::ParseQueryString($Uri.Query) if ($PSBoundParameters.ContainsKey('Id')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Id) } else { # We're not looking for a specific ID, so set limit and offset based on requested page $QueryParam['limit'] = $Size $QueryParam['offset'] = ($Page * $Size) } # Add the query back to the URI $Uri.Query = $QueryParam.ToString() # Make the call to the API Write-Verbose "$Me : Uri : $($Uri.Uri)" $Response = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method # Determine if we need to loop through pages if (!($PSBoundParameters.ContainsKey('Page'))) { # A specific page was not requested, get all available pages $Page = 0 if ($Response.agents) { Write-Output $Response.agents Write-Debug ('{0}: Response: {1}' -f $Me, ($Response | ConvertTo-Json -Compress -Depth 10)) $ResCount += $Response.agents.Count $TotalResults = $Response.pagination.total # Get the number pages of results (have to round up) $Pages = [Math]::Ceiling($TotalResults / $Size) Write-Verbose ('{0}: Fetching all pages. Total Results: {1}; Total Pages: {2}' -f $Me, $TotalResults, $Pages) } else { Write-Output $Response } # We have the first page, loop through the remainder (if there are any) for ($Page = 1; $Page -lt $Pages; $Page++ ) { Clear-Variable 'Response' Write-Verbose ("{0} : Fetching Page {1}" -f $Me, $Page) # Call ourselves to get the next page(s) $Response = Get-TioAgent -ApiKeys $ApiKeys -Size $Size -Page $Page Write-Debug ('{0}: Page: {1}; Response: {2}' -f $Me, $Page, ($Response | ConvertTo-Json -Compress -Depth 10)) Write-Verbose ("{0} : Fetched Page {1}" -f $Me, $Page) Write-Output $Response $ResCount += $Response.Count } } else { if ($Response.agents) { Write-Output $Response.agents } else { Write-Output $Response } } } End { } } function Get-TioAsset { <# .SYNOPSIS Get Asset information .DESCRIPTION This function returns information about one or more Tenable.io Assets .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id (UUID) of Asset for which to retrieve details')] [Alias("Id")] [string] $Uuid, [Parameter(Mandatory=$false, ParameterSetName = 'ByHostname', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Hostname for which to retrieve details')] [string] $Hostname, [Parameter(Mandatory=$false, ParameterSetName = 'ByIpv4', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'IPv4 for which to retrieve details')] [string] $IPv4 ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "assets") } Process { if ($PSBoundParameters.ContainsKey('Uuid')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Uuid) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Assets = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($PSBoundParameters.ContainsKey('Hostname')) { Write-Verbose ('Checking Assets by Hostname: ' + $Hostname) # We're looking up a specific Hostname foreach ($Asset in $Assets.assets) { Write-Verbose (' Checking: ' + $Folder.name) if ($asset.hostname -eq $Hostname) { Write-Output $Asset } } } elseif ($PSBoundParameters.ContainsKey('IPv4')) { Write-Verbose ('Checking Folders by Type: ' + $IPv4) # We're looking up a specific Type foreach ($Asset in $Assets.assets) { Write-Verbose (' Checking: ' + $Asset.ipv4) if ($Asset.ipv4 -eq $Ipv4) { Write-Output $Asset } } } else { if ($Assets.assets) { Write-Output $Assets.assets } else { Write-Output $Assets } } } End { } } function Get-TioExportAsset { <# .SYNOPSIS Exports all assets that match the request criteria. .DESCRIPTION This function returns information about one or more Tenable.io Assets .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported assets. To return all assets, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ByTag')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory=$false, HelpMessage = 'Results per chunk')] [int64] $ChunkSize = 1000, [Parameter(Mandatory=$true, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [psobject] $Filter, [Parameter(Mandatory=$true, ParameterSetName = 'ByTag', HelpMessage = 'Tag Category Filter condition')] [string] $TagCategory, [Parameter(Mandatory=$true, ParameterSetName = 'ByTag', HelpMessage = 'Tag Value Filter condition')] [string] $TagValue, [Parameter(Mandatory=$true, ParameterSetName = 'ByUuid', HelpMessage = 'Tag Value Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $RetryInterval = 30 $Uri.Path = [io.path]::combine($Uri.Path, "assets/export") if (!$PSBoundParameters.ContainsKey('Uuid')) { # Starting a new search $Body = @{} $Body.Add('chunk_size',$ChunkSize) if ($PSBoundParameters.ContainsKey('TagCategory')) { $Body.Add('filters',@{}) $Body.filters.add(('tag.' + $TagCategory),$TagValue) } elseif ($PSBoundParameters.ContainsKey('Filter')) { $Body.Add('filters',$Filter) } } } Process { if (!$PSBoundParameters.ContainsKey('Uuid')) { # Initiate the Asset Export Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportParams = @{ ApiKeys = $ApiKeys ChunkSize = $ChunkSize } if ($PSBoundParameters.ContainsKey('Filter')) { $ExportParams.Add('Filter', $Filter) } if ($PSBoundParameters.ContainsKey('TagCategory')) { $ExportParams.Add('TagCategory', $TagCategory) $ExportParams.Add('TagValue', $TagValue) } $AssetExport = Start-TioExportAsset @ExportParams Write-Verbose ('{0}: AssetExport: {1}' -f $Me, ($AssetExport | ConvertTo-Json -depth 10 -Compress)) $Uuid = $AssetExport } Write-Verbose ($Me + ': Asset Export ID: ' + $Uuid) # Start by checking the status $ExportStatus = Get-TioExportAssetStatus -ApiKeys $ApiKeys -Uuid $Uuid if ($ExportStatus.Error) { Write-Error ("$Me : Exception: $($ExportStatus.Code) : $($ExportStatus.Note)") } Write-Verbose ($Me + ': Asset Export Status: ' +$ExportStatus.status) # Wait until the export is finished while ($ExportStatus.status -ne 'FINISHED') { Start-Sleep -Seconds $RetryInterval $ExportStatus = Get-TioExportAssetStatus -ApiKeys $ApiKeys -Uuid $Uuid # Check for failures if ($ExportStatus.status -eq 'CANCELLED' -or $ExportStatus.status -eq 'ERROR') { Write-Error 'Asset Export Failed' exit 1 } Write-Verbose ($Me + ': Asset Export Status: ' + $ExportStatus.status) } # We should have our results available for download now $Assets = @() foreach ($Chunk in $ExportStatus.chunks_available) { $Assets += Get-TioExportAssetChunk -ApiKeys $ApiKeys -Uuid $Uuid -Chunk $Chunk } Write-Output $Assets } End { } } function Get-TioExportAssetChunk { <# .SYNOPSIS Download exported asset chunk by ID. .DESCRIPTION Download exported asset chunk by ID. Chunks are available for download for up to 24 hours after they have been created. Tenable.io returns a 404 message for expired chunks. .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Uuid The UUID of the export request. .PARAMETER Chunk The ID of the Asset Chunk you want to download .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ById')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Asset Export UUID')] [string] $Uuid, [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Asset Chunk Number')] [string] $Chunk ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "assets/export", $uuid, "chunks", $Chunk) } Process { # Initiate the Asset Export Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportChunk = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method Write-Output $ExportChunk } End { } } function Get-TioExportAssetStatus { <# .SYNOPSIS Get the status of an in-progress asset export .DESCRIPTION This function returns information about one or more Tenable.io Assets .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported assets. To return all assets, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "assets/export", $uuid, "status") } Process { # Get an updated asset export status Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportStatus = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method if ($ExportStatus.exports) { Write-Output $ExportStatus.exports } else { Write-Output $ExportStatus } } End { } } function Get-TioExportVuln { <# .SYNOPSIS Exports all vulnerabilities that match the request criteria. .DESCRIPTION This function returns information about one or more Tenable.io Vulnerabilities .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported vulnerabilities. To return all vulnerabilities, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='IncludeAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory=$false, HelpMessage = 'Assets per chunk')] [int64] $ChunkSize = 1000, [Parameter(Mandatory=$false, HelpMessage = 'Include Unlicensed Assets')] [switch] $IncludeUnlicensed, [Parameter(Mandatory=$true, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [psobject] $Filter, [Parameter(Mandatory=$true, ParameterSetName = 'ByUuid', HelpMessage = 'Tag Value Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $RetryInterval = 30 $Uri.Path = [io.path]::combine($Uri.Path, "vulns/export") if (!$PSBoundParameters.ContainsKey('Uuid')) { # Starting a new search $Body = @{} $Body.Add('num_assets',$ChunkSize) if ($PSBoundParameters.ContainsKey('TagCategory')) { $Body.Add('filters',@{}) $Body.filters.add(('tag.' + $TagCategory),$TagValue) } elseif ($PSBoundParameters.ContainsKey('Filter')) { $Body.Add('filters',$Filter) } } if ($PSBoundParameters.ContainsKey('IncludeUnlicensed')) { # Include Unlicensed Assets in Vulnerability Export $Body.Add('include_unlicensed','true') } } Process { if (!$PSBoundParameters.ContainsKey('Uuid')) { # Initiate the Vuln Export Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportParams = @{ ApiKeys = $ApiKeys ChunkSize = $ChunkSize } if ($PSBoundParameters.ContainsKey('Filter')) { $ExportParams.Add('Filter', $Filter) } $VulnExport = Start-TioExportVuln @ExportParams $Uuid = $VulnExport } Write-Verbose ($Me + ': Vuln Export ID: ' + $Uuid) # Start by checking the status $ExportStatus = Get-TioExportVulnStatus -ApiKeys $ApiKeys -Uuid $Uuid if ($ExportStatus.Error) { Write-Error ("$Me : Exception: $($ExportStatus.Code) : $($ExportStatus.Note)") } Write-Verbose ($Me + ': Vuln Export Status: ' +$ExportStatus.status) # Wait until the export is finished while ($ExportStatus.status -ne 'FINISHED') { Start-Sleep -Seconds $RetryInterval $ExportStatus = Get-TioExportVulnStatus -ApiKeys $ApiKeys -Uuid $Uuid # Check for failures if ($ExportStatus.status -eq 'CANCELLED' -or $ExportStatus.status -eq 'ERROR') { Write-Error 'Vuln Export Failed' exit 1 } Write-Verbose ($Me + ': Vuln Export Status: ' + $ExportStatus.status) } # We should have our results available for download now $Vulns = @() foreach ($Chunk in $ExportStatus.chunks_available) { $Vulns += Get-TioExportVulnChunk -ApiKeys $ApiKeys -Uuid $Uuid -Chunk $Chunk } Write-Output $Vulns } End { } } function Get-TioExportVulnChunk { <# .SYNOPSIS Download exported Vuln chunk by ID. .DESCRIPTION Download exported Vuln chunk by ID. Chunks are available for download for up to 24 hours after they have been created. Tenable.io returns a 404 message for expired chunks. .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Uuid The UUID of the export request. .PARAMETER Chunk The ID of the Vuln Chunk you want to download .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ById')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Vuln Export UUID')] [string] $Uuid, [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Vuln Chunk Number')] [string] $Chunk ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "vulns/export", $uuid, "chunks", $Chunk) } Process { # Initiate the Vuln Export Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportChunk = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method Write-Output $ExportChunk } End { } } function Get-TioExportVulnStatus { <# .SYNOPSIS Get the status of an in-progress Vuln export .DESCRIPTION This function returns information about one or more Tenable.io Vulns .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported Vulns. To return all Vulns, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$true, ParameterSetName = 'ById', HelpMessage = 'Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "vulns/export", $uuid, "status") } Process { # Get an updated Vuln export status Write-Verbose "$Me : Uri : $($Uri.Uri)" $ExportStatus = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method if ($ExportStatus.exports) { Write-Output $ExportStatus.exports } else { Write-Output $ExportStatus } } End { } } function Start-TioExportAsset { <# .SYNOPSIS Starts an asynchronous export of all assets that match the request criteria. .DESCRIPTION This function returns the UUID of the export job that is executing in the Tenable.io environment .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported assets. To return all assets, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ByTag', SupportsShouldProcess)] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory = $false, HelpMessage = 'Results per chunk')] [int64] $ChunkSize = 1000, [Parameter(Mandatory=$true, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [PSObject] $Filter, [Parameter(Mandatory=$true, ParameterSetName = 'ByTag', HelpMessage = 'Tag Category Filter condition')] [string] $TagCategory, [Parameter(Mandatory=$true, ParameterSetName = 'ByTag', HelpMessage = 'Tag Value Filter condition')] [string] $TagValue ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "assets/export") # Starting a new search $Body = @{} $Body.Add('chunk_size',$ChunkSize) if ($PSBoundParameters.ContainsKey('TagCategory')) { $Body.Add('filters',@{}) $Body.filters.add(('tag.' + $TagCategory),$TagValue) } elseif ($PSBoundParameters.ContainsKey('Filter')) { $Body.Add('filters',$Filter) } Write-Debug ('{0}: Filters: {1}' -f $Me, ($Filter | ConvertTo-Json -Compress)) } Process { # Initiate the Asset Export Write-Verbose "$Me : Uri : $($Uri.Uri)" Write-Debug ('{0}: Body: {1}' -f $Me, ($Body | ConvertTo-Json -Depth 10 -Compress)) if ($PSCmdlet.ShouldProcess("$Uri", "Start Asset Export Task")) { $AssetExport = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Body } $Uuid = $AssetExport.export_uuid Write-Verbose ($Me + ': Asset Export ID: ' + $Uuid) Write-Output $Uuid } End { } } function Start-TioExportVuln { <# .SYNOPSIS Starts an export of all vulnerabilities that match the request criteria. .DESCRIPTION This function returns the UUID for the export task running in Tenable.io .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported vulnerabilities. To return all vulnerabilities, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'IncludeAll', SupportsShouldProcess)] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory=$false, HelpMessage = 'Assets per chunk')] [int64] $ChunkSize = 1000, [Parameter(Mandatory=$false, HelpMessage = 'Include Unlicensed Assets')] [switch] $IncludeUnlicensed, [Parameter(Mandatory=$true, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [psobject] $Filter ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "vulns/export") # Starting a new search $Body = @{} $Body.Add('num_assets',$ChunkSize) if ($PSBoundParameters.ContainsKey('TagCategory')) { $Body.Add('filters',@{}) $Body.filters.add(('tag.' + $TagCategory),$TagValue) } elseif ($PSBoundParameters.ContainsKey('Filter')) { $Body.Add('filters',$Filter) } if ($PSBoundParameters.ContainsKey('IncludeUnlicensed')) { # Include Unlicensed Assets in Vulnerability Export $Body.Add('include_unlicensed','true') } } Process { # Initiate the Vuln Export Write-Verbose "$Me : Uri : $($Uri.Uri)" if ($PSCmdlet.ShouldProcess($Uri, "Start Vulnerability Export Task")) { $VulnExport = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Body } $Uuid = $VulnExport.export_uuid Write-Verbose ($Me + ': Vuln Export ID: ' + $Uuid) Write-Output $Uuid } End { } } function Stop-TioExportAsset { <# .SYNOPSIS Cancel an in-progress asset export .DESCRIPTION This function returns information about one or more Tenable.io Assets .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported assets. To return all assets, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll',SupportsShouldProcess)] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "assets/export", $uuid, "cancel") } Process { # Initiate the Asset Export Write-Verbose "$Me : Uri : $($Uri.Uri)" if ($PSCmdlet.ShouldProcess($Uri.Uri, "Cancel Asset Export")) { $ExportStatus = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Filter } Write-Output $ExportStatus } End { } } function Stop-TioExportVuln { <# .SYNOPSIS Cancel an in-progress Vuln export .DESCRIPTION This function returns information about one or more Tenable.io Vulns .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Filter Specifies filters for exported Vulns. To return all Vulns, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll',SupportsShouldProcess)] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "POST", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Filter condition')] [string] $Uuid ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "vulns/export", $uuid, "cancel") } Process { # Initiate the Vuln Export Write-Verbose "$Me : Uri : $($Uri.Uri)" if ($PSCmdlet.ShouldProcess($Uri.Uri, "Cancel Vuln Export")) { $ExportStatus = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Filter } Write-Output $ExportStatus } End { } } function Get-TioFolder { <# .SYNOPSIS Lists both Tenable-provided folders and the current user's custom folders. .DESCRIPTION This function returns information about one or more Tenable.io Folders .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$true, ParameterSetName = 'ByName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Name of folder for which to retrieve details')] [string] $Name, [Parameter(Mandatory=$true, ParameterSetName = 'ByType', HelpMessage = 'Type of folder for which to retrieve details')] [ValidateSet('main', 'trash', 'custom')] [string] $Type ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "folders") } Process { Write-Verbose "$Me : Uri : $($Uri.Uri)" $Folders = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys Write-Debug ($Folders | ConvertTo-Json -depth 10) if ($PSBoundParameters.ContainsKey('Name')) { Write-Verbose ('Checking Folders by Name: ' + $Name) # We're looking up a specific Name foreach ($Folder in $Folders.folders) { Write-Verbose (' Checking: ' + $Folder.name) if ($Folder.name -eq $Name) { Write-Output $Folder } } } elseif ($PSBoundParameters.ContainsKey('Type')) { Write-Verbose ('Checking Folders by Type: ' + $Type) # We're looking up a specific Type foreach ($Folder in $Folders.folders) { Write-Verbose (' Checking: ' + $Folder.type) if ($Folder.type -eq $Type) { Write-Output $Folder } } } else { if ($Folders.folders) { Write-Output $Folders.folders } else { Write-Output $Folders } } } End { } } function Invoke-TioApiRequest { <# .SYNOPSIS Invoke the Tenable.io API .DESCRIPTION This function is intended to be called by other functions for specific resources/interactions .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .PARAMETER Depth How deep are we going? .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding()] param( [Parameter(Mandatory=$true, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri, [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, HelpMessage = 'PsCustomObject containing data that will be sent as the Json Body')] [PsCustomObject] $Body, [Parameter(Mandatory=$false, HelpMessage = 'How deep are we?')] [int] $Depth = 0 ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me if (($Method -eq 'GET') -and $Body) { throw "Cannot specify Request Body for Method GET." } $Header = @{} $ApiKey = "accessKey={0}; secretKey={1}" -f $ApiKeys.AccessKey.GetNetworkCredential().Password, $ApiKeys.SecretKey.GetNetworkCredential().Password $Header.Add('X-ApiKeys', ($ApiKey)) $Header.Add('Content-Type', 'application/json') $Header.Add('Accept', 'application/json') } Process { # Setup Error Object structure $ErrorObject = [PSCustomObject]@{ Code = $null Error = $false Type = $null Note = $null Raw = $_ } $Results = $null # Enforce TLSv1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 # Make the API Call if ($Body) { # Make the API Call, using the supplied Body. Contents of $Body are the responsibility of the calling code. Write-Verbose "$Me : Body supplied" Write-Debug ("$Me : Body : " + ($Body | ConvertTo-Json -Depth 20 -Compress)) try { $Results = Invoke-RestMethod -Method $Method -Uri $Uri.Uri -Headers $Header -Body ($Body|ConvertTo-Json -Depth 10) -ResponseHeadersVariable ResponseHeaders } catch { $Exception = $_.Exception Write-Verbose "$Me : Exception : $($Exception.Response.StatusCode.value__) : $($Exception.Message)" $ErrorObject.Error = $true $ErrorObject.Code = $Exception.Response.StatusCode.value__ $ErrorObject.Note = $Exception.Message $ErrorObject.Raw = $Exception Write-Debug ($ErrorObject | ConvertTo-Json -Depth 10) return $ErrorObject } Write-Debug ($ResponseHeaders | ConvertTo-Json -Depth 5) Write-Debug ($Results | ConvertTo-Json -Depth 10) } else { # Make the API Call without a body. This is for GET requests, where details of what we want to get is in the URI Write-Verbose "$Me : No Body supplied" try { $Results = Invoke-RestMethod -Method $Method -Uri $Uri.Uri -Headers $Header -ResponseHeadersVariable ResponseHeaders } catch { $Exception = $_.Exception Write-Verbose "$Me : Exception : $($Exception.StatusCode)" $ErrorObject.Error = $true $ErrorObject.Code = $Exception.Response.StatusCode.value__ $ErrorObject.Note = $Exception.Message $ErrorObject.Raw = $Exception # Write-Debug ($ErrorObject | ConvertTo-Json -Depth 2) Throw "$Me : Encountered error getting response. $($ErrorObject.Code) : $($ErrorObject.Note) from: $RelLink" return $ErrorObject } } Write-Debug ($ResponseHeaders | ConvertTo-Json -Depth 5) Write-Output $Results } End { Write-Verbose "Returning from Depth: $Depth" return } } function New-TioCredential { <# .SYNOPSIS Build a new Tenable.io credential object .DESCRIPTION This function is intended to create a PSObject containing 2 PSCredential Objects containing the AccessKey and SecretKey .PARAMETER AccessKey String containing the Access Key. If not specified, user will be prompted. .PARAMETER SecretKey String containing the Secret Key. If not specified, user will be prompted. .OUTPUTS PSCustomObject containing the AccessKey and SecretKey PSCredential Object, containing the keys supplied. #> [CmdletBinding(SupportsShouldProcess=$true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] param( [Parameter(Mandatory=$false, HelpMessage = 'Tenable.IO Access Key')] [string] $AccessKey, [Parameter(Mandatory=$false, HelpMessage = 'Tenable.IO Secret Key')] [string] $SecretKey ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me } Process { $Credential = @{} if ($AccessKey) { Write-Verbose 'Access Key Supplied' $AccessKeyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'AccessKey', ($AccessKey | ConvertTo-SecureString -AsPlainText -Force) } else { $AccessKeyCredential = (Get-Credential -UserName 'AccessKey' -Message 'Tenable.IO Access Key') } $Credential.Add('AccessKey', $AccessKeyCredential) if ($SecretKey) { Write-Verbose 'Secret Key Supplied' $SecretKeyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'SecretKey', ($SecretKey | ConvertTo-SecureString -AsPlainText -Force) } else { $SecretKeyCredential = (Get-Credential -UserName 'SecretKey' -Message 'Tenable.IO Secret Key') } $Credential.Add('SecretKey', $SecretKeyCredential) if ($PSCmdlet.ShouldProcess("Tenable.IO Credential", "Generate a new Tenable.IO Credential Object")) { Write-Output $Credential } } End { return } } function Get-TioMsspAccount { <# .SYNOPSIS Get Account information .DESCRIPTION This function returns information about one or more Tenable.io Accounts .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id (UUID) of Account for which to retrieve details')] [Alias("Id")] [string] $Uuid, [Parameter(Mandatory=$false, ParameterSetName = 'ByContainerName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Container Name of account for which to retrieve details')] [string] $Container, [Parameter(Mandatory=$false, ParameterSetName = 'ByCustomName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Custom name of account for which to retrieve details')] [string] $Name, [Parameter(Mandatory=$false, ParameterSetName = 'ByCustomName', HelpMessage = 'Require an exact match')] [switch] $Exact ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "mssp/accounts") } Process { Write-Verbose "$Me : Uri : $($Uri.Uri)" $Accounts = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($PSBoundParameters.ContainsKey('Uuid')) { Write-Verbose ('Checking Accounts by Uuid: ' + $Uuid) # We're looking up a specific Uuid foreach ($Account in $Accounts.accounts) { Write-Verbose (' Checking: ' + $Account.uuid) if ($Account.uuid -eq $Uuid) { Write-Output $Account } } } elseif ($PSBoundParameters.ContainsKey('Container')) { Write-Verbose ('Checking Accounts by Container: ' + $Container) # We're looking up by Container Name foreach ($Account in $Accounts.Accounts) { Write-Verbose (' Checking: ' + $Account.container_name) if ($Account.container_name -eq $Container) { Write-Output $Account } } } elseif ($PSBoundParameters.ContainsKey('Name')) { Write-Verbose ('Checking Accounts by Custom Name: ' + $Name) # We're looking up by custom name foreach ($Account in $Accounts.Accounts) { if ($Exact) { Write-Verbose (' Checking (exact): ' + $Account.custom_name) if ($Account.custom_name -eq $Name) { Write-Output $Account } } else { Write-Verbose (' Checking (match): ' + $Account.custom_name) if ($Account.custom_name -match $Name) { Write-Output $Account } } } } else { if ($Accounts.accounts) { Write-Output $Accounts.accounts } else { Write-Output $Accounts } } } End { } } function Get-TioMsspLogo { <# .SYNOPSIS Get Logo information .DESCRIPTION This function returns information about one or more Tenable.io Logos .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id (UUID) of Logo for which to retrieve details')] [Alias("Id")] [string] $Uuid, [Parameter(Mandatory=$false, ParameterSetName = 'ByContainerId', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Container Id of Logo for which to retrieve details')] [string] $ContainerId, [Parameter(Mandatory=$false, ParameterSetName = 'ByName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Custom name of Logo for which to retrieve details')] [string] $Name, [Parameter(Mandatory=$false, ParameterSetName = 'ByName', HelpMessage = 'Require an exact match')] [switch] $Exact ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "mssp/logos") } Process { if ($PSBoundParameters.ContainsKey('Uuid')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Uuid) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Logos = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($PSBoundParameters.ContainsKey('ContainerId')) { Write-Verbose ('Checking Logos by ContainerId: ' + $ContainerID) # We're looking up by Container Id foreach ($Logo in $Logos.Logos) { Write-Verbose (' Checking: ' + $Logo.container_uuid) if ($Logo.container_uuid -eq $ContainerId) { Write-Output $Logo } } } elseif ($PSBoundParameters.ContainsKey('Name')) { Write-Verbose ('Checking Logos by Custom Name: ' + $Name) # We're looking up by custom name foreach ($Logo in $Logos.logos) { if ($Exact) { Write-Verbose (' Checking (exact): ' + $Logo.name) if ($Logo.name -eq $Name) { Write-Output $Logo } } else { Write-Verbose (' Checking (match): ' + $Logo.name) if ($Logo.name -match $Name) { Write-Output $Logo } } } } else { if ($Logos.logos) { Write-Output $Logos.logos } else { Write-Output $Logos } } } End { } } function Set-TioMsspLogo { <# .SYNOPSIS Get Logo information .DESCRIPTION This function returns information about one or more Tenable.io Logos .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ById',SupportsShouldProcess)] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "PUT", [Parameter(Mandatory=$true, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id (UUID) of Logo for which to assign on account(s)')] [Alias("Id")] [string] $Uuid, [Parameter(Mandatory=$true, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Array of Account Ids to assign logo to')] [string[]] $Accounts ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "mssp/logos") } Process { Write-Verbose "$Me : Uri : $($Uri.Uri)" $Body = @{} $Body.Add('logo_uuid',$Uuid) if (($Accounts.GetType()).Name -eq 'String') { $AccountList = @() $AccountList += $Accounts } else { $AccountList = $Accounts } $Body.Add('account_uuids',$AccountList) if ($PSCmdlet.ShouldProcess("Logo ID $Uuid", "Assign to Accounts")) { $Logos = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Body } Write-Output $Logos } End { } } function Get-TioScanner { <# .SYNOPSIS Get Tenable Scanner Information .DESCRIPTION This function returns information about available Tenable Scanners .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param( [Parameter(Mandatory = $false, HelpMessage = 'Base URI for the Tenable Environment, defaults to Tenable Cloud')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory = $true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory = $false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id of Scanner for which to retrieve details')] [Alias("Uuid")] [string] $Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "scanners") $Method = 'GET' } Process { if ($PSBoundParameters.ContainsKey('Id')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Id) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Response = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method if ($Response.scanners) { Write-Output $Response.scanners } else { Write-Output $Response } } End { } } function Get-TioServer { <# .SYNOPSIS Get Tenable Server Information .DESCRIPTION This function returns information about the Tenable Server .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param( [Parameter(Mandatory = $false, HelpMessage = 'Base URI for the Tenable Environment, defaults to Tenable Cloud')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory = $true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "server", "properties") $Method = 'GET' } Process { Write-Verbose "$Me : Uri : $($Uri.Uri)" $Response = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method Write-Output $Response } End { } } function Get-TioServerStatus { <# .SYNOPSIS Get Tenable Server Status .DESCRIPTION This function returns the operational status of the Tenable Server .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param( [Parameter(Mandatory = $false, HelpMessage = 'Base URI for the Tenable Environment, defaults to Tenable Cloud')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory = $true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "server", "status") $Method = 'GET' } Process { Write-Verbose "$Me : Uri : $($Uri.Uri)" $Response = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method Write-Output $Response } End { } } function Get-TioTagCategory { <# .SYNOPSIS Get Tag Category information .DESCRIPTION This function returns information about one or more Tenable.io Tag Categories .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', HelpMessage = 'Id (UUID) of Category for which to retrieve details')] [string] $Uuid, [Parameter(Mandatory=$false, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [string] $Filter, [Parameter(Mandatory=$false, ParameterSetName = 'ByName', HelpMessage = 'Get details of Asset Tag Categry by name')] [string] $Name ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "tags/categories") # Uri Parameter based processing $UriQuery = [System.Web.HttpUtility]::ParseQueryString([string]$Uri.Query) if ($PSBoundParameters.ContainsKey('Filter')) { $UriQuery.Add('f',$Filter) } elseif ($PSBoundParameters.ContainsKey('Name')) { $NameFilter = 'name:eq:{0}' -f $Name $UriQuery.Add('f',$NameFilter) } # Add the parameters to the URI object $Uri.Query = $UriQuery.ToString() } Process { if ($PSBoundParameters.ContainsKey('Uuid')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Uuid) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Category = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($Category.categories) { Write-Output $Category.categories } else { Write-Output $Category } } End { } } function Get-TioTagValue { <# .SYNOPSIS Get Tag Category information .DESCRIPTION This function returns information about one or more Tenable.io Tag Categories .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', HelpMessage = 'Id (UUID) of Tag Category Value for which to retrieve details')] [string] $Uuid, [Parameter(Mandatory=$false, ParameterSetName = 'ByFilter', HelpMessage = 'Filter condition')] [string] $Filter, [Parameter(Mandatory=$false, ParameterSetName = 'ByValue', HelpMessage = 'Get details of Asset Tag by Value')] [string] $Value, [Parameter(Mandatory=$false, ParameterSetName = 'ByCategoryName', HelpMessage = 'Get details of Asset Tag Vaues by Categry name')] [string] $CategoryName ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "tags/values") # Uri Parameter based processing $UriQuery = [System.Web.HttpUtility]::ParseQueryString([string]$Uri.Query) if ($PSBoundParameters.ContainsKey('Filter')) { $UriQuery.Add('f',$Filter) } elseif ($PSBoundParameters.ContainsKey('Value')) { $TagFilter = 'value:eq:{0}' -f $Value $UriQuery.Add('f',$TagFilter) } elseif ($PSBoundParameters.ContainsKey('CategoryName')) { $TagFilter = 'category_name:eq:{0}' -f $CategoryName $UriQuery.Add('f',$TagFilter) } # Add the parameters to the URI object $Uri.Query = $UriQuery.ToString() } Process { if ($PSBoundParameters.ContainsKey('Uuid')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Uuid) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Category = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($Category.values) { Write-Output $Category.values } else { Write-Output $Category } } End { } } function Get-TioVulnPlugin { <# .SYNOPSIS Get Vuln Plugin information .DESCRIPTION This function returns information about one or more Tenable.io Vuln Plugins .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id of Vuln Plugin for which to retrieve details')] [string] $Id, [Parameter(Mandatory=$false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'The number of records to include in the result set')] [ValidateRange(1, 10000)] [int32] $Size = 1000, [Parameter(Mandatory=$false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'The page number of the result set')] [ValidateRange(1, 10000)] [int32] $Page = 1, [Parameter(Mandatory=$false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'Only provide Plugins updated since the provided date (YYY-MM-DD)')] [string] $LastUpdated ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "plugins/plugin") } Process { $QueryParam = [System.Web.HttpUtility]::ParseQueryString($Uri.Query) if ($PSBoundParameters.ContainsKey('Id')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Id) } else { $QueryParam['size'] = $Size $QueryParam['page'] = $Page if ($PSBoundParameters.ContainsKey('LastUpdated')) { $QueryParam['last_updated'] = $LastUpdated } } $Uri.Query = $QueryParam.ToString() Write-Verbose "$Me : Uri : $($Uri.Uri)" $Plugins = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if (!($PSBoundParameters.ContainsKey('Page'))) { # A specific page was not requested, get all available pages $Page = 1 if ($Plugins.data.plugin_details) { Write-Output $Plugins.data.plugin_details $ResCount = $Plugins.data.plugin_details.Count } else { Write-Output $Plugins } While ($ResCount -eq $size) { $Page++ Write-Verbose ("{0} : Fetching Page {1}" -f $Me, $Page) $Plugins = Get-TioVulnPlugin -ApiKeys $ApiKeys -Method $Method -Size $Size -Page $Page Write-Output $Plugins $ResCount = $Plugins.Count } } else { # Provide only the requested page if ($Plugins.data.plugin_details) { Write-Output $Plugins.data.plugin_details } else { Write-Output $Plugins } } } End { } } function Get-TioVulnPluginFamily { <# .SYNOPSIS Get Vuln Plugin information .DESCRIPTION This function returns information about one or more Tenable.io Vuln Plugins .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .PARAMETER Method Valid HTTP Method to use: GET (Default), POST, DELETE, PUT .PARAMETER Body PSCustomObject containing data to be sent as HTTP Request Body in JSON format. .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName='ListAll')] param( [Parameter(Mandatory=$false, HelpMessage = 'Full URI to requested resource, including URI parameters')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory=$true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'The ID of the plugin family you want to retrieve the list of plugins for.')] [string] $Id, [Parameter(Mandatory=$false, ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, HelpMessage = 'Specifies whether to return all plugin families. If true, the plugin families hidden in Tenable Vulnerability Management UI, for example, Port Scanners, are included in the list.')] [boolean] $All = $false ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "plugins/families") } Process { if ($PSBoundParameters.ContainsKey('Id')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Id) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $PluginFamilies = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys if ($PluginFamilies.families) { Write-Output $PluginFamilies.families $ResCount = $PluginFamilies.families.Count } elseif ($PluginFamilies.plugins) { Write-Output $PluginFamilies.plugins $ResCount = $PluginFamilies.plugins.count } else { Write-Output $PluginFamilies } While ($ResCount -eq $size) { $Page++ Write-Verbose ("{0} : Fetching Page {1}" -f $Me, $Page) $PluginFamilies = Get-TioVulnPlugin -ApiKeys $ApiKeys -Method $Method -Size $Size -Page $Page Write-Output $PluginFamilies $ResCount = $PluginFamilies.Count } } End { } } function Get-TioVulnScan { <# .SYNOPSIS Get Tenable Scan Information .DESCRIPTION This function returns information about configured Tenable Vulnerability Scans .PARAMETER Uri Base API URL for the API Call .PARAMETER ApiKeys PSObject containing PSCredential Objects with AccessKey and SecretKey. Must contain PSCredential Objects named AccessKey and SecretKey with the respective keys stored in the Password property .OUTPUTS PSCustomObject containing results if successful. May be $null if no data is returned ErrorObject containing details of error if one is encountered. #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param( [Parameter(Mandatory = $false, HelpMessage = 'Base URI for the Tenable Environment, defaults to Tenable Cloud')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder] $Uri = 'https://cloud.tenable.com', [Parameter(Mandatory = $true, HelpMessage = 'PSObject containing PSCredential Objects with AccessKey and SecretKey')] [PSObject] $ApiKeys, [Parameter(Mandatory = $false, ParameterSetName = 'ById', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Id of Scanner for which to retrieve details')] [Alias("Uuid")] [string] $Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose $Me $Uri.Path = [io.path]::combine($Uri.Path, "scans") $Method = 'GET' } Process { if ($PSBoundParameters.ContainsKey('Id')) { # We're looking up a specific Id $Uri.Path = [io.path]::combine($Uri.Path, $Id) } Write-Verbose "$Me : Uri : $($Uri.Uri)" $Response = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method if ($Response.scans) { Write-Output $Response.scans } else { Write-Output $Response } } End { } } Export-ModuleMember -Function Get-TioAgent, Get-TioAsset, Get-TioExportAsset, Get-TioExportAssetChunk, Get-TioExportAssetStatus, Get-TioExportVuln, Get-TioExportVulnChunk, Get-TioExportVulnStatus, Start-TioExportAsset, Start-TioExportVuln, Stop-TioExportAsset, Stop-TioExportVuln, Get-TioFolder, Invoke-TioApiRequest, New-TioCredential, Get-TioMsspAccount, Get-TioMsspLogo, Set-TioMsspLogo, Get-TioScanner, Get-TioServer, Get-TioServerStatus, Get-TioTagCategory, Get-TioTagValue, Get-TioVulnPlugin, Get-TioVulnPluginFamily, Get-TioVulnScan # SIG # Begin signature block # MIIt4gYJKoZIhvcNAQcCoIIt0zCCLc8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCy1kxIb80GIdjG # HzP4CduJI0w1oJz4Ag1VOwDR1VrxIaCCEyswggWQMIIDeKADAgECAhAFmxtXno4h # MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV # BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z # ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z # G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ # anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s # Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL # 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb # BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 # JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c # AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx # YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 # viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL # T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud # EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf # Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk # aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS # PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK # 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB # cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp # 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg # dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri # RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 # 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 # nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 # i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H # EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G # CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C # 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce # 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da # E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T # SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA # FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh # D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM # 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z # 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 # huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY # mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP # /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T # AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD # VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV # HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN # BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry # sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL # IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf # Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh # OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh # dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV # 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j # wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH # Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC # XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l # /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW # eE4wggbfMIIEx6ADAgECAhABYmy7Lnkq4ZrwtGPYmCG8MA0GCSqGSIb3DQEBCwUA # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwHhcNMjQwMTE1MDAwMDAwWhcNMjUwMTE0MjM1OTU5WjBnMQsw # CQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExFTATBgNVBAcTDE5vdHRpbmcg # SGlsbDEWMBQGA1UEChMNSVBTZWMgUHR5IEx0ZDEWMBQGA1UEAxMNSVBTZWMgUHR5 # IEx0ZDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALbFLv+X/DB/f3wO # 2nvk53ZIkPhN7tzZCRKUDqY+u41Eb+QPgM/KhDP7Kl8g7uoapmrpyIPNoydJghST # C0rv23fAeOIVnA1IBFPCx4r/cgcEKKLD4QFxPhLS6pFWCEbHHWB6IXjt5uTM3/5f # c7qLunpnHKB4Nfh0845UzA30sz9mnT6CVpVKVl9owUNJusOXOtZCNrrG7nXXOphi # YtinoVCFQmq8LJO2wPQUTMY91W9IN7lPW4SARDkDidAovZu6BCveL4+K3K7UsIBT # pPopOLt6JXcitfedRsDUxz8v+Gak66dgCu2ie0jKC043FLr8ADZkHLcDKbHJnpQO # OYrOls50NU3ATuNKABUjkrtYy63WdMbDGYeCNbKSESUIkkhzG02dswRnBk0CN65V # +Jsyts6D3Ag6x10qeambGDEH8nlIZY2wdTvqT5UpGg6GvrOwu2cSt8eWYn69LCtB # XJCP6Aackgpbm8LbXHs2EHfg0yMMsdKWHTTfEZYs650U3xQUBwIDAQABo4ICAzCC # Af8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFIEj # lb9LKiYKWjk5Yyx4RPBn1Z6rMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQB6zn4EhgUEk3+8Oa2clKNbDWIOooLawaw7gO3lJp3PNCN/Ov4BrQ5NqnyO # GfEK7/eJ/7xGa6+jk4cexYEhFfzAyIUwo+Hm8f/Ui3sClnLBoX0kUBYVKT/+Npj4 # kc+4VncXdDC6q8NY0mTpRX0CdbZZgCwd04d83YX7YwqVsjkMtPCynFaGGYufG5Yo # V6gyeQy8tnm8maKRlP2yBPIH83gUT2rbfTnUaJ1lpKQP044HWNx75PpwtRK+nq9U # loYir5A6lAuyVcrTWKrpMxs5bXYxCSphqR+LKCbAa7Gg3P4X5/bTIyrPc7Tv1PsG # T8yo8wTzvAaZLpl7ncmCrlaiXusbKoVxVTJ8FKUUMLCAelOKKgkdHeUvd3EIuFqM # cP6sg3cR6wu+hexS7ZOZtkfzguHtyp4A+1a6HhEbTubzY9N9Bom0WNWyUe3Yzd5d # FaqtIkntwju48TIHdvP06Kh2166c7mIX+Krq/TR8AeND2FhhsNO/yTP+aU6CebBF # M94Ds14Q9aNPYD0ml0lPn2nFIcGPZetfnXCxQC/eudLrGd8jGaQjeeKQIvkhiCqL # POXcTQXu2PhwU14tJYehWtYZITLScPzLklMIOuGwO3LqsS/vvVXK5+N7CqxHgzAV # JAjIbgLvBdteW/zoUiJLifJNOdSAVc4DvDdw4elMWpIR7gCzfDGCGg0wghoJAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQAWJsuy55KuGa8LRj2JghvDANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCBQ9STNuGuZV8ah+kUN2X97J1wQcxAg1ATin7cCorzf7jANBgkqhkiG9w0B # AQEFAASCAYCS49Z7dXhopzk3LIcJ4jopm//AmskWEMn6vV0Kk+E4SWIYFOPd/QQE # AU38mKH7/aBHzCwiEZMcfdx+SxTF8iUYhEO7LLPgzzXmeLI3LJLdv/HbytiBcZYh # FkotCf2Oy5TgEyAkrBPgAqUm7SGBjvBwKVwsCX1lTz5OeRJ5ehQZoUAvEkU8WjUU # DSOmWH2fYXj71nKDjtUN9pQFN60gpKFkMC0dFdA0UQF9FNfnIZmXqoc+nBSoHjhk # wPkPUHzRTKGIhKFmEn5MXIrJhy+CvwQOy53AAD7+gsznCkjSKIJgkn+KORYXggJt # MHRLNcM5R4WdBJw6u5sUd1b+UNpBjSa9bvOhD6SA66Qa73LJeylnqjUaawmdFBUX # CFKqsmXBWVOKnbz0RggV5XeQcwn3bCp8YXFLmFvYxs3OdjRztxNJCHjaK5szKs0d # gbpoGAi8o8ILJZuNDcLanBwqvx4BH7yT5agxB7nuM91SQMgZm9E5OxYQWomaGPze # W9qmoBuP/iihghdaMIIXVgYKKwYBBAGCNwMDATGCF0YwghdCBgkqhkiG9w0BBwKg # ghczMIIXLwIBAzEPMA0GCWCGSAFlAwQCAgUAMIGHBgsqhkiG9w0BCRABBKB4BHYw # dAIBAQYJYIZIAYb9bAcBMEEwDQYJYIZIAWUDBAICBQAEMDhtK800Gerq0apxwD1Q # qE/0QmlALamSWb8ecpHWiJ4yw++6LuJmpiF1+mHqM8HLdwIQPz1V1LWn+XQCAFYy # yKsK8xgPMjAyNDEwMjQwMjQ1MzhaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+V # h8b56QTjMwQwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoT # DkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJT # QTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yNDA5MjYwMDAwMDBaFw0z # NTExMjUyMzU5NTlaMEIxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEg # MB4GA1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjQwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC+anOf9pUhq5Ywultt5lmjtej9kR8YxIg7apnjpcH9 # CjAgQxK+CMR0Rne/i+utMeV5bUlYYSuuM4vQngvQepVHVzNLO9RDnEXvPghCaft0 # djvKKO+hDu6ObS7rJcXa/UKvNminKQPTv/1+kBPgHGlP28mgmoCw/xi6FG9+Un1h # 4eN6zh926SxMe6We2r1Z6VFZj75MU/HNmtsgtFjKfITLutLWUdAoWle+jYZ49+wx # GE1/UXjWfISDmHuI5e/6+NfQrxGFSKx+rDdNMsePW6FLrphfYtk/FLihp/feun0e # V+pIF496OVh4R1TvjQYpAztJpVIfdNsEvxHofBf1BWkadc+Up0Th8EifkEEWdX4r # A/FE1Q0rqViTbLVZIqi6viEk3RIySho1XyHLIAOJfXG5PEppc3XYeBH7xa6VTZ3r # OHNeiYnY+V4j1XbJ+Z9dI8ZhqcaDHOoj5KGg4YuiYx3eYm33aebsyF6eD9MF5IDb # PgjvwmnAalNEeJPvIeoGJXaeBQjIK13SlnzODdLtuThALhGtyconcVuPI8AaiCai # JnfdzUcb3dWnqUnjXkRFwLtsVAxFvGqsxUA2Jq/WTjbnNjIUzIs3ITVC6VBKAOlb # 2u29Vwgfta8b2ypi6n2PzP0nVepsFk8nlcuWfyZLzBaZ0MucEdeBiXL+nUOGhCjl # +QIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD # VR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI # AYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQW # BBSfVywDdw4oFZBmpWNe7k+SH3agWzBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2 # VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hB # MjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQA9rR4fdplb # 4ziEEkfZQ5H2EdubTggd0ShPz9Pce4FLJl6reNKLkZd5Y/vEIqFWKt4oKcKz7wZm # Xa5VgW9B76k9NJxUl4JlKwyjUkKhk3aYx7D8vi2mpU1tKlY71AYXB8wTLrQeh83p # XnWwwsxc1Mt+FWqz57yFq6laICtKjPICYYf/qgxACHTvypGHrC8k1TqCeHk6u4I/ # VBQC9VK7iSpU5wlWjNlHlFFv/M93748YTeoXU/fFa9hWJQkuzG2+B7+bMDvmgF8V # lJt1qQcl7YFUMYgZU1WM6nyw23vT6QSgwX5Pq2m0xQ2V6FJHu8z4LXe/371k5QrN # 9FQBhLLISZi2yemW0P8ZZfx4zvSWzVXpAb9k4Hpvpi6bUe8iK6WonUSV6yPlMwer # wJZP/Gtbu3CKldMnn+LmmRTkTXpFIEB06nXZrDwhCGED+8RsWQSIXZpuG4WLFQOh # tloDRWGoCwwc6ZpPddOFkM2LlTbMcqFSzm4cd0boGhBq7vkqI1uHRz6Fq1IX7TaR # QuR+0BGOzISkcqwXu7nMpFu3mgrlgbAW+BzikRVQ3K2YHcGkiKjA4gi4OA/kz1YC # sdhIBHXqBzR0/Zd2QwQ/l4Gxftt/8wY3grcc/nS//TVkej9nmUYu83BDtccHHXKi # bMs/yXHhDXNkoPIdynhVAku7aRZOwqw6pDCCBq4wggSWoAMCAQICEAc2N7ckVHzY # R6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE # AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3 # MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2 # IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjz # aPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3E # F3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYnc # fGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8O # pWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROp # VymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4i # FNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmif # tkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0 # UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9Ne # S3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCj # WAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTAS # BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57I # bzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMC # AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0 # MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG # SAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAY # LhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQx # Z822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf # 7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDV # inF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7 # +6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJ # D5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvk # OHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJG # nXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimG # sJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38A # C+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d # 2zc4GqEr9u3WfPwwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqG # SIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFz # c3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTla # MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT # EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9v # dCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8 # MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauy # efLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34Lz # B4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+x # embud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhA # kHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1Lyu # GwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2 # PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37A # lLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD7 # 6GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ # ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXA # j6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTAD # AQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF # 66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEE # bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB # BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy # ZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAI # MAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979X # B72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4k # vFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU # 53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pc # VIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5v # Iy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggOG # MIIDggIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU # aW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAICBQCg # geEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0y # NDEwMjQwMjQ1MzhaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2 # cUhQhyTVhltFMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEIHZ2n6jyYy8fQws6IzCu # 1lZ1/tdz2wXWZbkFk5hDj5rbMD8GCSqGSIb3DQEJBDEyBDBmgiK1neoxhcCPA+K1 # w/DAYEnUVZjiwDUYgdNap9YPLhw54cK31gsFXAh0VJsrTd0wDQYJKoZIhvcNAQEB # BQAEggIAGwquJ9HF9ZCcDmBYXf1qa34aEaRBJu3Hb7tMejEEvJapdA7j7KbCALy2 # bL7iSBK/++sUkNAOb+Dzj7rl/J3jgZrxtndKSJJ4E30weYW0q7pXCRCRxlXFujjD # DybyCgBC8NTaB3pBPUrYByp2Jbp6pbf1Cc3yrBZxl7xDYwh3yA/ZG7bJRMWwwOE1 # vl8undFoQfjYz3WGhrE1t4BAHDkmHmT1i6nBflC1g4pwrYATODEsRyL2igDDTcq0 # Zmn8O2P0RY85WtCBHFrLvYWonA5usT/2Hx4KyQylh6KQUA49xQhUMqDpXJatawyP # pU2wMQ/tteircMuu1EfWrTw08P48PgvHRPcMjiw+sH1UsAzhpd4Rh8SRLy+qp0j1 # gWh+f1xkLyhfhDB90yDhaMiPc5YwPzop+yan0K1yv06hr9AFaM0DlOq7908pHJf4 # u0D9iv+4uqEjXfG2MUbh02TVeaQ1qORJVBwpqZC+mJUAN6bASPTaYZVkREF98HOc # Cv9g9MXl+bRkTvucR7j5Zthi4vUrlJLy8xcu+nc+raBbTYwTyN0CmBtxmUlJpvBT # Wv07BcVH5/ATn+WohDxoOCSI483IMvd4QfEk2OilYefZTiT5MRrvk8JMB1ofwzyx # vUK0uS6K0Fhi4SUTiIfCHE+XZxg2u1QBc91qyc21h8D9wW3kRjY= # SIG # End signature block |