Public/PSHuntress.ps1


$HUNTRESS_DEFAULT_URI = 'https://api.huntress.io/v1/'
$HuntressFilName = 'Huntress_API_Settings.json'
$HuntressSettingsFile = "$env:APPDATA\$HuntressFilName"


# https://api.huntress.io/docs#introduction
# https://api.huntress.io/docs/preview#/
# Get-Date -Format "o" in Powershell to get the date format needed

Function Get-HuntressAccount {
    [CmdletBinding()]
    Param()    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        $RelativeURI = "account"

        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
        WI -Prefix HUNTRESS -Message "Relative Api Url: $($RelativeURI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + $RelativeURI

        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.Account
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}

Function Get-HuntressOrganizations {
    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$page,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateRange(1, 500)]
        [int]$limit,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_max
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        [system.collections.arraylist]$Output = @()
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($MyInvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"

    }
    PROCESS {
        $ParamList = $Null
        $Data = $Null
        $CommandName = $MyInvocation.InvocationName
        $Parameters = (Get-Command -Name $CommandName).Parameters
        $Parameters.Values.Getenumerator() | Where-Object { 'HTTPQuery' -in $_.Attributes.Parametersetname } | Foreach-Object { $ParamList += @{$_.Name = $_.Attributes.Parametersetname } }
        $PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -in $ParamList.Keys } | ForEach-Object { $Data += @{$_.Key = $_.Value } }
        $Params.URI = $HUNTRESS_BASE_URI + "organizations?"
        If ($Null -ne $Data) {
            $Params.URI = New-HttpQueryString -uri $Params.URI -QueryParameter $Data
        }
        
        Try {
            $Response = Invoke-RestMethod @Params
            $Output.AddRange($Response.Organizations)
            WV -Prefix HUNTRESS -Message "Retrieved $($Response.Organizations.Count) items"
            If ($Response.Pagination.Next_page) {
                do {
                    $Params.URI = $Response.Pagination.Next_page_url
                    $Response = Invoke-RestMethod @Params
                    $Output.AddRange($Response.organizations)
                    WV -Prefix HUNTRESS -Message "Retrieved $($Response.Organizations.Count) items"
                } While ($Response.Pagination.Next_page)
            }
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Output
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}

Function Get-HuntressOrganization {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + "organizations/$id"
        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.Organization
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}
Function Get-HuntressAgents {
    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$organization_id,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$page,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateRange(1, 500)]
        [int]$limit,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_max
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        [system.collections.arraylist]$Output = @()
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $ParamList = $Null
        $Data = $Null
        $CommandName = $MyInvocation.InvocationName
        $Parameters = (Get-Command -Name $CommandName).Parameters
        $Parameters.Values.Getenumerator() | Where-Object { 'HTTPQuery' -in $_.Attributes.Parametersetname } | Foreach-Object { $ParamList += @{$_.Name = $_.Attributes.Parametersetname } }
        $PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -in $ParamList.Keys } | ForEach-Object { $Data += @{$_.Key = $_.Value } }
        $Params.URI = "$HUNTRESS_BASE_URI/agents?"
        If ($Null -ne $Data) {
            $Params.URI = New-HttpQueryString -uri $Params.URI -QueryParameter $Data
        }
        
        Try {
            $Response = Invoke-RestMethod @Params
            $Output.AddRange($Response.Agents)
            WV -Prefix HUNTRESS -Message "Retrieved $($Response.Agents.Count) items"
            If ($Response.Pagination.Next_page) {
                do {
                    $Params.URI = $Response.Pagination.Next_page_url
                    $Response = Invoke-RestMethod @Params
                    $Output.AddRange($Response.Agents)
                    WV -Prefix HUNTRESS -Message "Retrieved $($Response.Agents.Count) items"
                } While ($Response.Pagination.Next_page)
            }
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Output
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}

Function Get-HuntressAgent {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + "agents/$id"
        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.agent
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}
Function Get-HuntressIncidentReports {
    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$organization_id,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$page,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateRange(1, 500)]
        [int]$limit,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateSet('footholds', 'monitored_files', 'ransomware_canaries', 'antivirus_detections', 'process_detections')]
        [string]$indicator_type,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateSet('dismissed', 'sent', 'closed')]
        [string]$status,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateSet('low', 'high', 'critical')]
        [string]$severity,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$agent_id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        [system.collections.arraylist]$Output = @()
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($MyInvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $ParamList = $Null
        $Data = $Null
        $CommandName = $MyInvocation.InvocationName
        $Parameters = (Get-Command -Name $CommandName).Parameters
        $Parameters.Values.Getenumerator() | Where-Object { 'HTTPQuery' -in $_.Attributes.Parametersetname } | Foreach-Object { $ParamList += @{$_.Name = $_.Attributes.Parametersetname } }
        $PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -in $ParamList.Keys } | ForEach-Object { $Data += @{$_.Key = $_.Value } }
        $Params.URI = $HUNTRESS_BASE_URI + "incident_reports?"
        If ($Null -ne $Data) {
            $Params.URI = New-HttpQueryString -uri $Params.URI -QueryParameter $Data
        }
        Try {
            $Response = Invoke-RestMethod @Params
            $Output.AddRange($Response.Incident_Reports)
            WV -Prefix HUNTRESS -Message "Retrieved $($Response.Incident_Reports.Count) items"
            If ($Response.Pagination.Next_page) {
                do {
                    $Params.URI = $Response.Pagination.Next_page_url
                    $Response = Invoke-RestMethod @Params
                    $Output.AddRange($Response.Incident_Reports)
                    WV -Prefix HUNTRESS -Message "Retrieved $($Response.Incident_Reports.Count) items"
                } While ($Response.Pagination.Next_page)
            }
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Output
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}


Function Get-HuntressIncidentReport {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + "incident_reports/$id"
        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.Incident_report
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}
Function Get-HuntressReports {
    [CmdletBinding()]
    Param( 
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$organization_id,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$page,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateRange(1, 500)]
        [int]$limit,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$period_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$period_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateSet('monthly_summary', 'quarterly_summary', 'yearly_summary')]
        [string]$type
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        [system.collections.arraylist]$Output = @()
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $ParamList = $Null
        $Data = $Null
        $CommandName = $MyInvocation.InvocationName
        $Parameters = (Get-Command -Name $CommandName).Parameters
        $Parameters.Values.Getenumerator() | Where-Object { 'HTTPQuery' -in $_.Attributes.Parametersetname } | Foreach-Object { $ParamList += @{$_.Name = $_.Attributes.Parametersetname } }
        $PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -in $ParamList.Keys } | ForEach-Object { $Data += @{$_.Key = $_.Value } }
        $Params.URI = $HUNTRESS_BASE_URI + "reports?"
        If ($Null -ne $Data) {
            $Params.URI = New-HttpQueryString -uri $Params.URI -QueryParameter $Data
        }

        Try { 
            $Response = Invoke-RestMethod @Params
            $Output.AddRange($Response.Reports)
            WV -Prefix HUNTRESS -Message "Retrieved $($Response.Reports.Count) items"
            If ($Response.Pagination.Next_page) {
                do {
                    $Params.URI = $Response.Pagination.Next_page_url
                    $Response = Invoke-RestMethod @Params
                    $Output.AddRange($Response.Reports)
                    WV -Prefix HUNTRESS -Message "Retrieved $($Response.Reports.Count) items"
                } While ($Response.Pagination.Next_page)
            }
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Output
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}

Function Get-HuntressReport {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + "reports/$id"
        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.report
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}
Function Get-HuntressBillingReports {
    [CmdletBinding()]
    Param( 
        [int]$id,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [int]$page,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateRange(1, 500)]
        [int]$limit,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$updated_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_min,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [string]$created_at_max,
        [Parameter(ParameterSetName = "HTTPQuery")]
        [ValidateSet('open', 'paid', 'failed', 'partial_refund', 'full_refund')]
        [string]$status
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        [system.collections.arraylist]$Output = @()
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $ParamList = $Null
        $Data = $Null
        $CommandName = $MyInvocation.InvocationName
        $Parameters = (Get-Command -Name $CommandName).Parameters
        $Parameters.Values.Getenumerator() | Where-Object { 'HTTPQuery' -in $_.Attributes.Parametersetname } | Foreach-Object { $ParamList += @{$_.Name = $_.Attributes.Parametersetname } }
        $PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -in $ParamList.Keys } | ForEach-Object { $Data += @{$_.Key = $_.Value } }
        $Params.URI = $HUNTRESS_BASE_URI + "billing_reports?"
        If ($Null -ne $Data) {
            $Params.URI = New-HttpQueryString -uri $Params.URI -QueryParameter $Data
        }

        Try {
            $Response = Invoke-RestMethod @Params
            $Output.AddRange($Response.Billing_reports)
            WV -Prefix HUNTRESS -Message "Retrieved $($Response.Billing_reports.Count) items"
            If ($Response.Pagination.Next_page) {
                do {
                    $Params.URI = $Response.Pagination.Next_page_url
                    $Response = Invoke-RestMethod @Params
                    $Output.AddRange($Response.Billing_reports)
                    WV -Prefix HUNTRESS -Message "Retrieved $($Response.Billing_reports.Count) items"
                } While ($Response.Pagination.Next_page)
            }
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Output
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}


Function Get-HuntressBillingReport {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [int]$id
    )    
    BEGIN {
        $Params = @{
            URI         = $HUNTRESS_BASE_URI
            Headers     = $HUNTRESS_HEADER
            Method      = "GET"
            ContentType = $HUNTRESS_TYPE
            ErrorAction = "STOP"
        }
        $Begin = Get-Date
        WI -Prefix HUNTRESS -Message "Starting $($Myinvocation.MyCommand)"
        WI -Prefix HUNTRESS -Message "Base Api Url: $($Params.URI)"
    }
    PROCESS {
        $Params.URI = $HUNTRESS_BASE_URI + "billing_reports/$id"
        Try {
            $Response = Invoke-RestMethod @Params
            $OK = $True
        }
        Catch {
            $OK = $False
            $RespStream = $_.Exception.Response.GetResponseStream()
            $Reader = New-Object System.IO.StreamReader($RespStream)
            $RespBodyJson = $Reader.ReadToEnd()
            $RespBody = $RespBodyJson | ConvertFrom-Json
            Write-warning $($RespBody.Error | Out-String)
        }
        If ($OK) {
            Write-Output $Response.billing_report
        }
    }
    END {
        $Runtime = New-TimeSpan -Start $Begin -End (Get-Date)
        WI -Prefix HUNTRESS -Message "Retrieved data in $Runtime"
        WI -Prefix HUNTRESS -Message "Ending $($Myinvocation.Mycommand)"
    } 
}


Function Add-HuntressAPIKey {
    [cmdletbinding()]
    Param (
        [Parameter(ValueFromPipeline)]
        [string]$HUNTRESS_API_KEY
    )
    If ($PSBoundParameters.ContainsKey('HUNTRESS_API_KEY')) {
        $HUNTRESS_API_KEY_INTERNAL = $HUNTRESS_API_KEY
        Set-Variable -Name "HUNTRESS_API_KEY" -Value $HUNTRESS_API_KEY_INTERNAL -Option ReadOnly -Scope Script -Force
    }
    else {
        $HUNTRESS_API_KEY_INTERNAL = Read-Host "Enter Huntress API key"
        Set-Variable -Name "HUNTRESS_API_KEY" -Value $HUNTRESS_API_KEY_INTERNAL -Option ReadOnly -Scope Script -Force
    }
}

Function Add-HuntressAPISecret {
    Param (
        [Parameter(ValueFromPipeline)]
        [string]$HUNTRESS_API_SECRET
    )
    If ($PSBoundParameters.ContainsKey('HUNTRESS_API_SECRET')) {
        $HUNTRESS_API_SECRET_INTERNAL = ConvertTo-SecureString $HUNTRESS_API_SECRET -AsPlainText -Force
        Set-Variable -Name "HUNTRESS_API_SECRET" -Value $HUNTRESS_API_SECRET_INTERNAL -Option ReadOnly -Scope Script -Force
    }
    else {
        $HUNTRESS_API_SECRET_INTERNAL = Read-Host "Enter Huntress API Secret" -AsSecureString
        Set-Variable -Name "HUNTRESS_API_SECRET" -Value $HUNTRESS_API_SECRET_INTERNAL -Option ReadOnly -Scope Script -Force
    }
}

Function Add-HuntressBaseURI {
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline)]
        [string]$HUNTRESS_BASE_URI
    )
    If ($PSBoundParameters.ContainsKey('HUNTRESS_BASE_URI')) {
        $HUNTRESS_BASE_URI_INTERNAL = $HUNTRESS_BASE_URI
        If ($HUNTRESS_BASE_URI_INTERNAL.endswith('/')) {} else { $HUNTRESS_BASE_URI_INTERNAL = $HUNTRESS_BASE_URI_INTERNAL + '/' }
        Set-Variable -Name "HUNTRESS_BASE_URI" -Value $HUNTRESS_BASE_URI_INTERNAL -Option ReadOnly -Scope Script -Force
    }
    else {
        $HUNTRESS_BASE_URI_INTERNAL = Read-Host "Enter Huntress API base URI or press D for default URI (https://api.huntress.io/v1/)"
        If ($HUNTRESS_BASE_URI_INTERNAL -eq 'D') {
            Set-Variable -Name "HUNTRESS_BASE_URI" -Value $HUNTRESS_DEFAULT_URI -Option ReadOnly -Scope Script -Force
        }
        else {
            If ($HUNTRESS_BASE_URI_INTERNAL.endswith('/')) {} else { $HUNTRESS_BASE_URI_INTERNAL = $HUNTRESS_BASE_URI_INTERNAL + '/' }
            Set-Variable -Name "HUNTRESS_BASE_URI" -Value $HUNTRESS_BASE_URI_INTERNAL -Option ReadOnly -Scope Script -Force
        }
            
    }
}

Function Import-HuntressAPISettings {
    [CmdletBinding()]
    Param()
    $PlainTextMethod = { [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(($this))) }
    Try {
        WV -Prefix HUNTRESS -Message "Importing Huntress API settings from $($HuntressSettingsFile)"
        $HuntressSettings = Get-Content -Path $HuntressSettingsFile -ErrorAction STOP
        $OK = $True
    }
    Catch {
        Write-Warning "Unable to get Huntress API settings file at $($HuntressSettingsFile)"
        Write-Warning "Run Add-HuntressAPIKey, Add-HuntressAPISecret, Add-HuntressBaseURI, Export-HuntressAPISettings, and Import-HuntressAPISettings"
        $OK = $False
    }
    If ($OK) {
        WV -Prefix HUNTRESS -Message "Setting Huntress API variables"
        $HuntressSettingsObject = $HuntressSettings | ConvertFrom-Json
        Set-Variable -Name "HUNTRESS_BASE_URI" -Value $HuntressSettingsObject.HUNTRESS_BASE_URI -Option ReadOnly -Scope Script -Force
        Set-Variable -Name "HUNTRESS_API_KEY" -Value $HuntressSettingsObject.HUNTRESS_API_KEY -Option ReadOnly -Scope Script -Force
        Set-Variable -Name "HUNTRESS_API_SECRET" -Value $($HuntressSettingsObject.HUNTRESS_API_SECRET | ConvertTo-SecureString) -Option ReadOnly -Scope Script -Force
        $HUNTRESS_API_SECRET | Add-Member -MemberType ScriptMethod -Name ToText -Value $PlainTextMethod -Force
        
        If ($HUNTRESS_API_KEY -and $HUNTRESS_API_SECRET -and $HUNTRESS_BASE_URI) {
            $huntressCreds = "$($HUNTRESS_API_KEY):$($HUNTRESS_API_SECRET.ToText())"
            $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($huntressCreds))
            $Script:HUNTRESS_TYPE = "application/json"
            WV -Prefix HUNTRESS -Message "Creating Huntress API header"
            $Script:HUNTRESS_HEADER = @{
                Authorization = "Basic $encodedCredentials"
            }
        }
    }
}
Function Export-HuntressAPISettings {
    [CmdletBinding()]
    Param()
    If ($HUNTRESS_API_KEY -and $HUNTRESS_API_SECRET -and $HUNTRESS_BASE_URI) {
        $HuntressSecretSecureString = $HUNTRESS_API_SECRET | ConvertFrom-SecureString
        $HuntressSettingsObject = [PSCustomObject]@{
            HUNTRESS_API_KEY    = $HUNTRESS_API_KEY
            HUNTRESS_API_SECRET = $HuntressSecretSecureString
            HUNTRESS_BASE_URI   = $HUNTRESS_BASE_URI
        }
        WV -Prefix HUNTRESS -Message "Exporting Huntress API settings to $($HuntressSettingsFile)h"
        $HuntressSettingsObject | ConvertTo-Json | Out-File $HuntressSettingsFile
    }
    else {
        Write-Warning "Missing some settings values"
        Write-Warning "Run Add-HuntressAPIKey, Add-HuntressAPISecret, Add-HuntressBaseURI, and Export-HuntressAPISettings"
    }
}

Function WV {
    Param($prefix, $message, $File)
    $time = Get-Date -f MM-dd-HH:mm:ss:ffff
    Write-Verbose "$time [$($prefix.padright(10,' '))] $message"
    If ($File) {
        [pscustomobject]@{
            Time    = $time
            Prefix  = $Prefix
            Message = $Message
        } | Export-Csv -Path $File -Append -NoTypeInformation
    }
}
Function WD {
    Param($prefix, $message, $File)
    $time = Get-Date -f MM-dd-HH:mm:ss:ffff
    Write-Debug "$time [$($prefix.padright(10,' '))] $message"
    If ($File) {
        [pscustomobject]@{
            Time    = $time
            Prefix  = $Prefix
            Message = $Message
        } | Export-Csv -Path $File -Append -NoTypeInformation
    }
}

Function WI {
    Param($prefix, $message)
    $time = Get-Date -f MM-dd-HH:mm:ss:ffff
    Write-Information "$time [$($prefix.padright(10,' '))] $message"
}


Function New-HttpQueryString {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [String]$Uri,
        [Parameter(Mandatory)]
        [Hashtable]$QueryParameter
    )
    Add-Type -AssemblyName System.Web
    
    # Create a http name value collection from an empty string
    $nvCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
    
    foreach ($key in $QueryParameter.Keys) {
        $nvCollection.Add($key, $QueryParameter.$key)
    }
    
    # Build the uri
    $uriRequest = [System.UriBuilder]$uri
    $uriRequest.Query = $nvCollection.ToString()
    return $uriRequest.Uri.OriginalString
}

Import-HuntressAPISettings