functions/New-XdrAdvancedHuntingFunction.ps1

function New-XdrAdvancedHuntingFunction {
    <#
    .SYNOPSIS
        Creates a new Advanced Hunting function in Microsoft Defender XDR.

    .DESCRIPTION
        Creates a new saved function for Advanced Hunting queries in Microsoft Defender XDR.
        These functions can be reused across multiple hunting queries and detection rules.
        Functions can be shared with the organization or kept private in folder structures.

    .PARAMETER Name
        The name of the function. This will be used to call the function in queries.

    .PARAMETER KQLQuery
        The KQL (Kusto Query Language) body of the function.
        This is the query logic that will be executed when the function is called.

    .PARAMETER Description
        Optional description of what the function does.

    .PARAMETER IsShared
        Switch to make the function shared with the organization.
        If not specified, the function will be private to the creator.

    .PARAMETER FolderPath
        Optional folder path for organizing private functions.
        Use forward slashes (/) or backslashes (\) - they will be automatically converted to double backslashes.
        Example: "MyFolder/SubFolder" or "MyFolder\SubFolder"

    .PARAMETER WhatIf
        Shows what would happen if the cmdlet runs. The function is not created.

    .PARAMETER Confirm
        Prompts you for confirmation before running the cmdlet.

    .EXAMPLE
        New-XdrAdvancedHuntingFunction -Name "GetSuspiciousLogons" -KQLQuery "DeviceLogonEvents | where LogonType == 'Network'" -IsShared
        Creates a shared function that can be used across the organization.

    .EXAMPLE
        New-XdrAdvancedHuntingFunction -Name "ExtendedEntraIdSignInEvents" -KQLQuery $query -Description "Combines XDR and Sentinel data" -IsShared
        Creates a shared function with a description.

    .EXAMPLE
        New-XdrAdvancedHuntingFunction -Name "MyFunction" -KQLQuery "DeviceEvents" -FolderPath "TestFolder/SubFolder"
        Creates a private function in a folder structure.

    .EXAMPLE
        $query = @"
        EntraIdSignInEvents
        | where RiskLevelAggregated > 50
        | project Timestamp, AccountUpn, IPAddress
        "@
        New-XdrAdvancedHuntingFunction -Name "HighRiskSignIns" -KQLQuery $query -Description "Returns high-risk sign-ins" -IsShared
        Creates a shared function with a multi-line query.

    .OUTPUTS
        Object
        Returns the created function object from the API including:
        - Id: Unique identifier for the function
        - Name: Function name
        - Body: KQL query body
        - Description: Function description
        - Path: Folder path
        - IsShared: Sharing status
        - CreatedBy: Creator's UPN
        - LastUpdatedBy: Last updater's UPN
        - LastUpdateTime: Last update timestamp
        - OutputColumns: Schema of the function output

    .NOTES
        Functions can be called in queries using their name, e.g.: GetSuspiciousLogons()
        Shared functions are visible to all users in the organization.
        Private functions can be organized in folders for better management.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$KQLQuery,

        [Parameter()]
        [string]$Description = "",

        [Parameter()]
        [switch]$IsShared,

        [Parameter()]
        [string]$FolderPath = ""
    )

    begin {
        Update-XdrConnectionSettings
    }

    process {
        try {
            # Normalize folder path: convert / or \ to \\
            if (-not [string]::IsNullOrWhiteSpace($FolderPath)) {
                # Replace forward slashes with backslashes first
                $normalizedPath = $FolderPath -replace '/', '\'
                # Then double the backslashes for JSON
                $normalizedPath = $normalizedPath -replace '\\', '\\'
            } else {
                $normalizedPath = ""
            }

            # Build API request body
            $body = @{
                Name            = $Name
                Path            = $normalizedPath
                Description     = $Description
                InputParameters = @()
                IsShared        = $IsShared.IsPresent
                Body            = $KQLQuery
            } | ConvertTo-Json -Depth 10

            $Uri = "https://security.microsoft.com/apiproxy/mtp/huntingService/savedFunctions"

            # If WhatIf is specified, output the JSON body
            if ($WhatIfPreference) {
                Write-Host "JSON Body for function '$Name':"
                Write-Host $body
                return
            }

            if ($PSCmdlet.ShouldProcess($Name, "Create Advanced Hunting function")) {
                Write-Verbose "Creating Advanced Hunting function: $Name"

                $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers

                Write-Verbose "Successfully created function with ID: $($result.Id)"
                Write-Host $result
            }
        } catch {
            Write-Error "Failed to create Advanced Hunting function '$Name': $($_.Exception.Message)"
        }
    }

    end {

    }
}