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-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')] [string] $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)" $AssetExport = Invoke-TioApiRequest -Uri $Uri -ApiKeys $ApiKeys -Method $Method -Body $Body $Uuid = $AssetExport.export_uuid } 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 -Body $Filter Write-Output $ExportChunk } End { } } function Get-TioExportAssetStatus { <# .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')] 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 -Body $Filter if ($ExportStatus.exports) { Write-Output $ExportStatus.exports } else { Write-Output $ExportStatus } } 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', 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 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 10 -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-Verbose ($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-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 { } } Export-ModuleMember -Function Get-TioAsset, Get-TioExportAsset, Get-TioExportAssetChunk, Get-TioExportAssetStatus, Stop-TioExportAsset, Get-TioFolder, Invoke-TioApiRequest, New-TioCredential, Get-TioMsspAccount, Get-TioMsspLogo, Set-TioMsspLogo, Get-TioTagCategory, Get-TioTagValue # SIG # Begin signature block # MIIe9QYJKoZIhvcNAQcCoIIe5jCCHuICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA+waGzxWUUYXu+ # 5QV6HYBA3mXPMblXMFP7iK9Ww+2s7KCCGQIwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggX3MIIE36ADAgECAhNeAAAAZ5nQaTAJUWULAAIAAABnMA0G # CSqGSIb3DQEBCwUAMFwxEjAQBgoJkiaJk/IsZAEZFgJhdTETMBEGCgmSJomT8ixk # ARkWA25ldDEVMBMGCgmSJomT8ixkARkWBWlwc2VjMRowGAYDVQQDExFpcHNlYy1N # RUxQQUQwMi1DQTAeFw0yMjEwMjYwMTI1NDNaFw0yMzEwMjYwMTI1NDNaMIGYMQsw # CQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExFTATBgNVBAcTDE5vdHRpbmcg # SGlsbDEWMBQGA1UEChMNSVBTZWMgUHR5IHRsZDEdMBsGA1UECxMUUGxhdGZvcm0g # RW5naW5lZXJpbmcxKDAmBgNVBAMTH0lQU2VjIFNPQyBJbnRlcm5hbCBDb2RlIFNp # Z25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7ql559hbhP/91 # rhmtN/hEoyjJQ4ixfoeMnu+PDiBsDHSD8VMwBwbSyeaRzcQF2Wyh9TuIHhiFhH26 # sFgt/423epg0hX3MdygRvHThRWcj/k6RugwjZLMAOZJ/fHc80cEqIbLrVr/w1FWs # +bom1wp0Y1xcd0+GD1ZNObBTxkfhyAtbdDXzXT7VYtMyFHPCKW9mUykLVQhsoXuq # wK4F5CWto8HR95c77ZTV/ClL5oIjgCK2sAHd1ohkDtVivTAVc3huzbTL7mZd013C # 7u8GYG2LKKa9uaH9SzQlGyYZYXwrC4RrofNCCzmgbms5WZnUCtpdcQmX5S+qb4E0 # cWVvEgqJAgMBAAGjggJzMIICbzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI # KwYBBQUHAwMwGwYJKwYBBAGCNxUKBA4wDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8E # AjAAMB0GA1UdDgQWBBQYf5vW6krL4J4C03ojtN4s6JRqGTAfBgNVHSMEGDAWgBS8 # 4sRITCwAluwf+8t2W59UD29XaDCB1AYDVR0fBIHMMIHJMIHGoIHDoIHAhoG9bGRh # cDovLy9DTj1pcHNlYy1NRUxQQUQwMi1DQSxDTj1tZWxwYWQwNCxDTj1DRFAsQ049 # UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJh # dGlvbixEQz1pcHNlYyxEQz1uZXQsREM9YXU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u # TGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIHHBggr # BgEFBQcBAQSBujCBtzCBtAYIKwYBBQUHMAKGgadsZGFwOi8vL0NOPWlwc2VjLU1F # TFBBRDAyLUNBLENOPUFJQSxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1T # ZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWlwc2VjLERDPW5ldCxEQz1hdT9j # QUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhv # cml0eTA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiFhO05g+LYEIGtjyeFkKpv # 1PpcLIPdrgSH659VAgFkAgEDMA0GCSqGSIb3DQEBCwUAA4IBAQBIy5AvHaYHtcv5 # lZybu6za0KpU/1/wdoicLacOXpfmypJR/QLI1w776WpbBRi6QQab9pZd8sMwXBfG # o5wW9Hqazk02BiZ7US62wVLpFCDLJcKVp1FngryYjz4GNxYMGx2YPW3Z/uuhFRtp # XbxwzTSl2YGJs+n/uz2WdRXkRfbyu9ENwMwERNv10nfJatOICvl7+No2Sl7EAVw1 # qQq2by68qVnHdMUjDpuUjbKR+kfTOOA6MkvsyYq0STsvsmFURl7Rir6nDfEwd5zG # Lb8e75QhcFZrTWeaKQChK0+zU6yW+UfSnKcqXZbIZQ/gqGAnNKAsRQQr8+MeMiS0 # hYQ4SLu4MIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG9w0B # AQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk # IFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lD # ZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUDxPKR # N6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1ATCyZz # lm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW1Oco # LevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS8nZH # 92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jBZHRA # p8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCYJn+g # GkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucfWmyU # 8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLcGEh/ # FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNFYLwj # jVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI+RQQ # EgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjASvUae # tdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAw # HQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX44LS # cV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF # BQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYy # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJLKftw # ig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgWvalW # zxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2MvGQm # h2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSumScb # qyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJxLaf # zYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un8WbD # Qc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SVe+0K # XzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4k9Tm # 8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJDwq9 # gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr5n8a # pIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBsAwggSo # oAMCAQICEAxNaXJLlPo8Kko9KQeAPVowDQYJKoZIhvcNAQELBQAwYzELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2Vy # dCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0y # MjA5MjEwMDAwMDBaFw0zMzExMjEyMzU5NTlaMEYxCzAJBgNVBAYTAlVTMREwDwYD # VQQKEwhEaWdpQ2VydDEkMCIGA1UEAxMbRGlnaUNlcnQgVGltZXN0YW1wIDIwMjIg # LSAyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAz+ylJjrGqfJru43B # DZrboegUhXQzGias0BxVHh42bbySVQxh9J0Jdz0Vlggva2Sk/QaDFteRkjgcMQKW # +3KxlzpVrzPsYYrppijbkGNcvYlT4DotjIdCriak5Lt4eLl6FuFWxsC6ZFO7Khbn # UEi7iGkMiMbxvuAvfTuxylONQIMe58tySSgeTIAehVbnhe3yYbyqOgd99qtu5Wbd # 4lz1L+2N1E2VhGjjgMtqedHSEJFGKes+JvK0jM1MuWbIu6pQOA3ljJRdGVq/9XtA # bm8WqJqclUeGhXk+DF5mjBoKJL6cqtKctvdPbnjEKD+jHA9QBje6CNk1prUe2nhY # HTno+EyREJZ+TeHdwq2lfvgtGx/sK0YYoxn2Off1wU9xLokDEaJLu5i/+k/kezbv # BkTkVf826uV8MefzwlLE5hZ7Wn6lJXPbwGqZIS1j5Vn1TS+QHye30qsU5Thmh1EI # a/tTQznQZPpWz+D0CuYUbWR4u5j9lMNzIfMvwi4g14Gs0/EH1OG92V1LbjGUKYvm # QaRllMBY5eUuKZCmt2Fk+tkgbBhRYLqmgQ8JJVPxvzvpqwcOagc5YhnJ1oV/E9mN # ec9ixezhe7nMZxMHmsF47caIyLBuMnnHC1mDjcbu9Sx8e47LZInxscS451NeX1XS # fRkpWQNO+l3qRXMchH7XzuLUOncCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIH # gDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZ # MBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2 # mi91jGogj57IbzAdBgNVHQ4EFgQUYore0GH8jzEU7ZcLzT0qlBTfUpwwWgYDVR0f # BFMwUTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUH # AQEEgYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBY # BggrBgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAVaoqGvNG83hXNzD8deNP1oUj8fz5lTmbJeb3coqYw3fUZPwV # +zbCSVEseIhjVQlGOQD8adTKmyn7oz/AyQCbEx2wmIncePLNfIXNU52vYuJhZqMU # KkWHSphCK1D8G7WeCDAJ+uQt1wmJefkJ5ojOfRu4aqKbwVNgCeijuJ3XrR8cuOyY # QfD2DoD75P/fnRCn6wC6X0qPGjpStOq/CUkVNTZZmg9U0rIbf35eCa12VIp0bcrS # BWcrduv/mLImlTgZiEQU5QpZomvnIj5EIdI/HMCb7XxIstiSDJFPPGaUr10CU+ue # 4p7k0x+GAWScAMLpWnR1DT3heYi/HAGXyRkjgNc2Wl+WFrFjDMZGQDvOXTXUWT5D # mhiuw8nLw/ubE19qtcfg8wXDWd8nYiveQclTuf80EGf2JjKYe/5cQpSBlIKdrAqL # xksVStOYkEVgM4DgI974A6T2RUflzrgDQkfoQTZxd639ouiXdE4u2h4djFrIHprV # wvDGIqhPm73YHJpRxC+a9l+nJ5e6li6FV8Bg53hWf2rvwpWaSxECyIKcyRoFfLpx # tU56mWz06J7UWpjIn7+NuxhcQ/XQKujiYu54BNu90ftbCqhwfvCXhHjjCANdRyxj # qCU4lwHSPzra5eX25pvcfizM/xdMTQCi2NYBDriL7ubgclWJLCcZYfZ3AYwxggVJ # MIIFRQIBATBzMFwxEjAQBgoJkiaJk/IsZAEZFgJhdTETMBEGCgmSJomT8ixkARkW # A25ldDEVMBMGCgmSJomT8ixkARkWBWlwc2VjMRowGAYDVQQDExFpcHNlYy1NRUxQ # QUQwMi1DQQITXgAAAGeZ0GkwCVFlCwACAAAAZzANBglghkgBZQMEAgEFAKCBhDAY # BgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3 # AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi # BCBjlBrNFBPChcppd+JNJU6abuPvT7JOww3vFWWm4xtgEjANBgkqhkiG9w0BAQEF # AASCAQA+9a2fYa6e2jwQ+Y6z6ToynUecFRu108HdVlKtuSnm7kZTfoOF9S5zREsu # ciduTS/8r+Ig4yLlKdfUcppWjaMvRdKgws+Db+7CUCEjNAmZoArWEIAcac5dt1fV # gijFlrm4tQTV5qHGDHuVOxWNCSRwqdGeQVo3w0u7GYeyulVDvhb2m1MBForrRgem # msgEjdSvgnpr4WD+M0WdI0w8Zq/OFdmMelIJ7IvNAhgE99y0LOUFU1REQEqDYsso # G644k1n1X/3Quemt6jgtLx3NrMxovIftOnNTl/2pTW7MsRDgJ0hbGBW/U3RyhYt9 # Al+LtqrcNh7hOXtpFy+NFxotA+GNoYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0wggMJ # AgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTsw # OQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVT # dGFtcGluZyBDQQIQDE1pckuU+jwqSj0pB4A9WjANBglghkgBZQMEAgEFAKBpMBgG # CSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIyMTEwMjAw # NDIzNlowLwYJKoZIhvcNAQkEMSIEIPmAhB5JJFjzIOkdoGrVc7ZlHVqALyqdZmyY # FencIlxgMA0GCSqGSIb3DQEBAQUABIICAMADy5/pqJo75n5L1XwpJjc5kv/4y5bi # kOzyjq9IyjpkHR7kBh2up5+tH7Clo02k+hIJ96Vr83qfsjMuPKhcnCU59HG12UPg # KfewAl7lDFTx8f2WFRu6zs2pVdpIp3TKn2QnNtVAjzZmuiENwzMJ2OFW3aQzjn6Y # ID4NdSz9dKADefHQfZSoaA+nyk4acH3iMzknv5JJSZ2nOwd+h+DBtZgd1Nn6QFJE # hkmBJQeGgGNaP323lXKorKhTfofhjIDlIIoLGy+pukIBwfJOInAJy/BP5EEjn5wf # I3RToeNqN81DuSKhcCANJ06RXKwWt0BSdAUYCH2Qooo6tSwjqWeWSESFK8C6oc7U # J82t9h6Z/VntFbNRDnIuXVsNNWsyCr8FcyV4dR+rq705GkdLmVMgMtcE1KkAhvhW # hEaatNiVCNoEB503qyMfhF4w8Rs9zqk6U6T4Mrlw6/vhoSNzRm5/Z7m41k67367M # EcsUZxeJ0hhKRC/OXB3E6J5BlXGwhQrP+c6GcsWq8BJU3wXPDo2o2V78zZLfWynC # 8TkVa1vV3WzNZE2KOZYV6d/FG8ndvUWmRuuV32PnCW5iTla4BgOO63TgMKleFTlA # zjfnn5ZDsmT7v5/tFiFskaDpCTFejUe6rUvRWqMFIYpfEyKUvC92fgbs399vuVu8 # +nUfFoY/mRPc # SIG # End signature block |