DefenderAPI.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\DefenderAPI.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName DefenderAPI.Import.DoDotSource -Fallback $false
if ($DefenderAPI_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName DefenderAPI.Import.IndividualFiles -Fallback $false
if ($DefenderAPI_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'DefenderAPI' -Language 'en-US'

class DefenderToken {
    #region Token Data
    [string]$AccessToken
    [System.DateTime]$ValidAfter
    [System.DateTime]$ValidUntil
    [string[]]$Scopes
    [string]$RefreshToken
    [string]$Audience
    [string]$Issuer
    [PSObject]$TokenData
    #endregion Token Data
    
    #region Connection Data
    [string]$Service
    [string]$Type
    [string]$ClientID
    [string]$TenantID
    [string]$ServiceUrl
    [Hashtable]$Header = @{}
    
    # Workflow: Client Secret
    [System.Security.SecureString]$ClientSecret
    
    # Workflow: Certificate
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate

    # Workflow: Username & Password
    [PSCredential]$Credential
    #endregion Connection Data
    
    #region Constructors
    DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [Securestring]$ClientSecret, [string]$ServiceUrl) {
        $this.Service = $Service
        $this.ClientID = $ClientID
        $this.TenantID = $TenantID
        $this.ClientSecret = $ClientSecret
        $this.ServiceUrl = $ServiceUrl
        $this.Type = 'ClientSecret'
    }
    
    DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [string]$ServiceUrl) {
        $this.Service = $Service
        $this.ClientID = $ClientID
        $this.TenantID = $TenantID
        $this.Certificate = $Certificate
        $this.ServiceUrl = $ServiceUrl
        $this.Type = 'Certificate'
    }

    DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [pscredential]$Credential, [string]$ServiceUrl) {
        $this.Service = $Service
        $this.ClientID = $ClientID
        $this.TenantID = $TenantID
        $this.Credential = $Credential
        $this.ServiceUrl = $ServiceUrl
        $this.Type = 'UsernamePassword'
    }

    DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [string]$ServiceUrl, [bool]$IsDeviceCode) {
        $this.Service = $Service
        $this.ClientID = $ClientID
        $this.TenantID = $TenantID
        $this.ServiceUrl = $ServiceUrl
        if ($IsDeviceCode) { $this.Type = 'DeviceCode' }
        else { $this.Type = 'Browser' }
    }
    #endregion Constructors

    [void]SetTokenMetadata([PSObject] $AuthToken) {
        $this.AccessToken = $AuthToken.AccessToken
        $this.ValidAfter = $AuthToken.ValidAfter
        $this.ValidUntil = $AuthToken.ValidUntil
        $this.Scopes = $AuthToken.Scopes
        if ($AuthToken.RefreshToken) { $this.RefreshToken = $AuthToken.RefreshToken }

        $tokenPayload = $AuthToken.AccessToken.Split(".")[1].Replace('-', '+').Replace('_', '/')
        while ($tokenPayload.Length % 4) { $tokenPayload += "=" }
        $bytes = [System.Convert]::FromBase64String($tokenPayload)
        $data = [System.Text.Encoding]::ASCII.GetString($bytes) | ConvertFrom-Json
        
        if ($data.roles) { $this.Scopes = $data.roles }
        elseif ($data.scp) { $this.Scopes = $data.scp -split " " }

        $this.Audience = $data.aud
        $this.Issuer = $data.iss
        $this.TokenData = $data
    }

    [hashtable]GetHeader() {
        if ($this.ValidUntil -lt (Get-Date).AddMinutes(5)) {
            $this.RenewToken()
        }

        $currentHeader = @{}
        if ($this.Header.Count -gt 0) {
            $currentHeader = $this.Header.Clone()
        }
        $currentHeader.Authorization = "Bearer $($this.AccessToken)"

        return $currentHeader
    }

    [void]RenewToken()
    {
        $defaultParam = @{
            ServiceUrl = $this.ServiceUrl
            TenantID = $this.TenantID
            ClientID = $this.ClientID
        }
        switch ($this.Type) {
            Certificate {
                $result = Connect-ServiceCertificate @defaultParam -Certificate $this.Certificate
                $this.SetTokenMetadata($result)
            }
            ClientSecret {
                $result = Connect-ServiceClientSecret @defaultParam -ClientSecret $this.ClientSecret
                $this.SetTokenMetadata($result)
            }
            UsernamePassword {
                $result = Connect-ServicePassword @defaultParam -Credential $this.Credential
                $this.SetTokenMetadata($result)
            }
            DeviceCode {
                if ($this.RefreshToken) {
                    Connect-ServiceRefreshToken -Token $this
                    return
                }

                $result = Connect-ServiceDeviceCode @defaultParam
                $this.SetTokenMetadata($result)
            }
            Browser {
                if ($this.RefreshToken) {
                    Connect-ServiceRefreshToken -Token $this
                    return
                }

                $result = Connect-ServiceBrowser @defaultParam -SelectAccount
                $this.SetTokenMetadata($result)
            }
        }
    }
}

<#
# Example:
Register-PSFTeppScriptblock -Name "DefenderAPI.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>


Register-PSFTeppScriptblock -Name 'DefenderAPI.Service' -ScriptBlock {
    (Get-DefenderAPIService).Name
} -Global

<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name DefenderAPI.alcohol
#>

Register-PSFTeppArgumentCompleter -Command Connect-DefenderAPIService -Parameter Service -Name DefenderAPI.Service

function Connect-ServiceBrowser {
    <#
    .SYNOPSIS
        Interactive logon using the Authorization flow and browser. Supports SSO.
     
    .DESCRIPTION
        Interactive logon using the Authorization flow and browser. Supports SSO.
 
        This flow requires an App Registration configured for the platform "Mobile and desktop applications".
        Its redirect Uri must be "http://localhost"
 
        On successful authentication
     
    .PARAMETER ClientID
        The ID of the registered app used with this authentication request.
     
    .PARAMETER TenantID
        The ID of the tenant connected to with this authentication request.
     
    .PARAMETER SelectAccount
        Forces account selection on logon.
        As this flow supports single-sign-on, it will otherwise not prompt for anything if already signed in.
        This could be a problem if you want to connect using another (e.g. an admin) account.
     
    .PARAMETER Scopes
        Generally doesn't need to be changed from the default '.default'
 
    .PARAMETER LocalPort
        The local port that should be redirected to.
        In order to process the authentication response, we need to listen to a local web request on some port.
        Usually needs not be redirected.
        Defaults to: 8080
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
 
    .PARAMETER Browser
        The path to the browser to use for the authentication flow.
        Provide the full path to the executable.
        The browser must accept the url to open as its only parameter.
        Defaults to your default browser.
     
    .PARAMETER NoReconnect
        Disables automatic reconnection.
        By default, this module will automatically try to reaquire a new token before the old one expires.
     
    .EXAMPLE
        PS C:\> Connect-ServiceBrowser -ClientID '<ClientID>' -TenantID '<TenantID>'
     
        Connects to the specified tenant using the specified client, prompting the user to authorize via Browser.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,

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

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

        [switch]
        $SelectAccount,

        [AllowEmptyCollection()]
        [string[]]
        $Scopes,

        [int]
        $LocalPort = 8080,

        [string]
        $Browser,

        [switch]
        $NoReconnect
    )
    process {
        Add-Type -AssemblyName System.Web
        if (-not $Scopes) { $Scopes = @('.default') }

        $redirectUri = "http://localhost:$LocalPort"
        $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource

        if (-not $NoReconnect) {
            $actualScopes = @($actualScopes) + 'offline_access'
        }

        $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/authorize?"
        $state = Get-Random
        $parameters = @{
            client_id     = $ClientID
            response_type = 'code'
            redirect_uri  = $redirectUri
            response_mode = 'query'
            scope         = $actualScopes -join ' '
            state         = $state
        }
        if ($SelectAccount) {
            $parameters.prompt = 'select_account'
        }

        $paramStrings = foreach ($pair in $parameters.GetEnumerator()) {
            $pair.Key, ([System.Web.HttpUtility]::UrlEncode($pair.Value)) -join '='
        }
        $uriFinal = $uri + ($paramStrings -join '&')
        Write-PSFMessage -Level Verbose -String 'Connect-ServiceBrowser.AuthorizeUri' -StringValues $uriFinal

        $redirectTo = 'https://raw.githubusercontent.com/FriedrichWeinmann/MiniGraph/master/nothing-to-see-here.txt'
        if ((Get-Random -Minimum 10 -Maximum 99) -eq 66) {
            $redirectTo = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
        }
        
        # Start local server to catch the redirect
        $http = [System.Net.HttpListener]::new()
        $http.Prefixes.Add("$redirectUri/")
        try { $http.Start() }
        catch { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Failed to create local http listener on port $LocalPort. Use -LocalPort to select a different port. $_" -Category OpenError }

        # Execute in default browser
        if ($Browser) { & $Browser $uriFinal }
        else { Start-Process $uriFinal }

        # Get Result
        $task = $http.GetContextAsync()
        $authorizationCode, $stateReturn, $sessionState = $null
        try {
            while (-not $task.IsCompleted) {
                Start-Sleep -Milliseconds 200
            }
            $context = $task.Result
            $context.Response.Redirect($redirectTo)
            $context.Response.Close()
            $authorizationCode, $stateReturn, $sessionState = $context.Request.Url.Query -split "&"
        }
        finally {
            $http.Stop()
            $http.Dispose()
        }

        if (-not $stateReturn) {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Authentication failed (see browser for details)" -Category AuthenticationError
        }

        if ($stateReturn -match '^error_description=') {
            $message = $stateReturn -replace '^error_description=' -replace '\+',' '
            $message = [System.Web.HttpUtility]::UrlDecode($message)
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Error processing the request: $message" -Category InvalidOperation
        }

        if ($state -ne $stateReturn.Split("=")[1]) {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Received invalid authentication result. Likely returned from another flow redirecting to the same local port!" -Category InvalidOperation
        }

        $actualAuthorizationCode = $authorizationCode.Split("=")[1]

        $body = @{
            client_id    = $ClientID
            scope        = $actualScopes -join " "
            code         = $actualAuthorizationCode
            redirect_uri = $redirectUri
            grant_type   = 'authorization_code'
        }
        $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
        try { $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body -ErrorAction Stop }
        catch {
            if ($_ -notmatch '"error":\s*"invalid_client"') { Invoke-TerminatingException -Cmdlet $PSCmdlet -ErrorRecord $_ }
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "The App Registration $ClientID has not been configured correctly. Ensure you have a 'Mobile and desktop applications' platform with redirect to 'http://localhost' configured (and not a 'Web' Platform). $_" -Category $_.CategoryInfo.Category
        }
        Read-AuthResponse -AuthResponse $authResponse
    }
}

function Connect-ServiceCertificate {
    <#
    .SYNOPSIS
        Connects to AAD using a application ID and a certificate.
     
    .DESCRIPTION
        Connects to AAD using a application ID and a certificate.
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
     
    .PARAMETER Certificate
        The certificate to use for authentication.
     
    .PARAMETER TenantID
        The ID of the tenant/directory to connect to.
     
    .PARAMETER ClientID
        The ID of the registered application used to authenticate as.
     
    .EXAMPLE
        PS C:\> Connect-ServiceCertificate -Certificate $cert -TenantID $tenantID -ClientID $clientID
     
        Connects to the specified tenant using the specified app & cert.
     
    .LINK
        https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Resource,

        [Parameter(Mandatory = $true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate,
        
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,
        
        [Parameter(Mandatory = $true)]
        [string]
        $ClientID
    )
    
    #region Build Signature Payload
    $jwtHeader = @{
        alg = "RS256"
        typ = "JWT"
        x5t = [Convert]::ToBase64String($Certificate.GetCertHash()) -replace '\+', '-' -replace '/', '_' -replace '='
    }
    $encodedHeader = $jwtHeader | ConvertTo-Json | ConvertTo-Base64
    $claims = @{
        aud = "https://login.microsoftonline.com/$TenantID/v2.0"
        exp = ((Get-Date).AddMinutes(5) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int]
        iss = $ClientID
        jti = "$(New-Guid)"
        nbf = ((Get-Date) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int]
        sub = $ClientID
    }
    $encodedClaims = $claims | ConvertTo-Json | ConvertTo-Base64
    $jwtPreliminary = $encodedHeader, $encodedClaims -join "."
    $jwtSigned = ($jwtPreliminary | ConvertTo-SignedString -Certificate $Certificate) -replace '\+', '-' -replace '/', '_' -replace '='
    $jwt = $jwtPreliminary, $jwtSigned -join '.'
    #endregion Build Signature Payload
    
    $body = @{
        client_id             = $ClientID
        client_assertion      = $jwt
        client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
        scope                 = '{0}/.default' -f $Resource
        grant_type            = 'client_credentials'
    }
    $header = @{
        Authorization = "Bearer $jwt"
    }
    $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
    
    try { $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Headers $header -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop }
    catch { throw }
    
    Read-AuthResponse -AuthResponse $authResponse
}

function Connect-ServiceClientSecret {
    <#
    .SYNOPSIS
        Connets using a client secret.
     
    .DESCRIPTION
        Connets using a client secret.
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
     
    .PARAMETER ClientID
        The ID of the registered app used with this authentication request.
     
    .PARAMETER TenantID
        The ID of the tenant connected to with this authentication request.
     
    .PARAMETER ClientSecret
        The actual secret used for authenticating the request.
     
    .EXAMPLE
        PS C:\> Connect-ServiceClientSecret -ClientID '<ClientID>' -TenantID '<TenantID>' -ClientSecret $secret
     
        Connects to the specified tenant using the specified client and secret.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Resource,

        [Parameter(Mandatory = $true)]
        [string]
        $ClientID,
        
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,
        
        [Parameter(Mandatory = $true)]
        [securestring]
        $ClientSecret
    )
    
    process {
        $body = @{
            resource      = $Resource
            client_id     = $ClientID
            client_secret = [PSCredential]::new('NoMatter', $ClientSecret).GetNetworkCredential().Password
            grant_type    = 'client_credentials'
        }
        try { $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body $body -ErrorAction Stop }
        catch { throw }
        
        Read-AuthResponse -AuthResponse $authResponse
    }
}

function Connect-ServiceDeviceCode {
    <#
    .SYNOPSIS
        Connects to Azure AD using the Device Code authentication workflow.
     
    .DESCRIPTION
        Connects to Azure AD using the Device Code authentication workflow.
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
 
    .PARAMETER ClientID
        The ID of the registered app used with this authentication request.
     
    .PARAMETER TenantID
        The ID of the tenant connected to with this authentication request.
     
    .PARAMETER Scopes
        The scopes to request.
        Automatically scoped to the service specified via Service Url.
        Defaults to ".Default"
     
    .EXAMPLE
        PS C:\> Connect-ServiceDeviceCode -ServiceUrl $url -ClientID '<ClientID>' -TenantID '<TenantID>'
     
        Connects to the specified tenant using the specified client, prompting the user to authorize via Browser.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Resource,

        [Parameter(Mandatory = $true)]
        [string]
        $ClientID,
        
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,
        
        [AllowEmptyCollection()]
        [string[]]
        $Scopes
    )

    if (-not $Scopes) { $Scopes = @('.default') }
    $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource

    try {
        $initialResponse = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/devicecode" -Body @{
            client_id = $ClientID
            scope     = @($actualScopes) + 'offline_access' -join " "
        } -ErrorAction Stop
    }
    catch {
        throw
    }

    Write-Host $initialResponse.message

    $paramRetrieve = @{
        Uri         = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
        Method      = "POST"
        Body        = @{
            grant_type  = "urn:ietf:params:oauth:grant-type:device_code"
            client_id   = $ClientID
            device_code = $initialResponse.device_code
        }
        ErrorAction = 'Stop'
    }
    $limit = (Get-Date).AddSeconds($initialResponse.expires_in)
    while ($true) {
        if ((Get-Date) -gt $limit) {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Timelimit exceeded, device code authentication failed" -Category AuthenticationError
        }
        Start-Sleep -Seconds $initialResponse.interval
        try { $authResponse = Invoke-RestMethod @paramRetrieve }
        catch {
            if ($_ -match '"error":\s*"authorization_pending"') { continue }
            $PSCmdlet.ThrowTerminatingError($_)
        }
        if ($authResponse) {
            break
        }
    }

    Read-AuthResponse -AuthResponse $authResponse
}

function Connect-ServicePassword {
    <#
    .SYNOPSIS
        Connect to graph using username and password.
     
    .DESCRIPTION
        Connect to graph using username and password.
        This logs into graph as a user, not as an application.
        Only cloud-only accounts can be used for this workflow.
        Consent to scopes must be granted before using them, as this command cannot show the consent prompt.
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
 
    .PARAMETER Credential
        Credentials of the user to connect as.
         
    .PARAMETER TenantID
        The Guid of the tenant to connect to.
 
    .PARAMETER ClientID
        The ClientID / ApplicationID of the application to use.
     
    .PARAMETER Scopes
        The permission scopes to request.
     
    .EXAMPLE
        PS C:\> Connect-ServicePassword -Credential max@contoso.com -ClientID $client -TenantID $tenant -Scopes 'user.read','user.readbasic.all'
         
        Connect as max@contoso.com with the rights to read user information.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Resource,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $Credential,
        
        [Parameter(Mandatory = $true)]
        [string]
        $ClientID,
        
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,
        
        [string[]]
        $Scopes = '.default'
    )

    $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource
    
    $request = @{
        client_id  = $ClientID
        scope      = $actualScopes -join " "
        username   = $Credential.UserName
        password   = $Credential.GetNetworkCredential().Password
        grant_type = 'password'
    }
    
    try { $authResponse = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Body $request -ErrorAction Stop }
    catch { throw }
    
    Read-AuthResponse -AuthResponse $authResponse
}

function Connect-ServiceRefreshToken {
    <#
    .SYNOPSIS
        Connect with the refresh token provided previously.
     
    .DESCRIPTION
        Connect with the refresh token provided previously.
        Used mostly for delegate authentication flows to avoid interactivity.
 
    .PARAMETER Token
        The DefenderToken object with the refresh token to use.
        The token is then refreshed in-place with no output provided.
     
    .EXAMPLE
        PS C:\> Connect-ServiceRefreshToken
         
        Connect with the refresh token provided previously.
    #>

    [CmdletBinding()]
    param (
        $Token
    )
    process {
        if (-not $Token.RefreshToken) {
            throw "Failed to refresh token: No refresh token found!"
        }

        $scopes = $Token.Scopes

        $body = @{
            client_id = $Token.ClientID
            scope = $scopes -join " "
            refresh_token = $Token.RefreshToken
            grant_type = 'refresh_token'
        }
        $uri = "https://login.microsoftonline.com/$($Token.TenantID)/oauth2/v2.0/token"
        $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body
        $Token.SetTokenMetadata((Read-AuthResponse -AuthResponse $authResponse))
    }
}

function Read-AuthResponse {
    <#
    .SYNOPSIS
        Produces a standard output representation of the authentication response received.
     
    .DESCRIPTION
        Produces a standard output representation of the authentication response received.
        This streamlines the token processing and simplifies the connection code.
     
    .PARAMETER AuthResponse
        The authentication response received.
     
    .EXAMPLE
        PS C:\> Read-AuthResponse -AuthResponse $authResponse
 
        Reads the authentication details received.
    #>

    [CmdletBinding()]
    param (
        $AuthResponse
    )
    process {
        if ($AuthResponse.expires_in) {
            $after = (Get-Date).AddMinutes(-5)
            $until = (Get-Date).AddSeconds($AuthResponse.expires_in)
        }
        else {
            $after = (Get-Date -Date '1970-01-01').AddSeconds($AuthResponse.not_before).ToLocalTime()
            $until = (Get-Date -Date '1970-01-01').AddSeconds($AuthResponse.expires_on).ToLocalTime()
        }
        $scopes = @()
        if ($AuthResponse.scope) { $scopes = $authResponse.scope -split " " }

        [pscustomobject]@{
            AccessToken  = $AuthResponse.access_token
            ValidAfter   = $after
            ValidUntil   = $until
            Scopes       = $scopes
            RefreshToken = $AuthResponse.refresh_token
        }
    }
}

function ConvertTo-Base64 {
<#
    .SYNOPSIS
        Converts the input-string to its base 64 encoded string form.
     
    .DESCRIPTION
        Converts the input-string to its base 64 encoded string form.
     
    .PARAMETER Text
        The text to convert.
     
    .PARAMETER Encoding
        The encoding of the input text.
        Used to correctly translate the input string into bytes before converting those to base 64.
        Defaults to UTF8
     
    .EXAMPLE
        PS C:\> Get-Content .\code.ps1 -Raw | ConvertTo-Base64
     
        Reads the input file and converts its content into base64.
#>

    [OutputType([string])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]
        $Text,
        
        [System.Text.Encoding]
        $Encoding = [System.Text.Encoding]::UTF8
    )
    
    process {
        foreach ($entry in $Text) {
            $bytes = $Encoding.GetBytes($entry)
            [Convert]::ToBase64String($bytes)
        }
    }
}

function ConvertTo-Hashtable {
    <#
    .SYNOPSIS
        Converts input objects into hashtables.
     
    .DESCRIPTION
        Converts input objects into hashtables.
        Allows explicitly including some properties only and remapping key-names as required.
     
    .PARAMETER Include
        Only select the specified properties.
     
    .PARAMETER Mapping
        Remap hashtable/property keys.
        This allows you to rename parameters before passing them through to other commands.
        Example:
        @{ Select = '$select' }
        This will map the "Select"-property/key on the input object to be '$select' on the output item.
     
    .PARAMETER InputObject
        The object to convert.
     
    .EXAMPLE
        PS C:\> $__body = $PSBoundParameters | ConvertTo-Hashtable -Include Name, UserID -Mapping $__mapping
 
        Converts the object $PSBoundParameters into a hashtable, including the keys "Name" and "UserID" and remapping them as specified in $__mapping
    #>

    [OutputType([hashtable])]
    [CmdletBinding()]
    param (
        [AllowEmptyCollection()]
        [string[]]
        $Include,

        [Hashtable]
        $Mapping = @{ },

        [Parameter(ValueFromPipeline = $true)]
        $InputObject
    )

    process {
        $result = @{ }
        if ($InputObject -is [System.Collections.IDictionary]) {
            foreach ($pair in $InputObject.GetEnumerator()) {
                if ($pair.Key -notin $Include) { continue }
                if ($Mapping[$pair.Key]) { $result[$Mapping[$pair.Key]] = $pair.Value }
                else { $result[$pair.Key] = $pair.Value }
            }
        }
        else {
            foreach ($property in $InputObject.PSObject.Properties) {
                if ($property.Name -notin $Include) { continue }
                if ($Mapping[$property.Name]) { $result[$Mapping[$property.Name]] = $property.Value }
                else { $result[$property.Name] = $property.Value }
            }
        }
        $result
    }
}

function ConvertTo-QueryString {
    <#
    .SYNOPSIS
        Convert conditions in a hashtable to a Query string to append to a webrequest.
     
    .DESCRIPTION
        Convert conditions in a hashtable to a Query string to append to a webrequest.
     
    .PARAMETER QueryHash
        Hashtable of query modifiers - usually filter conditions - to include in a web request.
     
    .EXAMPLE
        PS C:\> ConvertTo-QueryString -QueryHash $Query
 
        Converts the conditions in the specified hashtable to a Query string to append to a webrequest.
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Hashtable]
        $QueryHash
    )

    process {
        $elements = foreach ($pair in $QueryHash.GetEnumerator()) {
            '{0}={1}' -f $pair.Name, ($pair.Value -join ",")
        }
        '?{0}' -f ($elements -join '&')
    }
}

function ConvertTo-SignedString {
<#
    .SYNOPSIS
        Signs input string with the offered certificate.
     
    .DESCRIPTION
        Signs input string with the offered certificate.
     
    .PARAMETER Text
        The text to sign.
     
    .PARAMETER Certificate
        The certificate to sign with.
        The Private Key must be available.
     
    .PARAMETER Padding
        What RSA Signature padding to use.
        Defaults to Pkcs1
     
    .PARAMETER Algorithm
        What algorithm to use for signing.
        Defaults to SHA256
     
    .PARAMETER Encoding
        The encoding to use for transforming the text to bytes before signing it.
        Defaults to UTF8
     
    .EXAMPLE
        PS C:\> ConvertTo-SignedString -Text $token
     
        Signs the specified token
#>

    [OutputType([string])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]
        $Text,
        
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate,
        
        [Security.Cryptography.RSASignaturePadding]
        $Padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1,
        
        [Security.Cryptography.HashAlgorithmName]
        $Algorithm = [Security.Cryptography.HashAlgorithmName]::SHA256,
        
        [System.Text.Encoding]
        $Encoding = [System.Text.Encoding]::UTF8
    )
    
    begin {
        $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)
    }
    process {
        foreach ($entry in $Text) {
            $inBytes = $Encoding.GetBytes($entry)
            $outBytes = $privateKey.SignData($inBytes, $Algorithm, $Padding)
            [convert]::ToBase64String($outBytes)
        }
    }
}

function Invoke-TerminatingException
{
<#
    .SYNOPSIS
        Throw a terminating exception in the context of the caller.
     
    .DESCRIPTION
        Throw a terminating exception in the context of the caller.
        Masks the actual code location from the end user in how the message will be displayed.
     
    .PARAMETER Cmdlet
        The $PSCmdlet variable of the calling command.
     
    .PARAMETER Message
        The message to show the user.
     
    .PARAMETER Exception
        A nested exception to include in the exception object.
     
    .PARAMETER Category
        The category of the error.
     
    .PARAMETER ErrorRecord
        A full error record that was caught by the caller.
        Use this when you want to rethrow an existing error.
     
    .EXAMPLE
        PS C:\> Invoke-TerminatingException -Cmdlet $PSCmdlet -Message 'Unknown calling module'
     
        Terminates the calling command, citing an unknown caller.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        $Cmdlet,
        
        [string]
        $Message,
        
        [System.Exception]
        $Exception,
        
        [System.Management.Automation.ErrorCategory]
        $Category = [System.Management.Automation.ErrorCategory]::NotSpecified,
        
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord
    )
    
    process{
        if ($ErrorRecord -and -not $Message) {
            $Cmdlet.ThrowTerminatingError($ErrorRecord)
        }
        
        $exceptionType = switch ($Category) {
            default { [System.Exception] }
            'InvalidArgument' { [System.ArgumentException] }
            'InvalidData' { [System.IO.InvalidDataException] }
            'AuthenticationError' { [System.Security.Authentication.AuthenticationException] }
            'InvalidOperation' { [System.InvalidOperationException] }
        }
        
        
        if ($Exception) { $newException = $Exception.GetType()::new($Message, $Exception) }
        elseif ($ErrorRecord) { $newException = $ErrorRecord.Exception.GetType()::new($Message, $ErrorRecord.Exception) }
        else { $newException = $exceptionType::new($Message) }
        $record = [System.Management.Automation.ErrorRecord]::new($newException, (Get-PSCallStack)[1].FunctionName, $Category, $Target)
        $Cmdlet.ThrowTerminatingError($record)
    }
}

function Resolve-Certificate {
    <#
    .SYNOPSIS
        Helper function to resolve certificate input.
     
    .DESCRIPTION
        Helper function to resolve certificate input.
        This function expects the full $PSBoundParameters from the calling command and will (in this order) look for these parameter names:
 
        + Certificate: A full X509Certificate2 object with private key
        + CertificateThumbprint: The thumbprint of a certificate to use. Will look first in the user store, then the machine store for it.
        + CertificateName: The subject of the certificate to look for. Will look first in the user store, then the machine store for it. Will select the certificate with the longest expiration period.
        + CertificatePath: Path to a PFX file to load. Also expects a CertificatePassword parameter to unlock the file.
     
    .PARAMETER BoundParameters
        The $PSBoundParameter variable of the caller to simplify passthrough.
        See Description for more details on what the command expects,
     
    .EXAMPLE
        PS C:\> $certificateObject = Resolve-Certificate -BoundParameters $PSBoundParameters
 
        Resolves the certificate based on the parameters provided to the calling command.
    #>

    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    [CmdletBinding()]
    param (
        $BoundParameters
    )
    
    if ($BoundParameters.Certificate) { return $BoundParameters.Certificate }
    if ($BoundParameters.CertificateThumbprint) {
        if (Test-Path -Path "cert:\CurrentUser\My\$($BoundParameters.CertificateThumbprint)") {
            return Get-Item "cert:\CurrentUser\My\$($BoundParameters.CertificateThumbprint)"
        }
        if (Test-Path -Path "cert:\LocalMachine\My\$($BoundParameters.CertificateThumbprint)") {
            return Get-Item "cert:\LocalMachine\My\$($BoundParameters.CertificateThumbprint)"
        }
        Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to find certificate with thumbprint '$($BoundParameters.CertificateThumbprint)'"
    }
    if ($BoundParameters.CertificateName) {
        if ($certificate = (Get-ChildItem 'Cert:\CurrentUser\My\').Where{ $_.Subject -eq $BoundParameters.CertificateName -and $_.HasPrivateKey }) {
            return $certificate | Sort-Object NotAfter -Descending | Select-Object -First 1
        }
        if ($certificate = (Get-ChildItem 'Cert:\LocalMachine\My\').Where{ $_.Subject -eq $BoundParameters.CertificateName -and $_.HasPrivateKey }) {
            return $certificate | Sort-Object NotAfter -Descending | Select-Object -First 1
        }
        Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to find certificate with subject '$($BoundParameters.CertificateName)'"
    }
    if ($BoundParameters.CertificatePath) {
        try { [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($BoundParameters.CertificatePath, $BoundParameters.CertificatePassword) }
        catch {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to load certificate from file '$($BoundParameters.CertificatePath)': $_" -ErrorRecord $_
        }
    }
}

function Resolve-ScopeName {
    <#
    .SYNOPSIS
        Normalizes scope names.
     
    .DESCRIPTION
        Normalizes scope names.
        To help manage correct scopes naming with services that don't map directly to their urls.
     
    .PARAMETER Scopes
        The scopes to normalize.
     
    .PARAMETER Resource
        The Resource the scopes are meant for.
     
    .EXAMPLE
        PS C:\> $scopes | Resolve-ScopeName -Resource $Resource
         
        Resolves all them scopes
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $Scopes,

        [Parameter(Mandatory = $true)]
        [string]
        $Resource
    )
    process {
        foreach ($scope in $Scopes) {
            foreach ($scope in $Scopes) {
                if ($scope -like 'https://*/*') { $scope }
                elseif ($scope -like 'api:/') { $scope }
                else { "{0}/{1}" -f $Resource, $scope }
            }
        }
    }
}

function Assert-DefenderAPIConnection
{
<#
    .SYNOPSIS
        Asserts a connection has been established.
     
    .DESCRIPTION
        Asserts a connection has been established.
        Fails the calling command in a terminating exception if not connected yet.
         
    .PARAMETER Service
        The service to which a connection needs to be established.
     
    .PARAMETER Cmdlet
        The $PSCmdlet variable of the calling command.
        Used to execute the terminating exception in the caller scope if needed.
 
    .PARAMETER RequiredScopes
        Scopes needed, for better error messages.
     
    .EXAMPLE
        PS C:\> Assert-DefenderAPIConnection -Service 'Endpoint' -Cmdlet $PSCmdlet
     
        Silently does nothing if already connected to the specified defender service.
        Kills the calling command if not yet connected.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Service,
        
        [Parameter(Mandatory = $true)]
        $Cmdlet,
        
        [AllowEmptyCollection()]
        [string[]]
        $RequiredScopes
    )
    
    process
    {
        if ($script:_DefenderTokens["$Service"]) { return }
        
        $message = "Not connected yet! Use Connect-DefenderAPIService to establish a connection to '$Service' first."
        if ($RequiredScopes) { $message = $message + " Scopes required for this call: $($RequiredScopes -join ', ')"}
        Invoke-TerminatingException -Cmdlet $Cmdlet -Message  -Category ConnectionError
    }
}

function Connect-DefenderAPI {
    <#
    .SYNOPSIS
        Establish a connection to the defender APIs.
     
    .DESCRIPTION
        Establish a connection to the defender APIs.
        Prerequisite before executing any requests / commands.
     
    .PARAMETER ClientID
        ID of the registered/enterprise application used for authentication.
     
    .PARAMETER TenantID
        The ID of the tenant/directory to connect to.
     
    .PARAMETER Scopes
        Any scopes to include in the request.
        Only used for interactive/delegate workflows, ignored for Certificate based authentication or when using Client Secrets.
 
    .PARAMETER Browser
        Use an interactive logon in your default browser.
        This is the default logon experience.
 
    .PARAMETER DeviceCode
        Use the Device Code delegate authentication flow.
        This will prompt the user to complete login via browser.
     
    .PARAMETER Certificate
        The Certificate object used to authenticate with.
         
        Part of the Application Certificate authentication workflow.
     
    .PARAMETER CertificateThumbprint
        Thumbprint of the certificate to authenticate with.
        The certificate must be stored either in the user or computer certificate store.
         
        Part of the Application Certificate authentication workflow.
     
    .PARAMETER CertificateName
        The name/subject of the certificate to authenticate with.
        The certificate must be stored either in the user or computer certificate store.
        The newest certificate with a private key will be chosen.
         
        Part of the Application Certificate authentication workflow.
     
    .PARAMETER CertificatePath
        Path to a PFX file containing the certificate to authenticate with.
         
        Part of the Application Certificate authentication workflow.
     
    .PARAMETER CertificatePassword
        Password to use to read a PFX certificate file.
        Only used together with -CertificatePath.
         
        Part of the Application Certificate authentication workflow.
     
    .PARAMETER ClientSecret
        The client secret configured in the registered/enterprise application.
         
        Part of the Client Secret Certificate authentication workflow.
 
    .PARAMETER Credential
        The username / password to authenticate with.
 
        Part of the Resource Owner Password Credential (ROPC) workflow.
 
    .PARAMETER Service
        The service to connect to.
        Individual commands using Invoke-MdeRequest specify the service to use and thus identify the token needed.
        Defaults to: Endpoint
 
    .PARAMETER ServiceUrl
        The base url to the service connecting to.
        Used for authentication, scopes and executing requests.
        Defaults to: https://api.securitycenter.microsoft.com/api
     
    .EXAMPLE
        PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID
     
        Establish a connection to the defender for endpoint API, prompting the user for login on their default browser.
     
    .EXAMPLE
        PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -Certificate $cert
     
        Establish a connection to the defender APIs using the provided certificate.
     
    .EXAMPLE
        PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -CertificatePath C:\secrets\certs\mde.pfx -CertificatePassword (Read-Host -AsSecureString)
     
        Establish a connection to the defender APIs using the provided certificate file.
        Prompts you to enter the certificate-file's password first.
     
    .EXAMPLE
        PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -ClientSecret $secret
     
        Establish a connection to the defender APIs using a client secret.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [CmdletBinding(DefaultParameterSetName = 'Browser')]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $ClientID,
        
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,
        
        [string[]]
        $Scopes,

        [Parameter(ParameterSetName = 'Browser')]
        [switch]
        $Browser,

        [Parameter(ParameterSetName = 'DeviceCode')]
        [switch]
        $DeviceCode,
        
        [Parameter(ParameterSetName = 'AppCertificate')]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate,
        
        [Parameter(ParameterSetName = 'AppCertificate')]
        [string]
        $CertificateThumbprint,
        
        [Parameter(ParameterSetName = 'AppCertificate')]
        [string]
        $CertificateName,
        
        [Parameter(ParameterSetName = 'AppCertificate')]
        [string]
        $CertificatePath,
        
        [Parameter(ParameterSetName = 'AppCertificate')]
        [System.Security.SecureString]
        $CertificatePassword,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'AppSecret')]
        [System.Security.SecureString]
        $ClientSecret,

        [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')]
        [PSCredential]
        $Credential,

        [PsfArgumentCompleter('DefenderAPI.Service')]
        [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')]
        [string[]]
        $Service = 'Endpoint',

        [string]
        $ServiceUrl
    )
    process {
        foreach ($serviceName in $Service) {
            $serviceObject = Get-DefenderAPIService -Name $serviceName

            $commonParam = @{
                ClientID = $ClientID
                TenantID = $TenantID
                Resource = $serviceObject.Resource
            }
            $effectiveServiceUrl = $ServiceUrl
            if (-not $ServiceUrl) { $effectiveServiceUrl = $serviceObject.ServiceUrl }
            
            #region Connection
            switch ($PSCmdlet.ParameterSetName) {
                #region Browser
                Browser {
                    $scopesToUse = $Scopes
                    if (-not $Scopes) { $scopesToUse = $serviceObject.DefaultScopes }

                    Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.Browser' -ActionStringValues $serviceName -ScriptBlock {
                        $result = Connect-ServiceBrowser @commonParam -SelectAccount -Scopes $scopesToUse -ErrorAction Stop
                    } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet

                    $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $effectiveServiceUrl, $false)
                    if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() }
                    $token.SetTokenMetadata($result)
                    $script:_DefenderTokens[$serviceName] = $token
                }
                #endregion Browser

                #region DeviceCode
                DeviceCode {
                    $scopesToUse = $Scopes
                    if (-not $Scopes) { $scopesToUse = $serviceObject.DefaultScopes }

                    Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.DeviceCode' -ActionStringValues $serviceName -ScriptBlock {
                        $result = Connect-ServiceDeviceCode @commonParam -Scopes $scopesToUse -ErrorAction Stop
                    } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet

                    $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $effectiveServiceUrl, $true)
                    if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() }
                    $token.SetTokenMetadata($result)
                    $script:_DefenderTokens[$serviceName] = $token
                }
                #endregion DeviceCode

                #region ROPC
                UsernamePassword {
                    Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.ROPC' -ActionStringValues $serviceName -ScriptBlock {
                        $result = Connect-ServicePassword @commonParam -Credential $Credential -ErrorAction Stop
                    } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet

                    $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $Credential, $effectiveServiceUrl)
                    if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() }
                    $token.SetTokenMetadata($result)
                    $script:_DefenderTokens[$serviceName] = $token
                }
                #endregion ROPC

                #region AppSecret
                AppSecret {
                    Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.ClientSecret' -ActionStringValues $serviceName -ScriptBlock {
                        $result = Connect-ServiceClientSecret @commonParam -ClientSecret $ClientSecret -ErrorAction Stop
                    } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet

                    $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $ClientSecret, $effectiveServiceUrl)
                    if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() }
                    $token.SetTokenMetadata($result)
                    $script:_DefenderTokens[$serviceName] = $token
                }
                #endregion AppSecret

                #region AppCertificate
                AppCertificate {
                    try { $certificateObject = Resolve-Certificate -BoundParameters $PSBoundParameters }
                    catch {
                        Stop-PSFFunction -String 'Connect-DefenderAPI.Error.CertError' -StringValues $serviceName -Tag connect, fail -ErrorRecord $_ -EnableException $true -Cmdlet $PSCmdlet -Target $serviceName
                    }
    
                    Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.Certificate' -ActionStringValues $serviceName, $certificateObject.Subject, $certificateObject.Thumbprint -ScriptBlock {
                        $result = Connect-ServiceCertificate @commonParam -Certificate $certificateObject -ErrorAction Stop
                    } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet

                    $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $certificateObject, $effectiveServiceUrl)
                    if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() }
                    $token.SetTokenMetadata($result)
                    $script:_DefenderTokens[$serviceName] = $token
                }
                #endregion AppCertificate
            }
            #endregion Connection
        }
    }
}

function Get-DefenderAPIService {
    <#
    .SYNOPSIS
        Returns the list of available defender API services that can be connected to.
     
    .DESCRIPTION
        Returns the list of available defender API services that can be connected to.
        Includes for each the endpoint/service url and the default requested scopes.
     
    .PARAMETER Name
        Name of the service to return.
        Defaults to: *
     
    .EXAMPLE
        PS C:\> Get-DefenderAPIService
 
        List all available services.
    #>

    [CmdletBinding()]
    param (
        [PsfArgumentCompleter('DefenderAPI.Service')]
        [string]
        $Name = '*'
    )
    process {
        $script:_DefenderEndpoints.Values | Where-Object Name -like $Name
    }
}

function Get-DefenderAPIToken {
    <#
    .SYNOPSIS
        Returns the session token of a defender API connection.
     
    .DESCRIPTION
        Returns the session token of a defender API connection.
        The main use for those token objects is calling their "GetHeader()" method to get an authentication header
        that automatically refreshes tokens as needed.
     
    .PARAMETER Service
        The service for which to retrieve the token.
        Defaults to: *
     
    .EXAMPLE
        PS C:\> Get-DefenderAPIToken
         
        Returns all current session tokens
    #>

    
    [CmdletBinding()]
    param (
        [PsfArgumentCompleter('DefenderAPI.Service')]
        [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')]
        [string]
        $Service = '*'
    )
    process {
        $script:_DefenderTokens.Values | Where-Object Service -like $Service
    }
}

function Register-DefenderAPIService {
    <#
    .SYNOPSIS
        Define a new Defender API Service to connect to.
     
    .DESCRIPTION
        Define a new Defender API Service to connect to.
        This allows defining new endpoints to connect to ... or overriding existing endpoints to a different configuration.
     
    .PARAMETER Name
        Name of the Service.
     
    .PARAMETER ServiceUrl
        The base Url requests will use.
     
    .PARAMETER Resource
        The Resource ID. Used when connecting to identify which scopes of an App Registration to use.
     
    .PARAMETER DefaultScopes
        Default scopes to request.
        Used in interactive delegate flows to provide a good default user experience.
        Default scopes should usually include common read scenarios.
 
    .PARAMETER Header
        Header data to include in each request.
     
    .PARAMETER HelpUrl
        Link for more information about this service.
        Ideally to documentation that helps setting up the connection.
     
    .EXAMPLE
        PS C:\> Register-DefenderAPIService -Name Endpoint -ServiceUrl 'https://api.securitycenter.microsoft.com/api' -Resource 'https://api.securitycenter.microsoft.com'
         
        Registers the defender for endpoint API as a service.
    #>

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

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

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

        [AllowEmptyCollection()]
        [string[]]
        $DefaultScopes = @(),

        [hashtable]
        $Header = @{},

        [string]
        $HelpUrl
    )
    process {
        $script:_DefenderEndpoints[$Name] = [PSCustomObject]@{
            PSTypeName    = 'DefenderAPI.Service'
            Name          = $Name
            ServiceUrl    = $ServiceUrl
            Resource      = $Resource
            DefaultScopes = $DefaultScopes
            Header        = $Header
            HelpUrl       = $HelpUrl
        }
    }
}

function Set-DefenderAPIService {
    <#
    .SYNOPSIS
        Modify the settings on an existing Service configuration.
     
    .DESCRIPTION
        Modify the settings on an existing Service configuration.
        Service configurations are defined using Register-DefenderAPIService and define how connections and requests to a specific API service / endpoint are performed.
     
    .PARAMETER Name
        The name of the already existing Service configuration.
     
    .PARAMETER ServiceUrl
        The base Url requests will use.
     
    .PARAMETER Resource
        The Resource ID. Used when connecting to identify which scopes of an App Registration to use.
     
    .PARAMETER DefaultScopes
        Default scopes to request.
        Used in interactive delegate flows to provide a good default user experience.
        Default scopes should usually include common read scenarios.
 
    .PARAMETER Header
        Header data to include in each request.
     
    .PARAMETER HelpUrl
        Link for more information about this service.
        Ideally to documentation that helps setting up the connection.
     
    .EXAMPLE
        PS C:\> Set-DefenderAPIService -Name Endpoint -ServiceUrl 'https://api-us.securitycenter.microsoft.com/api'
 
        Changes the service url for the "Endpoint" service to 'https://api-us.securitycenter.microsoft.com/api'.
        Note: It is generally recommened to select the service url most suitable for your tenant, geographically:
        https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/exposed-apis-list?view=o365-worldwide#versioning
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PsfArgumentCompleter('DefenderAPI.Service')]
        [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')]
        [string]
        $Name,

        [string]
        $ServiceUrl,

        [string]
        $Resource,

        [AllowEmptyCollection()]
        [string[]]
        $DefaultScopes,

        [Hashtable]
        $Header,

        [string]
        $HelpUrl
    )
    process {
        $service = $script:_DefenderEndpoints.$Name
        if ($PSBoundParameters.Keys -contains 'ServiceUrl') { $service.ServiceUrl = $ServiceUrl }
        if ($PSBoundParameters.Keys -contains 'Resource') { $service.Resource = $Resource }
        if ($PSBoundParameters.Keys -contains 'DefaultScopes') { $service.DefaultScopes = $DefaultScopes }
        if ($PSBoundParameters.Keys -contains 'Header') { $service.Header = $Header }
        if ($PSBoundParameters.Keys -contains 'HelpUrl') { $service.HelpUrl = $HelpUrl }
    }
}

function Invoke-DefenderAPIRequest
{
<#
    .SYNOPSIS
        Executes a web request against a defender endpoint.
     
    .DESCRIPTION
        Executes a web request against a defender endpoint.
        Handles all the authentication details once connected using Connect-DefenderAPIService.
     
    .PARAMETER Path
        The relative path of the endpoint to query.
        For example, to retrieve defender for endpoint alerts, it would be a plain "alerts".
        To access details on a particular machine instead it would look thus: "machines/1e5bc9d7e413ddd7902c2932e418702b84d0cc07"
     
    .PARAMETER Body
        Any body content needed for the request.
 
    .PARAMETER Query
        Any query content to include in the request.
        In opposite to -Body this is attached to the request Url and usually used for filtering.
     
    .PARAMETER Method
        The Rest Method to use.
        Defaults to GET
     
    .PARAMETER RequiredScopes
        Any authentication scopes needed.
        Used for documentary purposes only.
 
    .PARAMETER Header
        Any additional headers to include on top of authentication and content-type.
     
    .PARAMETER Service
        Which service to execute against.
        Determines the API endpoint called to.
        Defaults to "Endpoint"
 
    .PARAMETER SerializationDepth
        How deeply to serialize the request body when converting it to json.
        Defaults to the value in the 'DefenderAPI.Request.SerializationDepth' configuration setting.
        This in turn defaults to "99"
     
    .EXAMPLE
        PS C:\> Invoke-DefenderAPIRequest -Path 'alerts' -RequiredScopes 'Alert.Read'
     
        Return a list of defender alerts.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Path,
        
        [Hashtable]
        $Body = @{ },

        [Hashtable]
        $Query = @{ },
        
        [string]
        $Method = 'GET',
        
        [string[]]
        $RequiredScopes,

        [hashtable]
        $Header = @{},
        
        [PsfArgumentCompleter('DefenderAPI.Service')]
        [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')]
        [string]
        $Service = 'Endpoint',

        [ValidateRange(1,666)]
        [int]
        $SerializationDepth = (Get-PSFConfigValue -FullName 'DefenderAPI.Request.SerializationDepth' -Fallback 99)
    )
    
    begin{
        Assert-DefenderAPIConnection -Service $Service -Cmdlet $PSCmdlet -RequiredScopes $RequiredScopes
        $token = $script:_DefenderTokens.$Service
    }
    process
    {
        $parameters = @{
            Method = $Method
            Uri = "$($token.ServiceUrl.Trim("/"))/$($Path.TrimStart('/'))"
        }
        if ($Body.Count -gt 0) {
            $parameters.Body = $Body | ConvertTo-Json -Compress -Depth $SerializationDepth
        }
        if ($Query.Count -gt 0) {
            $parameters.Uri += ConvertTo-QueryString -QueryHash $Query
        }

        while ($parameters.Uri) {
            $parameters.Headers = $token.GetHeader() + $Header # GetHeader() automatically refreshes expried tokens
            Write-PSFMessage -Level Debug -String 'Invoke-DefenderAPIRequest.Request' -StringValues $Method, $parameters.Uri
            try { $result = Invoke-RestMethod @parameters -ErrorAction Stop }
            catch {
                $letItBurn = $true
                $failure = $_

                if ($_.ErrorDetails.Message) {
                    $details = $_.ErrorDetails.Message | ConvertFrom-Json
                    if ($details.Error.Code -eq 'TooManyRequests') {
                        Write-PSFMessage -Level Verbose -Message $details.error.message
                        $delay = 1 + ($details.error.message -replace '^.+ (\d+) .+$','$1' -as [int])
                        if ($delay -gt 5) { Write-PSFMessage -Level Warning -String 'Invoke-DefenderAPIRequest.Query.Throttling' -StringValues $delay }
                        Start-Sleep -Seconds $delay
                        try {
                            $result = Invoke-RestMethod @parameters -ErrorAction Stop
                            $letItBurn = $false
                        }
                        catch {
                            $failure = $_
                        }
                    }
                }

                if ($letItBurn) {
                    Stop-PSFFunction -String 'Invoke-DefenderAPIRequest.Error.QueryFailed' -StringValues $Method, $Path -ErrorRecord $failure -EnableException $true -Cmdlet $PSCmdlet
                }
            }
            if ($result.PSObject.Properties.Where{ $_.Name -eq 'value' }) { $result.Value }
            else { $result }
            $parameters.Uri = $result.'@odata.nextLink'
        }
    }
}

function Invoke-MdAdvancedQuery {
<#
.SYNOPSIS
    Advanced Hunting
 
.DESCRIPTION
    Run a custom query in Windows Defender ATP
 
    Scopes required (delegate auth): AdvancedQuery.Read
 
.PARAMETER Query
    The query to run
 
.EXAMPLE
    PS C:\> Invoke-MdAdvancedQuery -Query $query
 
    Run a custom query in Windows Defender ATP
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-advanced-query-api?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Query
    )
    process {
        $__mapping = @{
            'Query' = 'Query'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'advancedqueries/run'
            Method = 'post'
            RequiredScopes = 'AdvancedQuery.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param | ConvertFrom-AdvancedQuery }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdAdvancedQuerySchema {
<#
.SYNOPSIS
    Advanced Hunting Schema
 
.DESCRIPTION
    Gets the schema for a Windows Defender ATP custom query
 
.PARAMETER Query
    The query to run
 
.EXAMPLE
    PS C:\> Set-MdAdvancedQuerySchema -Query $query
 
    Gets the schema for a Windows Defender ATP custom query
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Query
    )
    process {
        $__mapping = @{
            'Query' = 'Query'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'advancedqueries/schema'
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdAlert {
<#
.SYNOPSIS
    Alerts - Get list of alerts
 
.DESCRIPTION
    Retrieve from Windows Defender ATP the most recent alerts
 
    Scopes required (delegate auth): Alert.Read
 
.PARAMETER Top
    Returns only the first n results.
 
.PARAMETER AlertID
    The identifier of the alert to retrieve
 
.PARAMETER Orderby
    Sorts the results.
 
.PARAMETER Select
    Selects which properties to include in the response, defaults to all.
 
.PARAMETER Filter
    Filters the results, using OData syntax.
 
.PARAMETER Expand
    Expands related entities inline.
 
.PARAMETER Skip
    Skips the first n results.
 
.PARAMETER Count
    Includes a count of the matching results in the response.
 
.EXAMPLE
    PS C:\> Get-MdAlert
 
    Retrieve from Windows Defender ATP the most recent alerts
 
.EXAMPLE
    PS C:\> Get-MdAlert -AlertID $alertid
 
    Retrieve from Windows Defender ATP a specific alert
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-alerts?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Top,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleAlert')]
        [string]
        $AlertID,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Orderby,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string[]]
        $Select,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Filter,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Expand,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Skip,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [boolean]
        $Count
    )
    process {
        $__mapping = @{
            'Top' = '$top'
            'Orderby' = '$orderby'
            'Select' = '$select'
            'Filter' = '$filter'
            'Expand' = '$expand'
            'Skip' = '$skip'
            'Count' = '$count'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Filter','Expand','Skip','Count') -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'alerts'
            Method = 'get'
            RequiredScopes = 'Alert.Read'
            
        }
        if ($AlertID) { $__param.Path += "/$AlertID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function New-MdAlert {
<#
.SYNOPSIS
    Alerts - Create alert
 
.DESCRIPTION
    Create Alert based on specific Event
 
    Scopes required (delegate auth): Alert.ReadWrite
 
.PARAMETER EventTime
    Time of the event as string, e.g. 2018-08-03T16:45:21.7115183Z
 
.PARAMETER MachineID
    ID of the machine on which the event was identified
 
.PARAMETER Title
    Title of the Alert
 
.PARAMETER Severity
    Severity of the alert.
 
.PARAMETER Description
    Description of the Alert
 
.PARAMETER RecommendedAction
    Recommended action for the Alert
 
.PARAMETER Category
    Category of the alert
 
.PARAMETER ReportID
    Report Id of the event
 
.EXAMPLE
    PS C:\> New-MdAlert -Title $title -Severity $severity -Description $description -Category $category
 
    Create Alert based on specific Event
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/create-alert-by-reference?view=o365-worldwide
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $EventTime,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $MachineID,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Title,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Severity,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Description,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $RecommendedAction,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Category,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $ReportID
    )
    process {
        $__mapping = @{
            'EventTime' = 'Event Time'
            'MachineID' = 'Machine ID'
            'Title' = 'Title'
            'Severity' = 'Severity'
            'Description' = 'Description'
            'RecommendedAction' = 'Recommended Action'
            'Category' = 'Category'
            'ReportID' = 'Report ID'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('EventTime','MachineID','Title','Severity','Description','RecommendedAction','Category','ReportID') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'alerts/createAlertByReference'
            Method = 'post'
            RequiredScopes = 'Alert.ReadWrite'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdAlert {
<#
.SYNOPSIS
    Alerts - Update alert
 
.DESCRIPTION
    Update a Windows Defender ATP alert
 
    Scopes required (delegate auth): Alert.ReadWrite
 
.PARAMETER AlertID
    The identifier of the alert to update
 
.PARAMETER Comment
    A comment to associate to the alert
 
.PARAMETER Classification
    Classification of the alert. One of 'Unknown', 'FalsePositive', 'TruePositive'
 
.PARAMETER Status
    Status of the alert. One of 'New', 'InProgress' and 'Resolved'
 
.PARAMETER Determination
    The determination of the alert. One of 'NotAvailable', 'Apt', 'Malware', 'SecurityPersonnel', 'SecurityTesting', 'UnwantedSoftware', 'Other'
 
.PARAMETER AssignedTo
    Person to assign the alert to
 
.PARAMETER Confirm
    If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
.PARAMETER WhatIf
    If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
.EXAMPLE
    PS C:\> Set-MdAlert -AlertID $alertid
 
    Update a Windows Defender ATP alert
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/update-alert?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default', SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $AlertID,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Classification,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Status,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Determination,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $AssignedTo
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
            'Classification' = 'Classification'
            'Status' = 'Status'
            'Determination' = 'Determination'
            'AssignedTo' = 'Assigned to'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment','Classification','Status','Determination','AssignedTo') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'alerts/{AlertID}' -Replace '{AlertID}',$AlertID
            Method = 'patch'
            RequiredScopes = 'Alert.ReadWrite'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'
        if (-not $PSCmdlet.ShouldProcess("$AlertID","Update existing Alert")) { return }
        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdDeviceSecureScore {
<#
.SYNOPSIS
    Retrieves your Microsoft Secure Score for Devices
 
.DESCRIPTION
    Retrieves your Microsoft Secure Score for Devices. A higher Microsoft Secure Score for Devices means your endpoints are more resilient from cybersecurity threat attacks.
 
    Scopes required (delegate auth): Score.Read
 
 
 
.EXAMPLE
    PS C:\> Get-MdDeviceSecureScore
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-device-secure-score?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (

    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'configurationScore'
            Method = 'get'
            RequiredScopes = 'Score.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdExposureScore {
<#
.SYNOPSIS
    Retrieves the organizational exposure score.
 
.DESCRIPTION
    Retrieves the organizational exposure score.
 
    Scopes required (delegate auth): Score.Read
 
 
 
.EXAMPLE
    PS C:\> Get-MdExposureScore
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-exposure-score?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (

    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'exposureScore'
            Method = 'get'
            RequiredScopes = 'Score.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineGroupExposureScore {
<#
.SYNOPSIS
    Retrieves a collection of alerts related to a given domain address.
 
.DESCRIPTION
    Retrieves a collection of alerts related to a given domain address.
 
    Scopes required (delegate auth): Score.Read
 
 
 
.EXAMPLE
    PS C:\> Get-MdMachineGroupExposureScore
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machine-group-exposure-score?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (

    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'exposureScore/byMachineGroups'
            Method = 'get'
            RequiredScopes = 'Score.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdFile {
<#
.SYNOPSIS
    Files - Get a single file
 
.DESCRIPTION
    Retrieve from Windows Defender ATP a specific file by identifier Sha1, or Sha256
 
.PARAMETER FileID
    The file identifier - Sha1, or Sha256
 
.EXAMPLE
    PS C:\> Get-MdFile -FileID $fileid
 
    Retrieve from Windows Defender ATP a specific file by identifier Sha1, or Sha256
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $FileID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'files/{FileID}' -Replace '{FileID}',$FileID
            Method = 'get'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdFileAlert {
<#
.SYNOPSIS
    Files - Get alerts related to a file
 
.DESCRIPTION
    Retrieve from Windows Defender ATP a collection of alerts related to a given file by identifier Sha1, or Sha256
 
.PARAMETER FileID
    The file identifier - Sha1, or Sha256
 
.EXAMPLE
    PS C:\> Get-MdFileAlert -FileID $fileid
 
    Retrieve from Windows Defender ATP a collection of alerts related to a given file by identifier Sha1, or Sha256
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $FileID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'files/{FileID}/alerts' -Replace '{FileID}',$FileID
            Method = 'get'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdFileMachine {
<#
.SYNOPSIS
    Files - Get machines related to a file
 
.DESCRIPTION
    Retrieve from Windows Defender ATP a collection of machines related to a given file by identifier Sha1, or Sha256
 
.PARAMETER FileID
    The file identifier - Sha1, or Sha256
 
.EXAMPLE
    PS C:\> Get-MdFileMachine -FileID $fileid
 
    Retrieve from Windows Defender ATP a collection of machines related to a given file by identifier Sha1, or Sha256
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $FileID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'files/{FileID}/machines' -Replace '{FileID}',$FileID
            Method = 'get'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdIndicator {
<#
.SYNOPSIS
    Indicators - Get list of all active indicators
 
.DESCRIPTION
    Retrieve from Windows Defender ATP list of all active indicators
 
.PARAMETER Top
    Returns only the first n results.
 
.PARAMETER Orderby
    Sorts the results.
 
.PARAMETER Select
    Selects which properties to include in the response, defaults to all.
 
.PARAMETER Skip
    Skips the first n results.
 
.PARAMETER Filter
    Filters the results, using OData syntax.
 
.PARAMETER Count
    Includes a count of the matching results in the response.
 
.EXAMPLE
    PS C:\> Get-MdIndicator
 
    Retrieve from Windows Defender ATP list of all active indicators
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Top,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Orderby,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string[]]
        $Select,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Skip,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Filter,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [boolean]
        $Count
    )
    process {
        $__mapping = @{
            'Top' = '$top'
            'Orderby' = '$orderby'
            'Select' = '$select'
            'Skip' = '$skip'
            'Filter' = '$filter'
            'Count' = '$count'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'indicators'
            Method = 'get'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function New-MdIndicator {
<#
.SYNOPSIS
    Indicators - Submit a new indicator
 
.DESCRIPTION
    Submit a new indicator
 
    Scopes required (delegate auth): Ti.ReadWrite
 
.PARAMETER Title
    The indicator title
 
.PARAMETER IndicatorType
    The type of the indicator
 
.PARAMETER Description
    The indicator description
 
.PARAMETER ExpirationTime
    The expiration time of the indicator
 
.PARAMETER IndicatorValue
    The value of the indicator
 
.PARAMETER Severity
    The severity of the indicator
 
.PARAMETER Application
    The application associated with the indicator
 
.PARAMETER RecommendedActions
    Recommended actions for the indicator
 
.PARAMETER Action
    The action that will be taken if the indicator will be discovered in the organization
 
.EXAMPLE
    PS C:\> New-MdIndicator -Title $title -Description $description -Action $action
 
    Submit a new indicator
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/post-ti-indicator?view=o365-worldwide
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Title,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $IndicatorType,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Description,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $ExpirationTime,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $IndicatorValue,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Severity,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Application,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $RecommendedActions,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Action
    )
    process {
        $__mapping = @{
            'Title' = 'Title'
            'IndicatorType' = 'Indicator type'
            'Description' = 'Description'
            'ExpirationTime' = 'Expiration time'
            'IndicatorValue' = 'Indicator Value'
            'Severity' = 'Severity'
            'Application' = 'Application'
            'RecommendedActions' = 'Recommended Actions'
            'Action' = 'Action'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Title','IndicatorType','Description','ExpirationTime','IndicatorValue','Severity','Application','RecommendedActions','Action') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'indicators'
            Method = 'post'
            RequiredScopes = 'Ti.ReadWrite'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Remove-MdIndicator {
<#
.SYNOPSIS
    Indicators - Delete a single indicator by id
 
.DESCRIPTION
    Delete a single indicator by indicator id
 
.PARAMETER IndicatorID
    The identifier of the Indicator to delete
 
.EXAMPLE
    PS C:\> Remove-MdIndicator -IndicatorID $indicatorid
 
    Delete a single indicator by indicator id
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $IndicatorID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'indicators/{IndicatorID}' -Replace '{IndicatorID}',$IndicatorID
            Method = 'delete'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdInvestigation {
<#
.SYNOPSIS
    Actions - Get list of investigation
 
.DESCRIPTION
    Retrieve from Microsoft Defender ATP the most recent investigations
 
.PARAMETER Top
    Returns only the first n results.
 
.PARAMETER Orderby
    Sorts the results.
 
.PARAMETER Select
    Selects which properties to include in the response, defaults to all.
 
.PARAMETER Skip
    Skips the first n results.
 
.PARAMETER Filter
    Filters the results, using OData syntax.
 
.PARAMETER InvestigationID
    The identifier of the investigation to retrieve
 
.PARAMETER Count
    Includes a count of the matching results in the response.
 
.EXAMPLE
    PS C:\> Get-MdInvestigation
 
    Retrieve from Microsoft Defender ATP the most recent investigations
 
.EXAMPLE
    PS C:\> Get-MdInvestigation -InvestigationID $investigationid
 
    Retrieve from Microsoft Defender ATP a specific investigation
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Top,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Orderby,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string[]]
        $Select,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Skip,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Filter,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleInvestigation')]
        [string]
        $InvestigationID,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [boolean]
        $Count
    )
    process {
        $__mapping = @{
            'Top' = '$top'
            'Orderby' = '$orderby'
            'Select' = '$select'
            'Skip' = '$skip'
            'Filter' = '$filter'
            'Count' = '$count'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'investigations'
            Method = 'get'
            
            
        }
        if ($InvestigationID) { $__param.Path += "/$InvestigationID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdLiveResponseResultDownloadLink {
<#
.SYNOPSIS
    Retrieves a specific live response command result by its index.
 
.DESCRIPTION
    Retrieves a specific live response command result by its index.
 
    Scopes required (delegate auth): Machine.LiveResponse
 
.PARAMETER MachineActionID
    The identifier of the machine action
 
.PARAMETER CommandIndex
    The index of the live response command to get the results download URI for
 
.EXAMPLE
    PS C:\> Get-MdLiveResponseResultDownloadLink -MachineActionID $machineactionid -CommandIndex $commandindex
 
    Get result download URI for a completed live response command
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-live-response-result?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $MachineActionID,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $CommandIndex
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machineactions/{MachineActionID}/GetLiveResponseResultDownloadLink(index={CommandIndex})' -Replace '{MachineActionID}',$MachineActionID -Replace '{CommandIndex}',$CommandIndex
            Method = 'get'
            RequiredScopes = 'Machine.LiveResponse'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineAction {
<#
.SYNOPSIS
    Actions - Get list of machine actions
 
.DESCRIPTION
    Retrieve from Windows Defender ATP the most recent machine actions
 
    Scopes required (delegate auth): Machine.Read
 
.PARAMETER Top
    Returns only the first n results.
 
.PARAMETER Orderby
    Sorts the results.
 
.PARAMETER Select
    Selects which properties to include in the response, defaults to all.
 
.PARAMETER Skip
    Skips the first n results.
 
.PARAMETER MachineActionID
    The identifier of the machine action to retrieve
 
.PARAMETER Filter
    Filters the results, using OData syntax.
 
.PARAMETER Count
    Includes a count of the matching results in the response.
 
.EXAMPLE
    PS C:\> Get-MdMachineAction -MachineActionID $machineactionid
 
    Retrieve from Windows Defender ATP a specific machine action
 
.EXAMPLE
    PS C:\> Get-MdMachineAction
 
    Retrieve from Windows Defender ATP the most recent machine actions
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machineaction-object?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Top,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Orderby,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string[]]
        $Select,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Skip,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleMachineAction')]
        [string]
        $MachineActionID,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Filter,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [boolean]
        $Count
    )
    process {
        $__mapping = @{
            'Top' = '$top'
            'Orderby' = '$orderby'
            'Select' = '$select'
            'Skip' = '$skip'
            'Filter' = '$filter'
            'Count' = '$count'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machineactions'
            Method = 'get'
            RequiredScopes = 'Machine.Read'
            
        }
        if ($MachineActionID) { $__param.Path += "/$MachineActionID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineactionGetpackageuri {
<#
.SYNOPSIS
    Actions - Get investigation package download URI
 
.DESCRIPTION
    Get a URI that allows downloading of an investigation package
 
.PARAMETER MachineactionID
    The ID of the investigation package collection
 
.EXAMPLE
    PS C:\> Get-MdMachineactionGetpackageuri -MachineactionID $machineactionid
 
    Get a URI that allows downloading of an investigation package
 
.LINK
    <unknown>
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $MachineactionID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machineactions/{MachineactionID}/getPackageUri' -Replace '{MachineactionID}',$MachineactionID
            Method = 'get'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineactionCancel {
<#
.SYNOPSIS
    Actions - Cancel a single machine action
 
.DESCRIPTION
    Cancel a specific machine action
 
.PARAMETER MachineActionID
    The identifier of the machine action to cancel
 
.PARAMETER Comment
    A comment to associate to the machine action cancellation
 
.EXAMPLE
    PS C:\> Set-MdMachineactionCancel -MachineActionID $machineactionid -Comment $comment
 
    Cancel a specific machine action
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $MachineActionID,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machineactions/{MachineActionID}/cancel' -Replace '{MachineActionID}',$MachineActionID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Disable-MdMachineIsolation {
<#
.SYNOPSIS
    Undo isolation of a device.
 
.DESCRIPTION
    Undo isolation of a device.
 
    Scopes required (delegate auth): Machine.Isolate
 
.PARAMETER Comment
    A comment to associate to the unisolation
 
.PARAMETER MachineID
    The ID of the machine to unisolate
 
.EXAMPLE
    PS C:\> Disable-MdMachineIsolation -Comment $comment -MachineID $machineid
 
    Unisolate a machine from network
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/unisolate-machine?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/unisolate' -Replace '{MachineID}',$MachineID
            Method = 'post'
            RequiredScopes = 'Machine.Isolate'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Enable-MdMachineIsolation {
<#
.SYNOPSIS
    Isolates a device from accessing external network.
 
.DESCRIPTION
    Isolates a device from accessing external network.
 
    Scopes required (delegate auth): Machine.Isolate
 
.PARAMETER IsolationType
    Type of the isolation. Allowed values are 'Full' (for full isolation) or 'Selective' (to restrict only limited set of applications from accessing the network)
 
.PARAMETER Comment
    A comment to associate to the isolation
 
.PARAMETER MachineID
    The ID of the machine to isolate
 
.EXAMPLE
    PS C:\> Enable-MdMachineIsolation -Comment $comment -MachineID $machineid
 
    Isolate a machine from network
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/isolate-machine?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $IsolationType,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'IsolationType' = 'Isolation Type'
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('IsolationType','Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/isolate' -Replace '{MachineID}',$MachineID
            Method = 'post'
            RequiredScopes = 'Machine.Isolate'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachine {
<#
.SYNOPSIS
    Machines - Get list of machines
 
.DESCRIPTION
    Retrieve from Windows Defender ATP the most recent machines
 
    Scopes required (delegate auth): Machine.Read
 
.PARAMETER Top
    Returns only the first n results.
 
.PARAMETER Orderby
    Sorts the results.
 
.PARAMETER MachineID
    The identifier of the machine to retrieve
 
.PARAMETER Select
    Selects which properties to include in the response, defaults to all.
 
.PARAMETER Skip
    Skips the first n results.
 
.PARAMETER Filter
    Filters the results, using OData syntax.
 
.PARAMETER Count
    Includes a count of the matching results in the response.
 
.EXAMPLE
    PS C:\> Get-MdMachine -MachineID $machineid
 
    Retrieve from Windows Defender ATP a specific machine
 
.EXAMPLE
    PS C:\> Get-MdMachine
 
    Retrieve from Windows Defender ATP the most recent machines
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Top,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Orderby,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleMachine')]
        [Alias('Id')]
        [string]
        $MachineID,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string[]]
        $Select,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [int32]
        $Skip,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Filter,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [boolean]
        $Count
    )
    process {
        $__mapping = @{
            'Top' = '$top'
            'Orderby' = '$orderby'
            'Select' = '$select'
            'Skip' = '$skip'
            'Filter' = '$filter'
            'Count' = '$count'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines'
            Method = 'get'
            RequiredScopes = 'Machine.Read'
            
        }
        if ($MachineID) { $__param.Path += "/$MachineID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineRecommendation {
<#
.SYNOPSIS
    Retrieves a collection of security recommendations related to a given device ID.
 
.DESCRIPTION
    Retrieves a collection of security recommendations related to a given device ID.
 
    Scopes required (delegate auth): SecurityRecommendation.Read
 
.PARAMETER MachineID
    ID of the machine to get recommendations for.
 
.EXAMPLE
    PS C:\> Get-MdMachineRecommendation -MachineID $machineid
 
    Retrieves a collection of security recommendations related to the specified device ID.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-security-recommendations?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/recommendations' -Replace '{MachineID}',$MachineID
            Method = 'get'
            RequiredScopes = 'SecurityRecommendation.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineSoftware {
<#
.SYNOPSIS
    Retrieves a collection of installed software related to a given device ID.
 
.DESCRIPTION
    Retrieves a collection of installed software related to a given device ID.
 
    Scopes required (delegate auth): Software.Read
 
.PARAMETER MachineID
    ID of the machine to read the installed software from.
 
.EXAMPLE
    PS C:\> Get-MdMachineSoftware -MachineID $machineid
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-installed-software?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/software' -Replace '{MachineID}',$MachineID
            Method = 'get'
            RequiredScopes = 'Software.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdMachineVulnerability {
<#
.SYNOPSIS
    Retrieves a collection of discovered vulnerabilities related to a given device ID.
 
.DESCRIPTION
    Retrieves a collection of discovered vulnerabilities related to a given device ID.
 
    Scopes required (delegate auth): Vulnerability.Read
 
.PARAMETER MachineID
    ID of the machine to read the detected vulnerabilities from.
 
.EXAMPLE
    PS C:\> Get-MdMachineVulnerability -MachineID $machineid
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-discovered-vulnerabilities?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/vulnerabilities' -Replace '{MachineID}',$MachineID
            Method = 'get'
            RequiredScopes = 'Vulnerability.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineCollectinvestigationpackage {
<#
.SYNOPSIS
    Actions - Collect investigation package
 
.DESCRIPTION
    Collect investigation package from a machine
 
.PARAMETER Comment
    A comment to associate to the collection
 
.PARAMETER MachineID
    The ID of the machine to collect the investigation from
 
.EXAMPLE
    PS C:\> Set-MdMachineCollectinvestigationpackage -Comment $comment -MachineID $machineid
 
    Collect investigation package from a machine
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/collectInvestigationPackage' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineOffboard {
<#
.SYNOPSIS
    Actions - Offboard machine from Microsoft Defender ATP
 
.DESCRIPTION
    Offboard machine from Microsoft Defender ATP
 
.PARAMETER Comment
    A comment to associate to the offboarding action
 
.PARAMETER MachineID
    The ID of the machine to offboard
 
.EXAMPLE
    PS C:\> Set-MdMachineOffboard -Comment $comment -MachineID $machineid
 
    Offboard machine from Microsoft Defender ATP
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/offboard' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineRestrictcodeexecution {
<#
.SYNOPSIS
    Actions - Restrict app execution
 
.DESCRIPTION
    Restrict execution of all applications on the machine except a predefined set
 
.PARAMETER Comment
    A comment to associate to the restriction
 
.PARAMETER MachineID
    The ID of the machine to restrict
 
.EXAMPLE
    PS C:\> Set-MdMachineRestrictcodeexecution -Comment $comment -MachineID $machineid
 
    Restrict execution of all applications on the machine except a predefined set
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/restrictCodeExecution' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineRunantivirusscan {
<#
.SYNOPSIS
    Actions - Run antivirus scan
 
.DESCRIPTION
    Initiate Windows Defender Antivirus scan on a machine
 
.PARAMETER ScanType
    Type of scan to perform. Allowed values are 'Quick' or 'Full'
 
.PARAMETER Comment
    A comment to associate to the scan request
 
.PARAMETER MachineID
    The ID of the machine to scan
 
.EXAMPLE
    PS C:\> Set-MdMachineRunantivirusscan -Comment $comment -MachineID $machineid
 
    Initiate Windows Defender Antivirus scan on a machine
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $ScanType,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'ScanType' = 'Scan Type'
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('ScanType','Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/runAntiVirusScan' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineStartinvestigation {
<#
.SYNOPSIS
    Actions - Start automated investigation on a machine
 
.DESCRIPTION
    Start automated investigation on a machine
 
.PARAMETER Comment
    A comment to associate to the investigation
 
.PARAMETER MachineID
    The ID of the machine to investigate
 
.EXAMPLE
    PS C:\> Set-MdMachineStartinvestigation -Comment $comment -MachineID $machineid
 
    Start automated investigation on a machine
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/startInvestigation' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineStopandquarantinefile {
<#
.SYNOPSIS
    Actions - Stop and quarantine a file
 
.DESCRIPTION
    Stop execution of a file on a machine and delete it.
 
.PARAMETER Comment
    A comment to associate to the restriction removal
 
.PARAMETER MachineID
    The ID of the machine to unrestrict
 
.EXAMPLE
    PS C:\> Set-MdMachineStopandquarantinefile -Comment $comment -MachineID $machineid
 
    Stop execution of a file on a machine and delete it.
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/StopAndQuarantineFile' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineTag {
<#
.SYNOPSIS
    Machines - Tag machine
 
.DESCRIPTION
    Add or remove a tag to/from a machine
 
.PARAMETER Action
    The action to perform. Value should be one of 'Add' (to add a tag) or 'Remove' (to remove a tag)
 
.PARAMETER Value
    The tag to add or remove
 
.PARAMETER MachineID
    The ID of the machine to which the tag should be added or removed
 
.EXAMPLE
    PS C:\> Set-MdMachineTag -Action $action -Value $value -MachineID $machineid
 
    Add or remove a tag to/from a machine
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Action,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Value,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Action' = 'Action'
            'Value' = 'Value'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Action','Value') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/tags' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Set-MdMachineUnrestrictcodeexecution {
<#
.SYNOPSIS
    Actions - Remove app execution restriction
 
.DESCRIPTION
    Enable execution of any application on the machine
 
.PARAMETER Comment
    A comment to associate to the restriction removal
 
.PARAMETER MachineID
    The ID of the machine to unrestrict
 
.EXAMPLE
    PS C:\> Set-MdMachineUnrestrictcodeexecution -Comment $comment -MachineID $machineid
 
    Enable execution of any application on the machine
 
.LINK
    <unknown>
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/unrestrictCodeExecution' -Replace '{MachineID}',$MachineID
            Method = 'post'
            
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Start-MdMachineLiveResponse {
<#
.SYNOPSIS
    Runs a sequence of live response commands on a device
 
.DESCRIPTION
    Runs a sequence of live response commands on a device
 
    Scopes required (delegate auth): Machine.LiveResponse
 
.PARAMETER Commands
    The live response commands to execute.
Example:
@{
    type = "RunScript"
    params = @(
        @{
            key = "ScriptName"
            value = "minidump.ps1"
        },
        @{
            key = "Args"
            value = "OfficeClickToRun"
        }
    )
}
 
.PARAMETER Comment
    A comment to associate to the isolation
 
.PARAMETER MachineID
    ID of the machine to execute a live response script upon
 
.EXAMPLE
    PS C:\> Start-MdMachineLiveResponse -Commands $commands -Comment $comment -MachineID $machineid
 
    Run live response api commands for a single machine
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-live-response?view=o365-worldwide
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [array]
        $Commands,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Comment,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $MachineID
    )
    process {
        $__mapping = @{
            'Commands' = 'Commands'
            'Comment' = 'Comment'
        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Commands','Comment') -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'machines/{MachineID}/runliveresponse' -Replace '{MachineID}',$MachineID
            Method = 'post'
            RequiredScopes = 'Machine.LiveResponse'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdRecommendation {
<#
.SYNOPSIS
    Retrieves a list of all security recommendations affecting the organization.
 
.DESCRIPTION
    Retrieves a list of all security recommendations affecting the organization.
 
    Scopes required (delegate auth): SecurityRecommendation.Read
 
.PARAMETER RecommendationID
    ID of the recommendation to retrieve.
 
.EXAMPLE
    PS C:\> Get-MdRecommendation
 
    Lists all security recommendations
 
.EXAMPLE
    PS C:\> Get-MdRecommendation -RecommendationID $recommendationid
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-all-recommendations?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetRecommendationById')]
        [Alias('Id')]
        [string]
        $RecommendationID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'recommendations'
            Method = 'get'
            RequiredScopes = 'SecurityRecommendation.Read'
            
        }
        if ($RecommendationID) { $__param.Path += "/$RecommendationID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdRecommendationMachineReference {
<#
.SYNOPSIS
    Retrieves a list of devices associated with the security recommendation.
 
.DESCRIPTION
    Retrieves a list of devices associated with the security recommendation.
 
    Scopes required (delegate auth): SecurityRecommendation.Read
 
.PARAMETER RecommendationID
    ID of the recommendation for which to retrieve devices.
 
.EXAMPLE
    PS C:\> Get-MdRecommendationMachineReference -RecommendationID $recommendationid
 
    Retrieves a list of devices associated with the security recommendation.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-recommendation-machines?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $RecommendationID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'recommendations/{RecommendationID}/machineReferences' -Replace '{RecommendationID}',$RecommendationID
            Method = 'get'
            RequiredScopes = 'SecurityRecommendation.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdRecommendationSoftware {
<#
.SYNOPSIS
    Retrieves a security recommendation related to a specific software.
 
.DESCRIPTION
    Retrieves a security recommendation related to a specific software.
 
    Scopes required (delegate auth): SecurityRecommendation.Read
 
.PARAMETER RecommendationID
    ID of the recommendation for which to retrieve software information.
 
.EXAMPLE
    PS C:\> Get-MdRecommendationSoftware -RecommendationID $recommendationid
 
    Retrieves a security recommendation related to a specific software.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/list-recommendation-software?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $RecommendationID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'recommendations/{RecommendationID}/software' -Replace '{RecommendationID}',$RecommendationID
            Method = 'get'
            RequiredScopes = 'SecurityRecommendation.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdRecommendationVulnerability {
<#
.SYNOPSIS
    Retrieves a list of vulnerabilities associated with the security recommendation.
 
.DESCRIPTION
    Retrieves a list of vulnerabilities associated with the security recommendation.
 
    Scopes required (delegate auth): Vulnerability.Read
 
.PARAMETER RecommendationID
    ID of the recommendation for which to retrieve vulnerabilities.
 
.EXAMPLE
    PS C:\> Get-MdRecommendationVulnerability -RecommendationID $recommendationid
 
    Retrieves a list of vulnerabilities associated with the security recommendation.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-recommendation-vulnerabilities?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $RecommendationID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'recommendations/{RecommendationID}/vulnerabilities' -Replace '{RecommendationID}',$RecommendationID
            Method = 'get'
            RequiredScopes = 'Vulnerability.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdSoftware {
<#
.SYNOPSIS
    Retrieves the organization software inventory.
 
.DESCRIPTION
    Retrieves the organization software inventory.
 
    Scopes required (delegate auth): Software.Read
 
.PARAMETER SoftwareID
    ID of the software to retrieve.
 
.EXAMPLE
    PS C:\> Get-MdSoftware -SoftwareID $softwareid
 
    <insert description here>
 
.EXAMPLE
    PS C:\> Get-MdSoftware
 
    Lists all security recommendations
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-software?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetProductById')]
        [Alias('Id')]
        [string]
        $SoftwareID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'software'
            Method = 'get'
            RequiredScopes = 'Software.Read'
            
        }
        if ($SoftwareID) { $__param.Path += "/$SoftwareID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdSoftwareDistribution {
<#
.SYNOPSIS
    Shows the distribution of versions of a software in your organization.
 
.DESCRIPTION
    Shows the distribution of versions of a software in your organization.
 
    Scopes required (delegate auth): Software.Read
 
.PARAMETER SoftwareID
    ID of the software for which to retrieve distribution data.
 
.EXAMPLE
    PS C:\> Get-MdSoftwareDistribution -SoftwareID $softwareid
 
    Shows the distribution of versions of the specified software in your organization.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-software-ver-distribution?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $SoftwareID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'software/{SoftwareID}/distribution' -Replace '{SoftwareID}',$SoftwareID
            Method = 'get'
            RequiredScopes = 'Software.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdSoftwareMachinereference {
<#
.SYNOPSIS
    Retrieve a list of device references that has this software installed.
 
.DESCRIPTION
    Retrieve a list of device references that has this software installed.
 
    Scopes required (delegate auth): Software.Read
 
.PARAMETER SoftwareID
    ID of the software for which to retrieve devices that have it installed.
 
.EXAMPLE
    PS C:\> Get-MdSoftwareMachinereference -SoftwareID $softwareid
 
    Retrieve a list of device references that has this software installed.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines-by-software?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $SoftwareID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'software/{SoftwareID}/machineReferences' -Replace '{SoftwareID}',$SoftwareID
            Method = 'get'
            RequiredScopes = 'Software.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdSoftwareVulnerability {
<#
.SYNOPSIS
    Retrieve a list of vulnerabilities in the installed software.
 
.DESCRIPTION
    Retrieve a list of vulnerabilities in the installed software.
 
    Scopes required (delegate auth): Vulnerability.Read
 
.PARAMETER SoftwareID
    ID of the software for which to retrieve devices that have it installed.
 
.EXAMPLE
    PS C:\> Get-MdSoftwareVulnerability -SoftwareID $softwareid
 
    Retrieve a list of vulnerabilities in the installed software.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-vuln-by-software?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $SoftwareID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'software/{SoftwareID}/vulnerabilities' -Replace '{SoftwareID}',$SoftwareID
            Method = 'get'
            RequiredScopes = 'Vulnerability.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdVulnerability {
<#
.SYNOPSIS
    Retrieves a list of all the vulnerabilities.
 
.DESCRIPTION
    Retrieves a list of all the vulnerabilities.
 
    Scopes required (delegate auth): Vulnerability.Read
 
.PARAMETER VulnerabilityID
    ID of the vulnerability to retrieve.
 
.EXAMPLE
    PS C:\> Get-MdVulnerability
 
    Retrieves a list of all the vulnerabilities.
 
.EXAMPLE
    PS C:\> Get-MdVulnerability -VulnerabilityID $vulnerabilityid
 
    <insert description here>
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-all-vulnerabilities?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetVulnerability')]
        [Alias('Id')]
        [string]
        $VulnerabilityID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'vulnerabilities'
            Method = 'get'
            RequiredScopes = 'Vulnerability.Read'
            
        }
        if ($VulnerabilityID) { $__param.Path += "/$VulnerabilityID" }
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Get-MdVulnerableMachine {
<#
.SYNOPSIS
    Retrieves a list of devices affected by a vulnerability.
 
.DESCRIPTION
    Retrieves a list of devices affected by a vulnerability.
 
    Scopes required (delegate auth): Vulnerability.Read
 
.PARAMETER VulnerabilityID
    ID of the vulnerability for which to retrieve affected devices.
 
.EXAMPLE
    PS C:\> Get-MdVulnerableMachine -VulnerabilityID $vulnerabilityid
 
    Retrieves a list of devices affected by a vulnerability.
 
.LINK
    https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines-by-vulnerability?view=o365-worldwide
#>

    [CmdletBinding(DefaultParameterSetName = 'default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [Alias('Id')]
        [string]
        $VulnerabilityID
    )
    process {
        $__mapping = @{

        }

        $__param = @{
            Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
            Path = 'vulnerabilities/{VulnerabilityID}/machineReferences' -Replace '{VulnerabilityID}',$VulnerabilityID
            Method = 'get'
            RequiredScopes = 'Vulnerability.Read'
            
        }
        
        $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose'

        try { Invoke-DefenderAPIRequest @__param }
        catch { $PSCmdlet.ThrowTerminatingError($_) }
    }
}

function Invoke-MSecAdvancedHuntingQuery {
    <#
    .SYNOPSIS
        Execute an advanced hunting query.
     
    .DESCRIPTION
        Execute an advanced hunting query.
 
        Requires being connected to the "security" service.
        To establish a connection, use the "Connect-MdeService" command with the parameter '-Service "security"'
        Example:
        Connect-DefenderAPIService -Service security -DeviceCode -ClientID $ClientID -TenantID $TenantID
         
        Scopes required (delegate auth): AdvancedHunting.Read
     
    .PARAMETER Query
        The hunting query to execute.
     
    .EXAMPLE
        PS C:\> Invoke-MSecAdvancedHuntingQuery -Query 'DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\" | project Timestamp, FileName, InitiatingProcessFileName | order by Timestamp desc | limit 2'
 
        Executes the query, searching for the latest two powershell processes
     
    .LINK
        https://docs.microsoft.com/en-us/microsoft-365/security/defender/api-advanced-hunting?view=o365-worldwide
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')]
        [string]
        $Query
    )

    process {
        $__mapping = @{
            'Query' = 'Query'
        }
        $__body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping
        $__query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping
        $__path = 'advancedhunting/run'
    
        Invoke-DefenderAPIRequest -Service 'security' -Path $__path -Method post -Body $__body -Query $__query -RequiredScopes 'AdvancedHunting.Read' | ConvertFrom-AdvancedQuery
    }
}

<#
This is an example configuration file
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


<#
# Example Configuration
Set-PSFConfig -Module 'DefenderAPI' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
#>


Set-PSFConfig -Module 'DefenderAPI' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'DefenderAPI' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."
Set-PSFConfig -Module 'DefenderAPI' -Name 'Request.SerializationDepth' -Value 99 -Initialize -Validation integerpositive -Description 'How deep provided request bodies are serialized.'

<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'DefenderAPI.ScriptBlockName' -Scriptblock {
     
}
#>


# Available Tokens
$script:_DefenderTokens = @{}

# Endpoint Configuration for Requests
$script:_DefenderEndpoints = @{}

$script:_strings = Get-PSFLocalizedString -Module DefenderAPI

# Registers the default service configurations
$endpointCfg = @{
    Name          = 'Endpoint'
    ServiceUrl    = 'https://api.securitycenter.microsoft.com/api'
    Resource      = 'https://api.securitycenter.microsoft.com'
    DefaultScopes = @()
    Header        = @{ 'Content-Type' = 'application/json' }
    HelpUrl       = 'https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/apis-intro?view=o365-worldwide'
}
Register-DefenderAPIService @endpointCfg

$securityCfg = @{
    Name          = 'Security'
    ServiceUrl    = 'https://api.security.microsoft.com/api'
    Resource      = 'https://security.microsoft.com/mtp/'
    DefaultScopes = @('AdvancedHunting.Read')
    Header        = @{ 'Content-Type' = 'application/json' }
    HelpUrl       = 'https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-web?view=o365-worldwide'
}
Register-DefenderAPIService @securityCfg

New-PSFLicense -Product 'DefenderAPI' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2024-03-11") -Text @"
Copyright (c) 2024 Friedrich Weinmann
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@

#endregion Load compiled code