MISP.Tools.psm1

## Running A Build Will Compile This To A Single PSM1 File Containing All Module Code ##

## If Importing Module Source Directly, This Will Dynamically Build Root Module ##

# Get list of private functions and public functions to import, in order.
$PrivateFunctions = @(Get-ChildItem -Path $PSScriptRoot\private -Recurse -Filter "*.ps1") | Sort-Object Name
$PublicFunctions = @(Get-ChildItem -Path $PSScriptRoot\public -Recurse -Filter "*.ps1") | Sort-Object Name

$ModuleName = 'MISP.Tools'

$DefaultMispPreferencePath = Join-Path -Path ([System.Environment]::GetFolderPath('LocalApplicationData')) -ChildPath $ModuleName
$DefaultMispPreferenceFilename = 'MISPContext.xml'

Write-Verbose "Default Preference Path: $($DefaultMispPreferencePath); Default Preference Filename: $($DefaultMispPreferenceFilename)"

# Dot source the private function files.
foreach ($ImportItem in $PrivateFunctions) {
  try {
    . $ImportItem.FullName
    Write-Verbose -Message ("Imported private function {0}" -f $ImportItem.FullName)
  }
  catch {
    Write-Error -Message ("Failed to import private function {0}: {1}" -f $ImportItem.FullName, $_)
  }
}

# Dot source the public function files.
foreach ($ImportItem in $PublicFunctions) {
  try {
    . $ImportItem.FullName
    Write-Verbose -Message ("Imported public function {0}" -f $ImportItem.FullName)
  }
  catch {
    Write-Error -Message ("Failed to import public function {0}: {1}" -f $ImportItem.FullName, $_)
  }
}

# Export the public functions.
Export-ModuleMember -Function $PublicFunctions.BaseName
Export-ModuleMember -Variable DefaultMispPreferenceFilename, DefaultMispPreferencePath
# Trust all certificates
Function Enable-TrustAllCertsPolicy {
    <#
    .SYNOPSIS
        Trust all SSL certificates even if self-signed, and set protocol to Tls 1.2.
    #>

    [CmdletBinding()]
    Param(
      [Parameter(Mandatory=$False)]
      [switch]$NoValidateSsl
    )

    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($me): Setting TLS Preferences with Certificate Validation: $(!$NoValidateSsl)"
    # Establish Certification Policy Exception
    $PSDesktopException = @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
      }
    }
"@


    # Set PowerShell to TLS1.2
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

    if ($NoValidateSsl) {
        if ($PSEdition -ne 'Core'){
            if (-Not ("TrustAllCertsPolicy" -as [type])) {
                Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy is not enabled. Enabling."
                Add-Type $PSDesktopException
                try {
                    [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
                } catch {
                    throw [Exception] `
                        "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update System.Net.ServicePointManager::CertificatePolicy to new TrustAllCertsPolicy"
                }
            }
        } else {
            # Set session default for Invoke-RestMethod and Invoke-WebRequest to SkipCertificateCheck
            if (-Not $PSDefaultParameterValues.ContainsKey('Invoke-RestMethod:SkipCertificateCheck')) {
                Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: PowerShell Core - Invoke-RestMethod SkipCertificateCheck set to true"
                Try {
                    $PSDefaultParameterValues.Add("Invoke-RestMethod:SkipCertificateCheck",$true)
                } Catch {
                    throw [Exception] `
                    "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update PSDefaultParameterValues Invoke-RestMethod:SkipCertificateCheck to value true"
                }

            }
        }
    } else {
        Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy set as Not Required."
    }
}

# Private Function Example - Replace With Your Function
function Invoke-MispRestMethod {

    <#
    .SYNOPSIS
        Invoke a call to the MISP REST API
    .DESCRIPTION
        Invoke a call to the MISP REST API
 
        This function is intended to abstract the call to Invoke-RestMethod and supply the required details for a MISP call
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Method
        Base API URL for the API Calls to MISP
    .PARAMETER NoValidateSsl
        Don't validate the SSL Certificate
    .INPUTS
        [PSCredential] -> Credential
        [System.Uri.Builder] -> BaseUri
        [Switch] -> NoValidateSsl
        [string] -> Method (Post, Get, Put, Delete)
    .OUTPUTS
        [PSCustomObject] -> MISP Context
    .EXAMPLE
        PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com'
    .LINK
        https://github.com/IPSecMSSP/misp.tools
    #>


    [CmdletBinding()]

  param(
    [Parameter(Mandatory=$true,
      HelpMessage = 'MISP Context to use. A MISP Context includes both the BaseUri as well as the API Key')]
        [PSCustomObject] $Context,

    [Parameter(Mandatory=$true,
      HelpMessage = 'Full URI to requested REST Resource as String or UriBuilder object')]
    [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=$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
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name
    Write-Verbose "$($Me): Invoking MISP REST API with URI $($Uri)"

    $Headers = @{
      "Content-Type" = "application/json"
      "Accept" = "application/json"
      "Authorization" = $Context.Credential.GetNetworkCredential().Password
    }

  }

  Process {

    # Set SSL Preferences/Certificate Trust Policy
    Enable-TrustAllCertsPolicy -NoValidateSsl:$Context.NoValidateSsl

    # Build list of parameters to pass to Invoke-RestMethod
    $Request = @{
      Method = $Method
      Uri = $Uri.ToString()
      Headers = $Headers
    }

    # Add body if supplied
    if ($MyInvocation.BoundParameters.ContainsKey("Body")) {
      $Request.Add('Body', ($Body | ConvertTo-Json -Depth 10))
      # $Request.Add('Body', $Body)
    }

    # There's some altered handling of "Content-Type" Header in some PowerShell 7.2 releases where ContentType Parameter has to be passed instead or in addition
    if (($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -ge 2) -or ($PSVersionTable.PSVersion.Major -ge 8)) {
      $Request.Add("ContentType", "application/json")
    }

    $Response = Invoke-RestMethod @Request

    Write-Output $Response

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Get-MispAttribute {
  <#
    .SYNOPSIS
        Get MISP Attribute(s)
    .DESCRIPTION
        Get MISP Attribute(s)
 
        If no Attribute ID is supplied, all attributes are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific event
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Attributes
    .EXAMPLE
        PS> $Attributes = Get-MispAttribute -Context $MispContext
        Return all Attributes
    .EXAMPLE
        PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234
        Return details for attribute 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#attribute-management
    #>


  [CmdletBinding()]

  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $false)]
    [Int]$Id
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose ('{0}: Get MISP Attribute(s)' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "attributes")

    # Append the Event Id if requested
    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)")
    }
  }

  Process {

    # Call the API
    $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri

    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      # Only a single event was requested
      Write-Output $Response.Attribute
    }
    else {
      # Return all fo the events
      Write-Output $Response
    }

  }

  End {

  }

}

# Set MISP API Key/URI Context
function New-MispAttribute {
  <#
    .SYNOPSIS
        Construct a new MISP Attribute(s)
    .DESCRIPTION
        Construct a new MISP Attribute(s)
 
        The Attribute will be able to be added to an new Event, or added to an existing Event
    .PARAMETER Category
        The category that the Attribute belogs to.
 
        One of:
        - Internal reference
        - Targeting data
        - Antivirus detection
        - Payload delivery
        - Artifacts dropped
        - Payload installation
        - Persistence mechanism
        - Network activity
        - Payload type
        - Attribution
        - External analysis
        - Financial fraud
        - Support Tool
        - Social network
        - Person
        - Other
    .PARAMETER Type
        The type of Attribute.
 
        There are many types to choose from. Common types include:
        - md5
        - sha1
        - sha256
        - email
        - ip-src
        - ip-dst
        - hostname
        - domain
        - url
        - user-agent
        - port
        - comment
        - other
    .INPUTS
        [PsCustomObject] -> Context
        [string] -> Category
        [string] -> Type
    .OUTPUTS
        [Array] -> Array of Attributes
    .EXAMPLE
        PS> $Attributes = Get-MispAttribute -Context $MispContext
        Return all Attributes
    .EXAMPLE
        PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234
        Return details for attribute 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#attribute-management
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory = $false)]
    [ValidateSet("Internal reference", "Targeting data", "Antivirus detection", "Payload delivery", "Artifacts dropped", "Payload installation", "Persistence mechanism", "Network activity", `
        "Payload type", "Attribution", "External analysis", "Financial fraud", "Support Tool", "Social network", "Person", "Other")]
    [string]$Category = "Other",

    [Parameter(Mandatory = $true)]
    [ValidateSet("md5", "sha1", "sha256", "filename", "pdb", "filename|md5", "filename|sha1", "filename|sha256", "ip-src", "ip-dst", "hostname", "domain", "domain|ip", "email", "email-src", `
        "eppn", "email-dst", "email-subject", "email-attachment", "email-body", "float", "git-commit-id", "url", "http-method", "user-agent", "ja3-fingerprint-md5", "jarm-fingerprint", `
        "favicon-mmh3", "hassh-md5", "hasshserver-md5", "regkey", "regkey|value", "AS", "snort", "bro", "zeek", "community-id", "pattern-in-file", "pattern-in-traffic", "pattern-in-memory", `
        "pattern-filename", "pgp-public-key", "pgp-private-key", "yara", "stix2-pattern", "sigma", "gene", "kusto-query", "mime-type", "identity-card-number", "cookie", "vulnerability", "cpe",
        "weakness", "attachment", "malware-sample", "link", "comment", "text", "hex", "other", "named pipe", "mutex", "process-state", "target-user", "target-email", "target-machine", "target-org",
        "target-location", "target-external", "btc", "dash", "xmr", "iban", "bic", "bank-account-nr", "aba-rtn", "bin", "cc-number", "prtn", "phone-number", "threat-actor", "campaign-name", "campaign-id", `
        "malware-type", "uri", "authentihash", "vhash", "ssdeep", "imphash", "telfhash", "pehash", "impfuzzy", "sha224", "sha384", "sha512", "sha512/224", "sha512/256", "sha3-224", "sha3-256", "sha3-384", `
        "sha3-512", "tlsh", "cdhash", "filename|authentihash", "filename|vhash", "filename|ssdeep", "filename|imphash", "filename|impfuzzy", "filename|pehash", "filename|sha224", "filename|sha384", `
        "filename|sha512", "filename|sha512/224", "filename|sha512/256", "filename|sha3-224", "filename|sha3-256", "filename|sha3-384", "filename|sha3-512", "filename|tlsh", "windows-scheduled-task", `
        "windows-service-name", "windows-service-displayname", "whois-registrant-email", "whois-registrant-phone", "whois-registrant-name", "whois-registrant-org", "whois-registrar", "whois-creation-date", `
        "x509-fingerprint-sha1", "x509-fingerprint-md5", "x509-fingerprint-sha256", "dns-soa-email", "size-in-bytes", "counter", "datetime", "port", "ip-dst|port", "ip-src|port", "hostname|port", "mac-address", `
        "mac-eui-64", "email-dst-display-name", "email-src-display-name", "email-header", "email-reply-to", "email-x-mailer", "email-mime-boundary", "email-thread-index", "email-message-id", "github-username", `
        "github-repository", "github-organisation", "jabber-id", "twitter-id", "dkim", "dkim-signature", "first-name", "middle-name", "last-name", "full-name", "date-of-birth", "place-of-birth", "gender", `
        "passport-number", "passport-country", "passport-expiration", "redress-number", "nationality", "visa-number", "issue-date-of-the-visa", "primary-residence", "country-of-residence", "special-service-request", `
        "frequent-flyer-number", "travel-details", "payment-details", "place-port-of-original-embarkation", "place-port-of-clearance", "place-port-of-onward-foreign-destination", "passenger-name-record-locator-number", `
        "mobile-application-id", "chrome-extension-id", "cortex", "boolean", "anonymised")]
    [string] $Type,

    [Parameter(Mandatory = $false)]
    [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")]
    [string]$Distribution = "Organisation",

    [Parameter(Mandatory = $true)]
    [string] $Comment,

    [Parameter(Mandatory = $true)]
    [string] $Value,

    [Parameter(Mandatory = $false)]
    [boolean] $toIds = $false
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose ('{0}: Build New MISP Attribute(s)' -f $Me)

    $DistributionMap = @{
      Organisation = 0      # Your organisation only
      Community    = 1      # This community only
      Connected    = 2      # Connected Communities
      All          = 3      # All communities
      Group        = 4      # Sharing Group
      Inherit      = 5      # Inherit Event
    }
  }

  Process {

    if ($PSCmdlet.ShouldProcess("Construct New MISP Attribute")) {
      # Build the Attribute Body
      $Attribute = @{
        type         = $Type
        category     = $Category
        distribution = $DistributionMap.($Distribution)
        to_ids       = $toIds
        comment      = $Comment
        value        = $Value
      }

      # Return all for the events
      Write-Output $Attribute
    }
  }

  End {

  }

}

# Set MISP API Key/URI Context
function Search-MispAttribute {
  <#
    .SYNOPSIS
        Search for MISP Event(s)
    .DESCRIPTION
        Search for MISP Event(s)
 
        If no Event ID is supplied, the first 100 events are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Search
        JSON containing search criteria
 
        As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this
        $Search = @{
          returnFormat = "json"
          type = @{
            OR = @("ip-src", "ip-dst")
          }
          tags = @{
            NOT = @("tlp:red")
            OR = @("tlp:%")
          }
        }
        Refer to MISP Automation -> Search for further details on available search criteria
    .INPUTS
        [PsCustomObject] -> Context
        [hastable] -> Search
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Search = @{
          returnFormat = "json"
          value = 8.8.8.8
        }
        PS> $Attributes = Search-MispAttribute -Context $MispContext -Search $Search
        Return matching attributes
    .LINK
        https://www.circl.lu/doc/misp/automation/#search
        https://github.com/IPSecMSSP/misp.tools
    #>


  [CmdletBinding(
    SupportsShouldProcess,
    ConfirmImpact = "Low"
  )]

  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true)]
    [hashtable]$Search
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Search for MISP Attributes(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "attributes/restSearch")

  }

  Process {

    if ($PSCmdlet.ShouldProcess("Search MISP Attributes")) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body $Search -Method 'POST'

      # Return all fo the events
      Write-Output $Response
    }

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Get-MispEvent {
    <#
    .SYNOPSIS
        Get MISP Event(s)
    .DESCRIPTION
        Get MISP Event(s)
 
        If no Event ID is supplied, all events are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific event
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = Get-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = Get-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#events-management
    #>


  [CmdletBinding()]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory=$false)]
    [Int]$Id
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Get MISP Event(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "events")

    # Append the Event Id if requested
    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      $Uri.Path = [io.path]::combine($Uri.Path, $Id)
    }
  }

  Process {

    # Call the API
    $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri

    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      # Only a single event was requested
      Write-Output $Response.Event
    } else {
      # Return all fo the events
      Write-Output $Response
    }

  }

  End {

  }

}

# Set MISP API Key/URI Context
function New-MispEvent {
  <#
    .SYNOPSIS
        Create a new MISP Event
    .DESCRIPTION
        Create a new MISP Event
 
        Take the provided details and use these to create a new Event in MISP
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Info
        Name/Description of the Event
    .PARAMETER ThreatLevel
        Threat Level of the event. One of [High, Medium, Low, Undefined]
    .PARAMETER Analysis
        Analysis State for the Event. One of [Initial, Ongoing, Complete]. Defaults to 'Initial'
    .PARAMETER Distribution
        Distribution Level for the Event. One of [Organisation, Community, Connected, All, Group, Inherit]. Defaults to 'Organisation'
    .PARAMETER Published
        Boolean value as to whether the event should be published. Defaults to false.
    .PARAMETER Attribute
        Array of Attributes to attach ot the event. Each attribute consist of a Value and Type at minimum, and may include IPs, hostnames, file hashes, etc.
    .PARAMETER Organisation
        Name or Id of organisation to associate the event to. This will be uses as the "Creator Organisation".
 
        If not specified, will use the Organisation of the User creating the Event.
 
        The Owner Organisation is always the Organisation of the user creating the event.
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = New-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = New-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#events-management
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true)]
    [string]$Info,

    [Parameter(Mandatory = $true)]
    [ValidateSet("High", "Medium", "Low", "Undefined")]
    [string]$ThreatLevel,

    [Parameter(Mandatory = $false)]
    [ValidateSet("Initial", "Ongoing", "Complete")]
    [string]$Analysis = "Initial",

    [Parameter(Mandatory = $false)]
    [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")]
    [string]$Distribution = "Organisation",

    [Parameter(Mandatory = $false)]
    [System.Boolean]$Published = $false,

    [Parameter(Mandatory = $false)]
    [array] $Attribute,

    [Parameter(Mandatory = $false)]
    [string]$Organisation
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Create new MISP Event(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "events")

    $ThreatLevelMap = @{
      High      = 1
      Medium    = 2
      Low       = 3
      Undefined = 4
    }

    $AnalysisMap = @{
      Initial  = 0
      Ongoing  = 1
      Complete = 2
    }

    $DistributionMap = @{
      Organisation = 0      # Your organisation only
      Community    = 1         # This community only
      Connected    = 2         # Connected Communities
      All          = 3               # All communities
      Group        = 4             # Sharing Group
      Inherit      = 5           # Inherit Event
    }
  }

  Process {

    # Build up the Event Body
    $EventBody = [pscustomobject]@{
      date            = (Get-Date).ToString("yyyy-MM-dd")
      threat_level_id = $ThreatLevelMap.($ThreatLevel)
      info            = $Info
      published       = $Published
      analysis        = $AnalysisMap.($Analysis)
      distribution    = $DistributionMap.($Distribution)
    }

    if ($PSBoundParameters.ContainsKey('Organisation')) {
      # Check if the organisation is an Integer or something else
      $OrganisationId = 0
      if ([int]::TryParse($Organisation, [ref]$OrganisationId)) {
        # Integer ID, check for existence
        $Org = Get-MispOrganisation -Context $Context -Id $OrganisationId
        if (-not $Org) {
          throw ('Organisation does not exist in MISP: {0}' -f $Organisation)
        }
      }
      elseif ($Organisation -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
        # Id is a UUID
        $Org = Get-MispOrganisation -Context $Context -Id $Organisation
        if (-not $Org) {
          throw ('Organisation does not exist in MISP: {0}' -f $Organisation)
        }
      }
      else {
        # Not an integer, try a name match
        $Org = Get-MispOrganisation -Context $Context -Name $Organisation
        if (-not $Org) {
          Write-Verbose ('Organisation Name not found in MISP: {0}' -f $Organisation)
          $Org = New-MispOrganisation -Context $Context -Name $Organisation
        }
      }
      # When setting the Creator Org for an event, the back-end checks the value of orgc.uuid, so pass the whole Org object
      $EventBody | Add-Member -MemberType NoteProperty -Name 'orgc' -Value $Org
    }

    # If attributes were supplied, add these too
    if ($MyInvocation.BoundParameters.ContainsKey("Attribute")) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'attribute' -Value $Attribute
    }

    Write-Debug "Event Body:`n$($EventBody | ConvertTo-Json -Depth 10)"

    If ($PSCmdlet.ShouldProcess("Create new MISP Event")) {
      # Call the API
      #Write-Output $EventBody | ConvertTo-Json -depth 10
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" -Body $EventBody
    }

    Write-Output $Response.Event

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Publish-MispEvent {
    <#
    .SYNOPSIS
        Publish an existing MISP Event
    .DESCRIPTION
        Publish an existing MISP Event
 
        Mark the Event identified by the id (numeric or UUID) as published
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific event
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = New-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = New-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#events-management
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory=$true)]
    [string]$Id
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose ('{0}: Publish MISP Event(s)' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, 'events/publish')
  }

  Process {
    $Uri.Path = [io.path]::combine($Uri.Path, $Id)

    If ($PSCmdlet.ShouldProcess("Publish MISP Event")) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST"
    }

    Write-Output $Response

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Search-MispEvent {
    <#
    .SYNOPSIS
        Search for MISP Event(s)
    .DESCRIPTION
        Search for MISP Event(s)
 
        If no Event ID is supplied, the first 100 events are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Search
        JSON containing search criteria
 
        As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this
        $Search = @{
          returnFormat = "json"
          type = @{
            OR = @("ip-src", "ip-dst")
          }
          tags = @{
            NOT = @("tlp:red")
            OR = @("tlp:%")
          }
        }
        Refer to MISP Automation -> Search for further details on available search criteria
    .INPUTS
        [PsCustomObject] -> Context
        [hastable] -> Search
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = Search-MispEvent -Context $MispContext -Search $Search
        Return matching events
    .LINK
        https://www.circl.lu/doc/misp/automation/#search
        https://github.com/IPSecMSSP/misp.tools
    #>


  [CmdletBinding(
    SupportsShouldProcess,
    ConfirmImpact="Low"
  )]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory=$true)]
    [hashtable]$Search
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Search for MISP Event(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "events/restSearch")

  }

  Process {

    if ($PSCmdlet.ShouldProcess("Search MISP Events")) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body $Search -Method POST

      # Return all fo the events
      Write-Output $Response
    }

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Unpublish-MispEvent {
    <#
    .SYNOPSIS
        Unpublish an existing MISP Event
    .DESCRIPTION
        Unpublish an existing MISP Event
 
        Unmark the Event identified by the id (numeric or UUID) as published
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific event
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = New-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = New-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#events-management
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory=$true)]
    [string]$Id
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose ('{0}: Unpublish MISP Event(s)' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, 'events/unpublish')
  }

  Process {
    $Uri.Path = [io.path]::combine($Uri.Path, $Id)

    If ($PSCmdlet.ShouldProcess("Unpublish MISP Event")) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST"
    }

    Write-Output $Response

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Update-MispEvent {
  <#
    .SYNOPSIS
        Create a new MISP Event
    .DESCRIPTION
        Create a new MISP Event
 
        Take the provided details and use these to create a new Event in MISP
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of MISP event to update
    .PARAMETER Info
        Name/Description of the Event
    .PARAMETER ThreatLevel
        Threat Level of the event. One of [High, Medium, Low, Undefined]
    .PARAMETER Analysis
        Analysis State for the Event. One of [Initial, Ongoing, Complete]. Defaults to 'Initial'
    .PARAMETER Distribution
        Distribution Level for the Event. One of [Organisation, Community, Connected, All, Group, Inherit]. Defaults to 'Organisation'
    .PARAMETER Published
        Boolean value as to whether the event should be published. Defaults to false.
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = New-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = New-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#events-management
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true)]
    [int]$Id,

    [Parameter(Mandatory = $false)]
    [string]$Info,

    [Parameter(Mandatory = $false)]
    [ValidateSet("High", "Medium", "Low", "Undefined")]
    [string]$ThreatLevel,

    [Parameter(Mandatory = $false)]
    [ValidateSet("Initial", "Ongoing", "Complete")]
    [string]$Analysis = "Initial",

    [Parameter(Mandatory = $false)]
    [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")]
    [string]$Distribution = "Organisation",

    [Parameter(Mandatory = $false)]
    [System.Boolean]$Published = $false
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): UPdate MISP Event"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "events/edit")

    $ThreatLevelMap = @{
      High      = 1
      Medium    = 2
      Low       = 3
      Undefined = 4
    }

    $AnalysisMap = @{
      Initial  = 0
      Ongoing  = 1
      Complete = 2
    }

    $DistributionMap = @{
      Organisation = 0      # Your organisation only
      Community    = 1      # This community only
      Connected    = 2      # Connected Communities
      All          = 3      # All communities
      Group        = 4      # Sharing Group
      Inherit      = 5      # Inherit Event
    }
  }

  Process {
    # Create a new UriBuilder object for each event
    $EventUri = [System.UriBuilder]$Uri.Uri.ToString()
    $EventUri.Path = [io.path]::combine($EventUri.Path, $Id)


    # Build up the Event Body
    $EventBody = [pscustomobject]@{}

    if ($PSBoundParameters.ContainsKey('Info')) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'info' -Value $Info
    }

    if ($PSBoundParameters.ContainsKey('ThreatLevel')) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'threat_level_id' -Value $ThreatLevelMap.($ThreatLevel)
    }

    if ($PSBoundParameters.ContainsKey('Analysis')) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'analysis' -Value $AnalysisMap.($Analysis)
    }

    if ($PSBoundParameters.ContainsKey('Distribution')) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'distribution' -Value $DistributionMap.($Distribution)
    }

    if ($PSBoundParameters.ContainsKey('Published')) {
      $EventBody | Add-Member -MemberType NoteProperty -Name 'published' -Value $Published
    }

    Write-Debug "Event Body:`n$($EventBody | ConvertTo-Json -Depth 10)"

    If ($PSCmdlet.ShouldProcess("Update MISP Event")) {
      # Call the API
      #Write-Output $EventBody | ConvertTo-Json -depth 10
      $Response = Invoke-MispRestMethod -Context $Context -Uri $EventUri -Method "PUT" -Body $EventBody
    }

    Write-Output $Response.Event

  }

  End {

  }

}

# Set MISP API Key/URI Context
function New-MispContext {
    <#
    .SYNOPSIS
        Create a PSObject containing Credentials and Base URI for interaction with MISP
    .DESCRIPTION
        Create a PSObject containing Credentials and Base URI for interaction with MISP
 
        Credentials supplied in the form of a PSCredential Object and Base URI as either a string or Uri::Builder object
    .PARAMETER Credential
        PSCredential Object with ApiKey as Password
    .PARAMETER BaseUri
        Base API URL for the API Calls to MISP
    .PARAMETER NoValidateSsl
        Don't validate the SSL Certificate
    .INPUTS
        [PSCredential] -> Credential
        [System.Uri.Builder] -> BaseUri
        [Switch] -> NoValidateSsl
    .OUTPUTS
        [PSCustomObject] -> MISP Context
    .EXAMPLE
        PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com'
    .LINK
        https://github.com/IPSecMSSP/misp.tools
    #>


  [CmdletBinding(SupportsShouldProcess)]

  param (
    [Parameter(Mandatory=$true)]
    [pscredential]$Credential,

    [Parameter(Mandatory=$true)]
    [ValidateScript({
      $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique
      if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') {
        [System.UriBuilder]$_
      }
    })]
    [System.UriBuilder]$BaseUri,

    [Parameter(Mandatory=$false)]
    [switch]$NoValidateSsl
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Setting credentials for MISP with Base URI: $($BaseUri). Cerificiate Validation: $(!$NoValidateSsl)"

    $MispContext = New-Object pscustomobject -Property @{
      Credential = $Credential
      BaseUri = $BaseUri
      NoValidateSsl = [boolean]$NoValidateSsl
    }

  }

  Process {
    # Set SSL Preferences/Certificate Trust Policy
    Enable-TrustAllCertsPolicy
    if ($PSCmdlet.ShouldProcess($MispContext.BaseUri), "Create new MISP Context") {
      Write-Output $MispContext
    }

  }

  End {

  }

}

# Read MISP API Key/URI from configuration file
function Read-MispContext {
    <#
    .SYNOPSIS
        Read the MISP Context from a saved preferences file
    .DESCRIPTION
        Read the MISP Context from a saved preferences file
 
        Check to see that the contents of the file matches our expectations after loading
    .PARAMETER Path
        Path containing the preferences file, defaults to users AppDataLocal Path
    .PARAMETER FileName
        Name of file to use for MISP Context, defaults to MISPContext.xml
    .INPUTS
        [String] -> Path
        [String] -> Filename
    .OUTPUTS
        [PSCustomObject] -> MISP Context into $MispContext
    .EXAMPLE
        PS > $MispContext = Read-MispContext
    .EXAMPLE
        PS > $MispContext = Read-MispContext -Filename 'MyMISP.xml'
    .LINK
        https://github.com/IPSecMSSP/misp.tools
    #>


  [CmdletBinding()]

  Param (
    [Parameter(
      Mandatory = $false,
      ValueFromPipeline = $false,
      Position = 0
    )]
    [string] $Path = $DefaultMispPreferencePath,
    [Parameter(
      Mandatory = $false,
      ValueFromPipeline = $false,
      Position = 1
    )]
    [string] $Filename = $DefaultMispPreferenceFilename
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    $ContextFilename = Join-Path -Path $Path -ChildPath $Filename
    Write-Verbose "$($Me): Loading MISP Context from $($ContextFilename)"

  }

  Process {
    # Check directory exists
    if (!(Test-Path -Path $Path -PathType Container)) {
      throw "Path not found: $($Path)"
    }

    # Check file exists
    if (!(Test-Path -Path $ContextFilename -PathType Leaf)) {
      throw "Context File not found: $($ContextFilename)"
    }

    # Load file contents
    try {
      $LoadContext = Import-Clixml -Path $ContextFilename
    } catch {
      throw "Error loading context from file $($ContextFilename)"
    }

    $ContextProperties = $LoadContext | Get-Member -MemberType NoteProperty

    # Check if a Credential Property Exists
    If (!($ContextProperties.Name -contains 'Credential')){
      throw "Context File did not contain a Credential Property"
    }

    # Ensure Credential Property is of the correct type
    If (!($LoadContext.Credential.GetType().Name -eq 'PSCredential')) {
      throw "Context File Credential Property is not of type [PSCredential]"
    }

    # Check if a Credential Property Exists
    If (!($ContextProperties.Name -contains 'BaseUri')){
      throw "Context File did not contain a BaseUri Property"
    }

    # Ensure BaseUri Property is of the correct type
    If (!($LoadContext.BaseUri.GetType().Name -eq 'String')) {
      throw "Context File BaseUri Property is not of type [String]"
    }

    # Check if a NoValidateSsl Property Exists
    If (!($ContextProperties.Name -contains 'NoValidateSsl')){
      throw "Context File did not contain a NoValidateSsl Property"
    }

    # Ensure NoValidateSsl Property is of the correct type
    If (!($LoadContext.NoValidateSsl.GetType().Name -eq 'boolean')) {
      throw "Context File NoValidateSsl Property is not of type [boolean]"
    }

    $OutputContext = [PSCustomObject]@{
      Credential = [PSCredential]$LoadContext.Credential
      BaseUri = [System.UriBuilder]$LoadContext.BaseUri
      NoValidateSsl = $LoadContext.NoValidateSsl
    }

    Write-Output $OutputContext

  }

  End {

  }

}

# Save MISP API Key/URI to Configuration File
function Save-MispContext {
    <#
    .SYNOPSIS
        Write MISP Context containing Api Key/URL out to a file for future reference
    .DESCRIPTION
        Write MISP Context containing Api Key/Url out to a file for future reference
 
        Default location is user's profile path, but can be overridden with -DestinationPath
        Default Filename is MISPContext.xml, but can be overridden with -Filename
    .PARAMETER DestinationPath
        Path to save MispContext to, defaults to users AppDataLocal Path on your operating system
    .PARAMETER FileName
        Name of file to use for MISP Context, defaults to MISPContext.xml
    .INPUTS
        [PSCustomObject] -> MISP Context
        [String] -> DestinationPath
        [String] -> Filename
    .OUTPUTS
        No output is expected if this succeeds
    .EXAMPLE
        Save to default location and filename
        PS > $MispContext | Save-MispContext
    .EXAMPLE
        Save to default location with alternate filename
        PS > $MispContext | Save-MispContext -Filename 'MyMISP.xml'
    .LINK
        https://github.com/IPSecMSSP/misp.tools
    #>


  [CmdletBinding(SupportsShouldProcess)]


  Param (
    [Parameter(
      Mandatory = $true,
      ValueFromPipeline = $true,
      Position = 0
    )]
    [pscustomobject] $InputObject,
    [Parameter(
      Mandatory = $false,
      ValueFromPipeline = $false,
      Position = 1
    )]
    [string] $DestinationPath = $DefaultMispPreferencePath,
    [Parameter(
      Mandatory = $false,
      ValueFromPipeline = $false,
      Position = 2
    )]
    [string] $Filename = $DefaultMispPreferenceFilename
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    $ContextFilename = Join-Path -Path $DestinationPath -ChildPath $Filename
    Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)"

  }

  Process {
    # Create the folder if it does not exist
    if (!(Test-Path $DestinationPath)) {
      if ($PSCmdlet.ShouldProcess($DestinationPath, "Create Configuration Folder")) {
        Write-Verbose "$($me): Configration Folder $($DestinationPath) does not exist, Creating"
        $Output = New-Item -ItemType Directory -Path $DestinationPath
        Write-Debug $Output
      }
    }

    # Save the file
    if ($PSCmdlet.ShouldProcess($ContextFilename, "Save MISP Context")) {
      Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)"
      # Ensure output is appropriately formatted
      $OutputObject = [PSCustomObject]@{
        Credential = $InputObject.Credential
        BaseUri = $InputObject.BaseUri.ToString()
        NoValidateSsl = [bool]$InputObject.NoValidateSsl
      }
      $OutputObject | Export-Clixml $ContextFilename
    }
  }

  End {

  }

}

function Get-MispOrganisation {
  <#
    .SYNOPSIS
        Get MISP Organisation Details
    .DESCRIPTION
        Get MISP Organisation Details, either by Id, Name, or all
 
        Take the provided details and use these to create a new Organisation in MISP
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Name
        Name of the organisation to return detais for.
    .PARAMETER Id
        Id of Organisation to return details for.
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
        [string] -> Name
    .OUTPUTS
        [Array] -> Array of Organisations
    .EXAMPLE
        PS> $Org = Get-MispOrganisation -Context $MispContext -Name 'My Organisation'
        Returns definition of 'My Organisation', or $Null if it does not exist
    .EXAMPLE
        PS> $Org = Get-MispOrganisation -Context $MispContext -Id 7
        Returns definition of the organisation with Id 7, or $Null if it does not exist
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.misp-project.org/openapi/#tag/Organisations
    #>

  [cmdletbinding(DefaultParameterSetName = 'All')]
  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true,ParameterSetName = 'ByName')]
    [string]$Name,

    [Parameter(Mandatory = $true, ParameterSetName = 'ById')]
    [string]$Id
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name
    Write-Verbose ('Entering: {0}' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, 'organisations')
  }

  Process {

    switch ($PsCmdLet.ParameterSetName) {
      'ById' {
        # Append the Org ID to the URI Path
        $Uri.Path = [io.path]::combine($Uri.Path, 'view', $Id)

        $Organisation = (Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET").Organisation
      }

      'ByName' {
        # Get all orgs
        $Orgs = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET"

        # Filter to the matching Org
        $Organisation = $Orgs.Organisation | Where-Object -Property name -eq $Name
      }

      'All' {
        # Get all orgs
        $Organisation = (Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET").Organisation
      }
    }

    # Output Organisation(s)
    Write-Output $Organisation

  }

  End {

  }
}

function New-MispOrganisation {
  <#
    .SYNOPSIS
        Create a new MISP Organisation
    .DESCRIPTION
        Create a new MISP Organisation
 
        Take the provided details and use these to create a new Organisation in MISP
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Name
        Name of the organisation.
    .PARAMETER Uuid
        Specific UUID to assign to Organisation. If not specified, a new UUID will be generated automatically.
    .PARAMETER Description
        More detailed description of the Organisation.
    .PARAMETER Nationality
        Nationality of the Organisation, if applicable/known.
    .PARAMETER Sector
        Industry Sector of the Organisation. Sectors defined as per https://github.com/MISP/misp-galaxy/blob/main/clusters/sector.json
    .PARAMETER LocalOrg
        Boolean value indicating whether this is a local organisation. Defaults to true.
    .PARAMETER RestrictDomains
        Array of domains to restrict users to. Users that are part of the listed domains will be associated with this org.
    .PARAMETER LandingPage
        URL to Landing Page for Org.
    .PARAMETER Type
        Organisation Type. Freeform text.
    .INPUTS
        [PsCustomObject] -> Context
        [String] -> Name
        [string] -> Uuid
        [string] -> Description
        [string] -> Nationality
        [string] -> Sector
        [boolean] -> LocalOrg
        [array][string] -> RestrictDomains
        [string] -> LandingPage
        [string] -> Type
    .OUTPUTS
        [PsCustomObject] -> Properties of New Organisation
    .EXAMPLE
        PS> $Org = New-MispOrganisation -Context $MispContext -Name 'My Organisation'
        Returns definition of new organisation
    .EXAMPLE
        PS> $Org = New-MispOrganisation -Context $MispContext -Name 'My Organisation' -UUID '63f6c8c0-2563-4b3b-bc46-64b3016f9948' -Local $False
        Create a new remote organisation with the specified UUID (to match a definition elsewhere)
        Returns definition of the new organisation
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.misp-project.org/openapi/#tag/Organisations
    #>
  [cmdletbinding(SupportsShouldProcess)]
  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true)]
    [string]$Name,

    [Parameter(Mandatory = $false)]
    [string]$Uuid,

    [Parameter(Mandatory = $false)]
    [string]$Description,

    [Parameter(Mandatory = $false)]
    [string]$Type,

    [Parameter(Mandatory = $false)]
    [string]$Nationality,

    [Parameter(Mandatory = $false)]
    [string]$Sector,

    [Parameter(Mandatory = $false)]
    [boolean]$LocalOrg = $true,

    [Parameter(Mandatory = $false)]
    [array]$RestrictDomains,

    [Parameter(Mandatory = $false)]
    [string]$LandingPage

  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name
    Write-Verbose ('Entering: {0}' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, 'admin','organisations', 'add')
  }

  Process {

    $Organisation = @{
      name = $Name
      description = $Description
      type = $Type
      sector = $Sector
      uuid = $Uuid
      nationality = $Nationality
      restricted_to_domain = $RestrictDomains
      landingpage = $LandingPage
      local = $LocalOrg
    }

    if ($PSCmdlet.ShouldProcess($Uri.Uri.ToString(), 'Create new MISP Organisation')) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" -Body $Organisation
    }

    Write-Output $Response.Organisation
  }

  End {

  }
}

function Update-MispOrganisation {
  <#
    .SYNOPSIS
        Create a new MISP Event
    .DESCRIPTION
        Create a new MISP Event
 
        Take the provided details and use these to create a new Event in MISP
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of Organisation to update.
    .PARAMETER Name
        New Name of the organisation.
    .PARAMETER Uuid
        New UUID to assign to Organisation. If not specified, a new UUID will be generated automatically.
    .PARAMETER Description
        More detailed description of the Organisation.
    .PARAMETER Nationality
        Nationality of the Organisation, if applicable/known.
    .PARAMETER Sector
        Industry Sector of the Organisation. Sectors defined as per https://github.com/MISP/misp-galaxy/blob/main/clusters/sector.json
    .PARAMETER LocalOrg
        Boolean value indicating whether this is a local organisation. Defaults to true.
    .PARAMETER RestrictDomains
        Array of domains to restrict users to. Users that are part of the listed domains will be associated with this org.
    .PARAMETER LandingPage
        URL to Landing Page for Org.
    .PARAMETER Type
        Organisation Type. Freeform text.
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
    .OUTPUTS
        [Array] -> Array of Events
    .EXAMPLE
        PS> $Events = New-MispEvent -Context $MispContext
        Return first all Events
    .EXAMPLE
        PS> $Event = New-MispEvent -Context $MispContext -Id 1234
        Return details for event 1234
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.misp-project.org/openapi/#tag/Organisations
    #>
  [cmdletbinding(SupportsShouldProcess)]
  param (
    [Parameter(Mandatory = $true)]
    [PsCustomObject]$Context,

    [Parameter(Mandatory = $true)]
    [string]$Id,

    [Parameter(Mandatory = $false)]
    [string]$Name,

    [Parameter(Mandatory = $false)]
    [string]$Description,

    [Parameter(Mandatory = $false)]
    [string]$Type,

    [Parameter(Mandatory = $false)]
    [string]$Nationality,

    [Parameter(Mandatory = $false)]
    [string]$Sector,

    [Parameter(Mandatory = $false)]
    [boolean]$LocalOrg = $true,

    [Parameter(Mandatory = $false)]
    [array]$RestrictDomains,

    [Parameter(Mandatory = $false)]
    [string]$LandingPage

  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name
    Write-Verbose ('Entering: {0}' -f $Me)

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, 'admin', 'organisations', 'edit')
  }

  Process {
    $Uri.Path = [io.path]::combine($Uri.Path, $Id)

    $Organisation = @{}

    if ($PSBoundParameters.ContainsKey('Name')) {
      Write-Verbose ('{0}: Adding parameter [name]: {1}' -f $Me, $Name)
      $Organisation.Add('name', $Name)
    }

    if ($PSBoundParameters.ContainsKey('Description')) {
      $Organisation.Add('descriptiopn',$Description)
    }

    if ($PSBoundParameters.ContainsKey('Uuid')) {
      $Organisation.Add('uuid', $Uuid)
    }

    if ($PSBoundParameters.ContainsKey('Type')) {
      $Organisation.Add('type', $Type)
    }

    if ($PSBoundParameters.ContainsKey('Sector')) {
      $Organisation.Add('sector', $Sector)
    }

    if ($PSBoundParameters.ContainsKey('Nationality')) {
      $Organisation.Add('nationality', $Nationality)
    }

    if ($PSBoundParameters.ContainsKey('RestrictDomains')) {
      $Organisation.Add('restricted_to_domain', $RestrictDomains)
    }

    if ($PSBoundParameters.ContainsKey('LocalOrg')) {
      $Organisation.Add('local', $LocalOrg)
    }

    Write-Debug ('{0}: Payload: {1}' -f $Me, $EventBody)

    if ($PSCmdlet.ShouldProcess($Uri.Uri.ToString(), 'Update MISP Organisation')) {
      # Call the API
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "PUT" -Body $Organisation
    }

    Write-Output $Response
  }

  End {

  }
}

# Set MISP API Key/URI Context
function Add-MispAttributeTag {
  <#
  .SYNOPSIS
      Add a tag to a MISP Attribute
  .DESCRIPTION
      Add a tag to a MISP Attribute
 
      An attribute may have one or more tags associated. This can assist with the filtering and grouping of attributes
  .PARAMETER Context
      PSCustomObject with containing the MISP Context
  .PARAMETER TagId
      Id of tag to add to Attribute
  .PARAMETER EventId
      Id of Attribute to which tag should be added
  .PARAMETER Local
      Whether the tag should be attached locally or not to the event
  .INPUTS
      [PsCustomObject] -> Context
      [Int] -> TagId
      [Int] -> AttributeId
      [switch] -> Local
  .OUTPUTS
      None if successful
  .EXAMPLE
      PS> $Tags = Add-MispAttributeTag -Context $MispContext -AttributeId 1234 -TagId 69 -Local
      Add Tag with Id 69 to attribute Id 1234, locally
  .LINK
      https://github.com/IPSecMSSP/misp.tools
      https://www.circl.lu/doc/misp/automation/#attribute-management
  #>


[CmdletBinding()]

param (
  [Parameter(Mandatory=$true)]
  [PsCustomObject]$Context,

  [Parameter(
    Mandatory=$true
    )]
  [Int]$AttributeId,

  [Parameter(
    Mandatory=$true
    )]
  [Int]$TagId,

  [Parameter(
    Mandatory=$false
  )]
  [switch] $Local
)

Begin {
  $Me = $MyInvocation.MyCommand.Name

  Write-Verbose "$($Me): Add Tag to MISP Attribute"

  if($Local) {
    $TagLocal = 1
  } else {
    $TagLocal = 0
  }

  # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
  $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
  $Uri.Path = [io.path]::combine($Uri.Path, ("attributes/addTag/{0}/{1}/local:{2}" -f $AttributeId, $TagId, $TagLocal))

}

Process {

  # Call the API
  $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST'

  Write-Debug $Response | ConvertTo-Json -Depth 10
  if (!$Response.saved) {
    Write-Warning "Unable to add tag to Attribute"
  }
}

End {

}

}

# Set MISP API Key/URI Context
function Add-MispEventTag {
    <#
    .SYNOPSIS
        Add a tag to a MISP Event
    .DESCRIPTION
        Add a tag to a MISP Event
 
        An event may have one or more tags associated. This can assist with the filtering and grouping of events
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER TagId
        Id of tag to add to Event
    .PARAMETER EventId
        Id of event to which tag should be added
    .PARAMETER Local
        Whether the tag should be attached locally or not to the event
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> TagId
        [Int] -> EventId
        [switch] -> Local
    .OUTPUTS
        None if successful
    .EXAMPLE
        PS> $Tags = Add-MispEventTag -Context $MispContext -EventId 1234 -TagId 69 -Local
        Add Tag with Id 69 to event Id 1234, locally
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#attribute-management
    #>


  [CmdletBinding()]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(
      Mandatory=$true
      )]
    [Int]$EventId,

    [Parameter(
      Mandatory=$true
      )]
    [Int]$TagId,

    [Parameter(
      Mandatory=$false
    )]
    [switch] $Local
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Add Tag to MISP Event"

    if($Local) {
      $TagLocal = 1
    } else {
      $TagLocal = 0
    }

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, ("events/addTag/{0}/{1}/local:{2}" -f $EventId, $TagId, $TagLocal))

  }

  Process {

    # Call the API
    $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST'

    Write-Debug $Response | ConvertTo-Json -Depth 10

    if (!$Response.saved) {
      Write-Warning "Unable to add tag to Event"
    }
  }

  End {

  }

}

# Set MISP API Key/URI Context
function Get-MispTag {
    <#
    .SYNOPSIS
        Get MISP Tag(s)
    .DESCRIPTION
        Get MISP Tag(s)
 
        If no Tag ID is supplied, all tags are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific tag
    .PARAMETER Criteria
        Search criteria in SQL Match format. eg. "%banana%"
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
        [string] -> Criteria
    .OUTPUTS
        [Array] -> Array of tags
    .EXAMPLE
        PS> $Tags = Get-MispTag -Context $MispContext
        Return all Tags
    .EXAMPLE
        PS> $Event = Get-MispTag -Context $MispContext -Id 1234
        Return details for tag 1234
    .EXAMPLE
        PS> $Event = Get-MispTag -Context $MispContext -Criteria '%banana%'
        Return all tags containing the text 'banana'
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#attribute-management
    #>


  [CmdletBinding(DefaultParameterSetName='ListAll')]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(
      Mandatory=$false,
      ParameterSetName='ById'
      )]
    [Int]$Id,

    [Parameter(
      Mandatory=$false,
      ParameterSetName='ByCriteria'
    )]
    [string]$Criteria
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Get MISP Attribute(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "tags")

    # Append the Event Id if requested
    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)")
    } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') {
      $Uri.Path = [io.path]::Combine($Uri.Path, "search/$($Criteria)")
    }
  }

  Process {

    # Call the API
    $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri

    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      # Only a single event was requested
      Write-Output $Response
    } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') {
      Write-Output $Response.Tag
    } else {
      # Return all fo the events
      Write-Output $Response.Tag
    }

  }

  End {

  }

}

# Set MISP API Key/URI Context
function Get-MispWarningList {
    <#
    .SYNOPSIS
        Get MISP Warning List(s)
    .DESCRIPTION
        Get MISP Warning List(s)
 
        If no Warning List ID is supplied, all warning lists are returned
    .PARAMETER Context
        PSCustomObject with containing the MISP Context
    .PARAMETER Id
        Id of a specific warning list
    .PARAMETER Criteria
        Criteria to limit the set of returned Warning Lists
 
        Available Criteria are:
        - enabled
 
        Any criteria other than 'enabled' are ignored by the API
    .INPUTS
        [PsCustomObject] -> Context
        [Int] -> Id
        [hashtable] -> Criteria
    .OUTPUTS
        [Array] -> Array of Warning Lists
    .EXAMPLE
        PS> $WarningLists = Get-MispWarningList -Context $MispContext
        Return first all Warning Lists
    .EXAMPLE
        PS> $WarningLists = Get-MispWarningList -Context $MispContext -Id 1234
        Return details for warning list 1234
    .EXAMPLE
        PS> $Criteria = @{
          enabled = $true
        }
        PS> $WarningLists = Get-MispWarningList -Context $MispContext -Criteria $Criteria
        Return Enabled Warning Lists
    .LINK
        https://github.com/IPSecMSSP/misp.tools
        https://www.circl.lu/doc/misp/automation/#warninglists-api
    #>


  [CmdletBinding(DefaultParameterSetName='ListAll')]

  param (
    [Parameter(Mandatory=$true)]
    [PsCustomObject]$Context,

    [Parameter(
      Mandatory=$false,
      ParameterSetName='ById'
      )]
    [Int]$Id,

    [Parameter(
      Mandatory=$false,
      ParameterSetName='ByCriteria'
    )]
    [hashtable]$Criteria
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose "$($Me): Get MISP Warning List(s)"

    # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that.
    $Uri = [System.UriBuilder]$Context.BaseUri.ToString()
    $Uri.Path = [io.path]::combine($Uri.Path, "warninglists")

    # Append the Event Id if requested
    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)")
    }
  }

  Process {

    Write-Verbose "$($Me): Invoking with ParameterSetName: $($PSCmdlet.ParameterSetName)"
    # Call the API
    if ($PSCmdlet.ParameterSetName -eq 'ByCriteria') {
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' -Body ($Criteria | ConvertTo-Json)
    } else {
      $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri
    }

    if ($MyInvocation.BoundParameters.ContainsKey("Id")) {
      # Only a single event was requested
      Write-Output $Response.warningList
    } else {
      # Return all fo the events
      Write-Output $Response.warninglists.warninglist
    }

  }

  End {

  }

}

Export-ModuleMember -Function Get-MispAttribute, New-MispAttribute, Search-MispAttribute, Get-MispEvent, New-MispEvent, Publish-MispEvent, Search-MispEvent, Unpublish-MispEvent, Update-MispEvent, New-MispContext, Read-MispContext, Save-MispContext, Get-MispOrganisation, New-MispOrganisation, Update-MispOrganisation, Add-MispAttributeTag, Add-MispEventTag, Get-MispTag, Get-MispWarningList

# SIG # Begin signature block
# MIIt6AYJKoZIhvcNAQcCoIIt2TCCLdUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAgck+6ComkVPoC
# hwOYVa1GJjupvMDd0XFSsdZwFbaTpqCCEyswggWQMIIDeKADAgECAhAFmxtXno4h
# 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/zoUiJLifJNOdSAVc4DvDdw4elMWpIR7gCzfDGCGhMwghoPAgEB
# MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD
# VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI
# QTM4NCAyMDIxIENBMQIQAWJsuy55KuGa8LRj2JghvDANBglghkgBZQMEAgEFAKCB
# hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ
# BDEiBCATHDl31YlnCZ9Le+Wjshw3lGieNcGQGBgEreHLlfrOwTANBgkqhkiG9w0B
# AQEFAASCAYCsjzXXpWzy/If0KTORRqJYo5TP4lZ6pwoxzARCvbzwCKYea1a39MKO
# 0k6QhtKezXFuGGxf8rf44owpKdA7+XPgaNvElJoxK3WITluRafhKqeBYGWdvewyC
# lxbCy8zd9Y0kiL0rmGV/gwF41avteQoI+UslPk2L0qY6ik98918ZcctB6SNEqevQ
# bNiYu5Wt6M/MKW+E/56en6JzsZlvLLP6Z8GbETue5ERn+lgDiBMzLoaCqKz4rC3U
# DwTcR+WVlHl+c3RbFI/zLGT4NDxhuwLOkiX+emmoSUL6NN5XFqAzg6xPubX8ObCa
# RsrF7z4p41u9g1Qpa+yn9BAxl22h3kDEuuh5Gqq/4jBH+55BzfEluXwbNyixNrLu
# frKIqoNH/jPTooX9p8a4JF66LVCfhOiObIg26QnxFutGKijdqI13JIc4dbywPx2N
# xEy2iRfFgorA/J/0lu/cvV/f8zFQ1bsaVxtpcY6j0MsxVlWukMrMRRp43rEhCPh/
# oyFEGrm2MwihghdgMIIXXAYKKwYBBAGCNwMDATGCF0wwghdIBgkqhkiG9w0BBwKg
# ghc5MIIXNQIBAzEPMA0GCWCGSAFlAwQCAgUAMIGHBgsqhkiG9w0BCRABBKB4BHYw
# dAIBAQYJYIZIAYb9bAcBMEEwDQYJYIZIAWUDBAICBQAEMFgVnrKkMRwN/rPYtKNB
# bQtbIouqZSYxNoUnvd6NnO2oAquidF4qHGcUI7Se0K7BCgIQK+72/YNDhpYde1P5
# /79ZVxgPMjAyNDA4MTUwMjI0MDRaoIITCTCCBsIwggSqoAMCAQICEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
# DkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJT
# QTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yMzA3MTQwMDAwMDBaFw0z
# NDEwMTMyMzU5NTlaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjEgMB4GA1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjMwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQCjU0WHHYOOW6w+VLMj4M+f1+XS512hDgnc
# L0ijl3o7Kpxn3GIVWMGpkxGnzaqyat0QKYoeYmNp01icNXG/OpfrlFCPHCDqx5o7
# L5Zm42nnaf5bw9YrIBzBl5S0pVCB8s/LB6YwaMqDQtr8fwkklKSCGtpqutg7yl3e
# GRiF+0XqDWFsnf5xXsQGmjzwxS55DxtmUuPI1j5f2kPThPXQx/ZILV5FdZZ1/t0Q
# oRuDwbjmUpW1R9d4KTlr4HhZl+NEK0rVlc7vCBfqgmRN/yPjyobutKQhZHDr1eWg
# 2mOzLukF7qr2JPUdvJscsrdf3/Dudn0xmWVHVZ1KJC+sK5e+n+T9e3M+Mu5SNPvU
# u+vUoCw0m+PebmQZBzcBkQ8ctVHNqkxmg4hoYru8QRt4GW3k2Q/gWEH72LEs4VGv
# tK0VBhTqYggT02kefGRNnQ/fztFejKqrUBXJs8q818Q7aESjpTtC/XN97t0K/3k0
# EH6mXApYTAA+hWl1x4Nk1nXNjxJ2VqUk+tfEayG66B80mC866msBsPf7Kobse1I4
# qZgJoXGybHGvPrhvltXhEBP+YUcKjP7wtsfVx95sJPC/QoLKoHE9nJKTBLRpcCcN
# T7e1NtHJXwikcKPsCvERLmTgyyIryvEoEyFJUX4GZtM7vvrrkTjYUQfKlLfiUKHz
# OtOKg8tAewIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQC
# MAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIw
# CwYJYIZIAYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0G
# A1UdDgQWBBSltu8T5+/N0GSh1VapZTGj3tXjSTBaBgNVHR8EUzBRME+gTaBLhklo
# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2
# U0hBMjU2VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0
# MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCB
# GtbeoKm1mBe8cI1PijxonNgl/8ss5M3qXSKS7IwiAqm4z4Co2efjxe0mgopxLxjd
# TrbebNfhYJwr7e09SI64a7p8Xb3CYTdoSXej65CqEtcnhfOOHpLawkA4n13IoC4l
# eCWdKgV6hCmYtld5j9smViuw86e9NwzYmHZPVrlSwradOKmB521BXIxp0bkrxMZ7
# z5z6eOKTGnaiaXXTUOREEr4gDZ6pRND45Ul3CFohxbTPmJUaVLq5vMFpGbrPFvKD
# NzRusEEm3d5al08zjdSNd311RaGlWCZqA0Xe2VC1UIyvVr1MxeFGxSjTredDAHDe
# zJieGYkD6tSRN+9NUvPJYCHEVkft2hFLjDLDiOZY4rbbPvlfsELWj+MXkdGqwFXj
# hr+sJyxB0JozSqg21Llyln6XeThIX8rC3D0y33XWNmdaifj2p8flTzU8AL2+nCps
# eQHc2kTmOt44OwdeOVj0fHMxVaCAEcsUDH6uvP6k63llqmjWIso765qCNVcoFstp
# 8jKastLYOrixRoZruhf9xHdsFWyuq69zOuhJRrfVf8y2OMDY7Bz1tqG4QyzfTkx9
# HmhwwHcK1ALgXGC7KP845VJa1qwXIiNO9OzTF/tQa/8Hdx9xl0RBybhG02wyfFgv
# Z0dl5Rtztpn5aywGRu9BHvDwX+Db2a2QgESvgBBBijCCBq4wggSWoAMCAQICEAc2
# N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEh
# MB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAw
# MFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYg
# U0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFE
# FUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoi
# GN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YA
# e9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O
# 9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI
# 1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7m
# O1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPK
# qpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8F
# nGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMD
# iP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4Jduyr
# XUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFd
# MIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91
# jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8B
# Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKG
# NWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290
# RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQC
# MAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW
# 2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H
# +oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4os
# equFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p
# /yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnf
# xI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36T
# U6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0
# cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf
# +yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa6
# 3VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1d
# wvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9E
# FUrnEw4d2zc4GqEr9u3WfPwwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBha
# MA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDky
# MzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX
# BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0
# ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo
# 3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutW
# xpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQ
# RBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nP
# zaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6P
# gNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEB
# fCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3
# wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2
# mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2
# Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF1
# 3nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+d
# IPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMB
# Af8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSME
# GDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYB
# BQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# QwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2Ny
# bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNV
# HSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9
# thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4
# offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cG
# AxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9
# HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvP
# J6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXiji
# uZQxggOGMIIDggIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy
# dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI
# QTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5pr/bP1/lYRYwDQYJYIZIAWUD
# BAICBQCggeEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJ
# BTEPFw0yNDA4MTUwMjI0MDRaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFGbwKzLC
# wskPgl3OqorJxk8ZnM9AMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEINL25G3tdCLM
# 0dRAV2hBNm+CitpVmq4zFq9NGprUDHgoMD8GCSqGSIb3DQEJBDEyBDAaag0VeLTo
# alsuZVJ3ao03gcDjHWhZHQsW78OUx2X0Ggo1r+xnJPYk2ZMbgHjiGecwDQYJKoZI
# hvcNAQEBBQAEggIAi7fXl8NMCtLsV7lunjogN80v+LZIYz2w253AFPwH3iMg3jD2
# jQJPzQBDcAF+K1H0MfF6xbVWlL+ReibBSjV4x5sKiavNQjTM2ObANqeeJoyLcw/H
# xT5Ppp/dsbVc6b53wqZwNYSRfEVc3mCB2YmH/bMweBU8Oh5uD3JAMgfBZR6UnyQs
# w7UTaNKrGYDBZy4VEs+bbEy3674V8mc2hmZ6PvUC1dhGRDwqoYYVbDJhIm9GvTAb
# Wn2DAE/c04GzLIKS19lDVDLB0xHsE06bmXx20g9WuatzONSeQFozCfoO2XPoO0yG
# XmZlfZcpKbjlt2OKV7RwfjvlFShS1yY5d64D4eHTqWjfydi2lJmJEBS4rbYU69QC
# Z0pt7mJGIpGaI/Qx6e2BVtjSIiPa3xTVHBhlbLPXAXo+Rs7LqsATgQinV43mOuYn
# lP6tcPot65dx2QG9JBRvWs4CqZcm1ybnYM4M2M3ARSMgKaXahPR1hLR8zdwGu+g6
# hDYjW2cT0PuwZnnm1KGb0MxBy/aZW5e4v18LH7fVSS5Op+gFjoISW50r2iss5rNb
# DMaUZx8H5ouOud5oUb/wNxJPGj9iv7fs8IAK/FLlKpoXErwkN+hzdXLUGwCPtcky
# 9erK7SvyhSFS8rWuuCydP7whx20zkKkNQ46RPqKP0HslD7RCu/tnNabPnIM=
# SIG # End signature block