AzSentinel.psm1

#requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'}
#requires -version 6.2

enum AggregationKind {
    SingleAlert
    AlertPerResult
}

enum CloseReason {
    TruePositive
    FalsePositive
}

enum DataSourceName {
    ApplicationInsights
    AzureActivityLog
    AzureAuditLog
    ChangeTrackingContentLocation
    ChangeTrackingCustomPath
    ChangeTrackingDataTypeConfiguration
    ChangeTrackingDefaultRegistry
    ChangeTrackingLinuxPath
    ChangeTrackingPath
    ChangeTrackingRegistry
    ChangeTrackingServices
    CustomLog
    CustomLogCollection
    DnsAnalytics
    GenericDataSource
    IISLogs
    ImportComputerGroup
    Itsm
    LinuxChangeTrackingPath
    LinuxPerformanceCollection
    LinuxPerformanceObject
    LinuxSyslog
    LinuxSyslogCollection
    NetworkMonitoring
    Office365
    SecurityCenterSecurityWindowsBaselineConfiguration
    SecurityEventCollectionConfiguration
    SecurityInsightsSecurityEventCollectionConfiguration
    SecurityWindowsBaselineConfiguration
    SqlDataClassification
    WindowsEvent
    WindowsPerformanceCounter
    WindowsTelemetry
}

enum ExportType {
    Alert
    Hunting
    All
    Templates
}

enum GroupByEntities {
    Account
    Ip
    Host
    Url
    FileHash
}

enum Kind {
    Scheduled
    Fusion
    MLBehaviorAnalytics
    MicrosoftSecurityIncidentCreation
}

enum MatchingMethod {
    All
    None
    Custom
}

enum Severity {
    Medium
    High
    Low
    Informational
}

enum Status {
    New
    InProgress
    Closed
}

enum Tactics {
    InitialAccess
    Persistence
    Execution
    PrivilegeEscalation
    DefenseEvasion
    CredentialAccess
    LateralMovement
    Discovery
    Collection
    Exfiltration
    CommandAndControl
    Impact
}

enum TriggerOperator {
    GreaterThan
    LessThan
    Equal
    NotEqual
    gt
    lt
    eq
    ne
}

class MicrosoftSecurityIncidentCreation {
    [string] $DisplayName
    [string]$Description
    [bool]$Enabled
    [string]$ProductFilter
    [Severity[]]$SeveritiesFilter
    [string]$DisplayNamesFilter

    MicrosoftSecurityIncidentCreation ($DisplayName, $Description, $Enabled, $ProductFilter, $SeveritiesFilter, $DisplayNamesFilter) {
        $this.displayName = $DisplayName
        $this.description = $Description
        $this.enabled = $Enabled
        $this.productFilter = $ProductFilter
        $this.severitiesFilter = $SeveritiesFilter
        $this.displayNamesFilter = $DisplayNamesFilter
    }
}

class Fusion {
    [bool]$Enabled
    [string]$AlertRuleTemplateName

    Fusion ($Enabled, $AlertRuleTemplateName) {
        $this.enabled = $Enabled
        $this.AlertRuleTemplateName = $AlertRuleTemplateName
    }
}

class MLBehaviorAnalytics {
    [bool]$Enabled
    [string]$AlertRuleTemplateName

    MLBehaviorAnalytics ($Enabled, $AlertRuleTemplateName) {
        $this.enabled = $Enabled
        $this.AlertRuleTemplateName = $AlertRuleTemplateName
    }
}

class GroupingConfiguration {
    [bool]$Enabled

    [bool]$reopenClosedIncident

    [string]$lookbackDuration

    [MatchingMethod]$entitiesMatchingMethod

    [GroupByEntities[]]$groupByEntities

    # Convert string to ISO_8601 format PdDThHmMsS
    static [string] TimeString([string]$value) {
        $value = $value.ToUpper()
        # Return values already in ISO 8601 format
        if ($value -match "PT.*|P.*D") {
            return $value
        }
        # Format day time periods
        if ($value -like "*D") {
            return "P$value"
        }
        # Format hour and minute time periods
        if ($value -match ".*[HM]") {
            return "PT$value"
        }
        return $value
    }

    groupingConfiguration ($properties) {
        $this.enabled = $properties.enabled
        $this.reopenClosedIncident = $properties.reopenClosedIncident
        $this.lookbackDuration = $properties.lookbackDuration
        $this.entitiesMatchingMethod = $properties.entitiesMatchingMethod
        $this.groupByEntities = $properties.groupByEntities
    }

    groupingConfiguration ($Enabled, $reopenClosedIncident, $lookbackDuration, $entitiesMatchingMethod, $groupByEntities) {
        $this.enabled = if ($null -ne $Enabled ) { $Enabled } else { $false }
        $this.reopenClosedIncident = if ($null -ne $reopenClosedIncident) { $reopenClosedIncident } else { $false }
        $this.lookbackDuration = if ($lookbackDuration) { [groupingConfiguration]::TimeString($lookbackDuration) } else { "PT5H" }
        $this.entitiesMatchingMethod = if ($entitiesMatchingMethod) { $entitiesMatchingMethod } else { "All" }
        $this.groupByEntities = if ($groupByEntities) { $groupByEntities } else {
            @(
                "Account",
                "Ip",
                "Host",
                "Url",
                "FileHash"
            )
        }
    }
}

class IncidentConfiguration {
    [bool] $CreateIncident

    [GroupingConfiguration]$GroupingConfiguration

    IncidentConfiguration ($CreateIncident, $GroupingConfiguration) {
        $this.createIncident = if ($null -ne $createIncident) { $createIncident } else { $true }
        $this.groupingConfiguration = $GroupingConfiguration
    }
}

class ScheduledAlertProp {

    [guid] $Name

    [string] $DisplayName

    [string] $Description

    [Severity] $Severity

    [bool] $Enabled

    [string] $Query

    [string] $QueryFrequency

    [string] $QueryPeriod

    [TriggerOperator]$TriggerOperator

    [Int] $TriggerThreshold

    [string] $SuppressionDuration

    [bool] $SuppressionEnabled

    [Tactics[]] $Tactics

    [string] $PlaybookName

    [IncidentConfiguration]$IncidentConfiguration

    $eventGroupingSettings

    hidden [AggregationKind]$aggregationKind

    static [string] TriggerOperatorSwitch([string]$value) {
        switch ($value) {
            "gt" { $value = "GreaterThan" }
            "lt" { $value = "LessThan" }
            "eq" { $value = "Equal" }
            "ne" { $value = "NotEqual" }
            default { $value }
        }
        return $value
    }

    # Convert string to ISO_8601 format PdDThHmMsS
    static [string] TimeString([string]$value) {
        $value = $value.ToUpper()
        # Return values already in ISO 8601 format
        if ($value -match "PT.*|P.*D") {
            return $value
        }
        # Format day time periods
        if ($value -like "*D") {
            return "P$value"
        }
        # Format hour and minute time periods
        if ($value -match ".*[HM]") {
            return "PT$value"
        }
        return $value
    }
    ScheduledAlertProp (){

    }

    ScheduledAlertProp ($Name, $DisplayName, $Description, $Severity, $Enabled, $Query, $QueryFrequency, `
            $QueryPeriod, $TriggerOperator, $TriggerThreshold, $suppressionDuration, `
            $suppressionEnabled, $Tactics, $PlaybookName, $IncidentConfiguration, $aggregationKind) {
        $this.name = $Name
        $this.DisplayName = $DisplayName
        $this.Description = $Description
        $this.Severity = $Severity
        $this.Enabled = $Enabled
        $this.Query = $Query
        $this.QueryFrequency = [ScheduledAlertProp]::TimeString($QueryFrequency)
        $this.QueryPeriod = [ScheduledAlertProp]::TimeString($QueryPeriod)
        $this.TriggerOperator = [ScheduledAlertProp]::TriggerOperatorSwitch($TriggerOperator)
        $this.TriggerThreshold = $TriggerThreshold
        $this.SuppressionDuration = if (($null -eq $suppressionDuration) -or ( $false -eq $suppressionEnabled)) {
            "PT1H"
        }
        else {
            if ( [ScheduledAlertProp]::TimeString($suppressionDuration) -ge [ScheduledAlertProp]::TimeString($QueryFrequency) ) {
                [ScheduledAlertProp]::TimeString($suppressionDuration)
            }
            else {
                Write-Error "Invalid Properties for Scheduled alert rule: 'suppressionDuration' should be greater than or equal to 'queryFrequency'" -ErrorAction Stop
            }
        }
        $this.SuppressionEnabled = if ($suppressionEnabled) { $suppressionEnabled } else { $false }
        $this.Tactics = $Tactics
        if ($PlaybookName) {
            $this.PlaybookName = if ($PlaybookName.Split('/').count -gt 1){
                $PlaybookName.Split('/')[-1]
            } else {
                $PlaybookName
            }
        }
        $this.IncidentConfiguration = $IncidentConfiguration
        $this.eventGroupingSettings = @{
            aggregationKind = if ($aggregationKind) { $aggregationKind } else { "SingleAlert" }
        }
    }
}

class AlertRule {
    [string] $Name

    [string] $Etag

    [string]$type

    [Kind]$kind = 'Scheduled'

    [pscustomobject]$Properties

    [string]$Id

    AlertRule ($Name, $Etag, $Properties, $Id, $kind) {

        $this.id = $Id
        $this.type = 'Microsoft.SecurityInsights/alertRules'
        $this.kind = $kind
        $this.Name = $Name
        $this.Etag = $Etag
        $this.Properties = $Properties
    }
}

function Compare-Policy {
    <#
    .SYNOPSIS
    Compare PS Objects
    .DESCRIPTION
    This function is used for comparison to see if a rule needs to be updated
    .PARAMETER ReferenceTemplate
    Reference template is the data of the AlertRule as active on Azure
    .PARAMETER DifferenceTemplate
    Difference template is data that is generated and will be uploaded to Azure
    .EXAMPLE
    Compare-Policy -ReferenceTemplate -DifferenceTemplate
    .NOTES
    NAME: Compare-Policy
    #>


    [CmdletBinding()]
    param (
        # Reference value is the Online available
        [Parameter(Mandatory)]
        [psobject]$ReferenceTemplate,

        # Difference template is the template that will be uploaded
        [Parameter(Mandatory)]
        [psobject]$DifferenceTemplate
    )

    process {
        $objprops = $ReferenceTemplate | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name
        $objprops += $DifferenceTemplate | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name
        $objprops = $objprops | Sort-Object -Unique | Select-Object

        $diffs = @()

        foreach ($objprop in $objprops) {
            $diff = Compare-Object $ReferenceTemplate $DifferenceTemplate -Property $objprop
            if ($diff) {
                $diffprops = @{
                    PropertyName = $objprop
                    RefValue     = ($diff | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objprop))
                    DiffValue    = ($diff | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objprop))
                }
                $diffs += New-Object PSObject -Property $diffprops
            }
        }
        if ($diffs) {
            return ($diffs | Select-Object PropertyName, RefValue, DiffValue)
        }
    }
}

function Get-AuthToken {
    <#
    .SYNOPSIS
    Get Authorization Token
    .DESCRIPTION
    This function is used to generate the Authtoken for API Calls
    .EXAMPLE
    Get-AuthToken
    #>


    [CmdletBinding()]
    param (
    )

    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile

    Write-Verbose -Message "Using Subscription: $($azProfile.DefaultContext.Subscription.Name) from tenant $($azProfile.DefaultContext.Tenant.Id)"

    $script:subscriptionId = $azProfile.DefaultContext.Subscription.Id
    $script:tenantId = $azProfile.DefaultContext.Tenant.Id

    $profileClient = [Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient]::new($azProfile)
    $script:accessToken = $profileClient.AcquireAccessToken($script:tenantId)

    $script:authHeader = @{
        'Content-Type' = 'application/json'
        Authorization  = 'Bearer ' + $script:accessToken.AccessToken
    }

}

function Get-AzSentinelPlayBook {
    <#
      .SYNOPSIS
      Get Logic App Playbook
      .DESCRIPTION
      This function is used for resolving the Logic App and testing the compability with Azure Sentinel
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER Name
      Enter the Logic App name
      .EXAMPLE
      Get-AzSentinelPlayBook -Name ""
      This example will get search for the Logic app within the current subscripbtio and test to see if it's compatible for Sentinel
      .NOTES
      NAME: Get-AzSentinelPlayBook
    #>

    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

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

    begin {
        precheck
    }

    process {

        $triggerName = 'When_a_response_to_an_Azure_Sentinel_alert_is_triggered'

        if ($Name.Split('/').count -gt 1) {
            $uri = "https://management.azure.com/subscriptions/$($Name.Split('/')[2])/providers/Microsoft.Logic/workflows?api-version=2016-06-01"
            $Name = $Name.Split('/')[-1]
        }
        elseif ($SubscriptionId) {
            Write-Verbose "Getting LogicApp from Subscription $($subscriptionId)"
            $uri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.Logic/workflows?api-version=2016-06-01"
        }
        elseif ($script:subscriptionId) {
            Write-Verbose "Getting LogicApp from Subscription $($script:subscriptionId)"
            $uri = "https://management.azure.com/subscriptions/$($script:subscriptionId)/providers/Microsoft.Logic/workflows?api-version=2016-06-01"
        }
        else {
            $return = "No SubscriptionID provided"
            return $return
        }

        try {
            $logicappRaw = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader)
            $logicapp = $logicappRaw.value

            while ($logicappRaw.nextLink) {
                $logicappRaw = (Invoke-RestMethod -Uri $($logicappRaw.nextLink) -Headers $script:authHeader -Method Get)
                $logicapp += $logicappRaw.value
            }

            $playBook = $logicapp | Where-Object { $_.name -eq $Name }

            if ($playBook) {
                $uri1 = "https://management.azure.com$($playBook.id)/triggers/$($triggerName)/listCallbackUrl?api-version=2016-06-01"
                try {
                    $playbookTrigger = (Invoke-RestMethod -Uri $uri1 -Method Post -Headers $script:authHeader)
                    $playbookTrigger | Add-Member -NotePropertyName ResourceId -NotePropertyValue $playBook.id -Force

                    return $playbookTrigger
                }
                catch {
                    $return = "Playbook $($Name) doesn't start with 'When_a_response_to_an_Azure_Sentinel_alert_is_triggered' step! Error message: $($_.Exception.Message)"
                    Write-Error $return
                }
            }
            else {
                Write-Warning "Unable to find LogicApp $Name under Subscription Id: $($script:subscriptionId)"
            }
        }
        catch {
            $return = $_.Exception.Message
            Write-Error $return
        }
    }
}

function Get-AzSentinelResourceProvider {
    <#
    .SYNOPSIS
    Get AzSentinelResourceProvider
    .DESCRIPTION
    This function is used to get status of the required resource providers
    .PARAMETER NameSpace
    Enter the name of the namespace without 'Microsoft.'
    .EXAMPLE
    Get-AzSentinelResourceProvider -NameSpace 'OperationsManagement'
    #>

    param (
        [string]$NameSpace
    )

    $uri = "https://management.azure.com/subscriptions/$($script:subscriptionId)/providers/Microsoft.$($NameSpace)?api-version=2019-10-01"

    try {
        $invokeReturn = Invoke-RestMethod -Method Get -Uri $uri -Headers $script:authHeader
        return $invokeReturn
    }
    catch {
        $return = $_.Exception.Message
        Write-Error $return
        return $return
    }
}

function Get-LogAnalyticWorkspace {
    <#
    .SYNOPSIS
    Get log analytic workspace
    .DESCRIPTION
    This function is used by other function for getting the workspace infiormation and seting the right values for $script:workspace and $script:baseUri
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER FullObject
    If you want to return the full object data
    .EXAMPLE
    Get-LogAnalyticWorkspace -WorkspaceName ""
    This example will get the Workspace and set workspace and baseuri param on Script scope level
    .EXAMPLE
    Get-LogAnalyticWorkspace -WorkspaceName "" -FullObject
    This example will get the Workspace ands return the full data object
    .EXAMPLE
    Get-LogAnalyticWorkspace -SubscriptionId "" -WorkspaceName ""
    This example will get the workspace info from another subscrion than your "Azcontext" subscription
    .NOTES
    NAME: Get-LogAnalyticWorkspace
    #>

    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Switch]$FullObject
    )

    begin {
        precheck
    }

    process {
        if ($SubscriptionId) {
            Write-Verbose "Getting Worspace from Subscription $($subscriptionId)"
            $uri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.OperationalInsights/workspaces?api-version=2015-11-01-preview"
        }
        elseif ($script:subscriptionId) {
            Write-Verbose "Getting Worspace from Subscription $($script:subscriptionId)"
            $uri = "https://management.azure.com/subscriptions/$($script:subscriptionId)/providers/Microsoft.OperationalInsights/workspaces?api-version=2015-11-01-preview"
        }
        else {
            Write-Error "No SubscriptionID provided" -ErrorAction Stop
        }

        try {
            $workspaces = Invoke-webrequest -Uri $uri -Method get -Headers $script:authHeader -ErrorAction Stop
            $workspaceObject = ($workspaces.Content | ConvertFrom-Json).value | Where-Object { $_.name -eq $WorkspaceName }
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        if ($workspaceObject) {
            $Script:workspace = ($workspaceObject.id).trim()
            $script:workspaceId = $workspaceObject.properties.customerId
            Write-Verbose "Workspace is: $($Script:workspace)"
            $script:baseUri = "https://management.azure.com$($Script:workspace)"
            if ($FullObject) {
                return $workspaceObject
            }
            Write-Verbose ($workspaceObject | Format-List | Format-Table | Out-String)
            Write-Verbose "Found Workspace $WorkspaceName in RG $($workspaceObject.id.Split('/')[4])"
        }
        else {
            Write-Error "Unable to find workspace $WorkspaceName under Subscription Id: $($script:subscriptionId)"
        }
    }
}

function precheck {
  <#
  .SYNOPSIS
  PreCheck
  .DESCRIPTION
  This function is used as a precheck step by all the functions to test all the required authentication and properties.
  .EXAMPLE
  precheck
  Run the test
  .NOTES
  NAME: precheck
  #>


    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile

    if ($azProfile.Contexts.Count -ne 0) {
        if ($null -eq $script:accessToken ) {
            Get-AuthToken
        }
        elseif ($script:accessToken.ExpiresOn.DateTime - [datetime]::UtcNow.AddMinutes(-5) -le 0) {
            # if token expires within 5 minutes, request a new one
            Get-AuthToken
        }

        # Set the subscription from AzContext
        $script:subscriptionId = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext.Subscription.Id
    }
    else {
        Write-Error 'No subscription available, Please use Connect-AzAccount to login and select the right subscription'
        break
    }
}

function Set-AzSentinelResourceProvider {
    <#
    .SYNOPSIS
    Set AzSentinelResourceProvider
    .DESCRIPTION
    This function is enables the required Resource providers
    .PARAMETER NameSpace
    Enter the name of the namespace without 'Microsoft.'
    .EXAMPLE
    Set-AzSentinelResourceProvider -NameSpace 'OperationsManagementOperationsManagement'
    #>


    [OutputType([String])]
    param (
        [string]$NameSpace
    )

    $uri = "https://management.azure.com/subscriptions/$($script:subscriptionId)/providers/Microsoft.$($NameSpace)/register?api-version=2019-10-01"

    try {
        $invokeReturn = Invoke-RestMethod -Method Post -Uri $uri -Headers $script:authHeader
        Write-Verbose $invokeReturn
        do {
            $resourceProviderStatus = Get-AzSentinelResourceProvider -NameSpace $NameSpace
        }
        until ($resourceProviderStatus.registrationState -eq 'Registered')
        $return = "Successfully enabled Microsoft.$($NameSpace) on subscription $($script:subscriptionId). Status:$($resourceProviderStatus.registrationState)"
        return $return
    }
    catch {
        $return = $_.Exception.Message
        Write-Error $return
        return $return
    }
}

function Add-AzSentinelIncidentComment {
    <#
    .SYNOPSIS
    Add Azure Sentinel Incident comment
    .DESCRIPTION
    With this function you can add comment to existing Azure Sentinel Incident.
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER Name
    Enter the name of the incidnet in GUID format
    .PARAMETER CaseNumber
    Enter the case number to get specfiek details of a open case
    .PARAMETER Comment
    Enter Comment tekst to add comment to the incident
    .EXAMPLE
    Add-AzSentinelIncidentComment -WorkspaceName "" CaseNumber "" -Comment
    Add a comment to existing incidnet
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [guid]$Name,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [int]$CaseNumber,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Comment
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        Write-Verbose -Message "Using URI: $($uri)"

        if ($CaseNumber) {
            $incident = Get-AzSentinelIncident @arguments -CaseNumber $CaseNumber -All
        }
        elseif ($Name) {
            $incident = Get-AzSentinelIncident @arguments -Name $Name
        }
        else {
            Write-Error "Both CaseNumber and Name are empty" -ErrorAction Stop
        }

        if ($incident) {
            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/Cases/$($incident.name)/comments/$(New-Guid)?api-version=2019-01-01-preview"
            $body = @{
                "properties" = @{
                    "message" = $Comment
                }
            }

            Write-Verbose "Found incident with case number: $($incident.caseNumber)"

            try {
                $return = Invoke-WebRequest -Uri $uri -Method Put -Body ($body | ConvertTo-Json -Depth 99 -EnumsAsStrings) -Headers $script:authHeader
                return ($return.Content | ConvertFrom-Json).properties
            }
            catch {
                $return = $_.Exception.Message
                Write-Verbose $_
                Write-Error "Unable to update Incident $($incident.caseNumber) with error message $return"
                return $return
            }
        }
    }
}

function Rename-AzSentinelAlertRule {
    <#
      .SYNOPSIS
      Rename Azure Sentinel Alert Rule
      .DESCRIPTION
      With this function you can rename Azure Sentinel Alert rule
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER CurrentRuleName
      Enter the current name of the Alert rule
      .PARAMETER NewRuleName
      Enter the new name of the Alert rule
      .EXAMPLE
      Rename-AzSentinelAlertRule -WorkspaceName "" -CurrentRuleName "" -NewRuleName ""
      In this example you can rename the alert rule
    #>


    [cmdletbinding(SupportsShouldProcess)]
    [OutputType([String])]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

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

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

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        try {
            $rule = Get-AzSentinelAlertRule @arguments -RuleName $CurrentRuleName -ErrorAction Stop
        }
        catch {
            $return = $_.Exception.Message
            Write-Error $return
        }

        $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($rule.name)?api-version=2019-01-01-preview"

        $groupingConfiguration = [GroupingConfiguration]::new(
            $rule.incidentConfiguration.groupingConfiguration.GroupingConfigurationEnabled,
            $rule.incidentConfiguration.groupingConfiguration.ReopenClosedIncident,
            $rule.incidentConfiguration.groupingConfiguration.LookbackDuration,
            $rule.incidentConfiguration.groupingConfiguration.EntitiesMatchingMethod,
            $rule.incidentConfiguration.groupingConfiguration.GroupByEntities
        )

        $incidentConfiguration = [IncidentConfiguration]::new(
            $rule.incidentConfiguration.CreateIncident,
            $groupingConfiguration
        )

        $bodyAlertProp = [ScheduledAlertProp]::new(
            $rule.name,
            $NewRuleName,
            $rule.Description,
            $rule.Severity,
            $rule.Enabled,
            $rule.Query,
            $rule.QueryFrequency,
            $rule.QueryPeriod,
            $rule.TriggerOperator,
            $rule.TriggerThreshold,
            $rule.SuppressionDuration,
            $rule.SuppressionEnabled,
            $rule.Tactics,
            $rule.PlaybookName,
            $incidentConfiguration,
            $rule.AggregationKind
        )

        $body = [AlertRule]::new( $rule.name, $rule.etag, $bodyAlertProp, $rule.Id, 'Scheduled')

        try {
            $result = Invoke-RestMethod -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings) -ErrorAction Stop
            $return = "Successfully renamed rule $($CurrentRuleName) to $($NewRuleName) with status: $($result.StatusDescription)"
            return $return
        }
        catch {
            $return = $_.Exception.Message
            Write-Error "Rename failed with error $return"
        }
    }
}

function Remove-AzSentinelHuntingRule {
    <#
    .SYNOPSIS
    Remove Azure Sentinal Hunting Rules
    .DESCRIPTION
    With this function you can remove Azure Sentinal hunting rules from Powershell, if you don't provide andy Hunting rule name all rules will be removed
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER RuleName
    Enter the name of the rule that you wnat to remove
    .EXAMPLE
    Remove-AzSentinelHuntingRule -WorkspaceName "" -RuleName ""
    In this example the defined hunting rule will be removed from Azure Sentinel
    .EXAMPLE
    Remove-AzSentinelHuntingRule -WorkspaceName "" -RuleName "","", ""
    In this example you can define multiple hunting rules that will be removed
    .EXAMPLE
    Remove-AzSentinelHuntingRule -WorkspaceName ""
    In this example no hunting rule is specified, all hunting rules will be removed one by one. For each rule you need to confirm the action
    #>


    [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$RuleName
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($RuleName) {
            # remove defined rules
            foreach ($rule in $RuleName) {
                try {
                    $item = Get-AzSentinelHuntingRule @arguments -RuleName $rule -ErrorAction Stop
                }
                catch {
                    Write-Error $_.Exception.Message
                    break
                }

                if ($item) {
                    $uri = "$script:baseUri/savedSearches/$($item.name)?api-version=2017-04-26-preview"

                    if ($PSCmdlet.ShouldProcess("Do you want to remove: $rule")) {
                        Write-Output $item
                        try {
                            $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader
                            Write-Output "Successfully removed hunting rule: $($rule) with status: $($result.StatusDescription)"
                        }
                        catch {
                            Write-Verbose $_
                            Write-Error "Unable to remove rule: $($rule) with error message: $($_.Exception.Message)" -ErrorAction Continue
                        }
                    }
                    else {
                        Write-Output "No change have been made for hunting rule: $rule"
                    }
                }
                else {
                    Write-Warning "Hunting rule $rule not found in $WorkspaceName"
                }
            }
        }
        else {
            Write-Warning "No hunting rule selected, All hunting rules will be removed one by one!"
            Get-AzSentinelHuntingRule @arguments -Filter "Hunting Queries" | ForEach-Object {
                $uri = "$script:baseUri/savedSearches/$($_.name)?api-version=2017-04-26-preview"
                if ($PSCmdlet.ShouldProcess("Do you want to remove: $($_.displayName)")) {
                    try {
                        $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader
                        Write-Output "Successfully removed hunting rule: $($_.displayName) with status: $($result.StatusDescription)"
                    }
                    catch {
                        Write-Verbose $_
                        Write-Error "Unable to remove rule: $($_.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                    }
                }
                else {
                    Write-Output "No change have been made for hunting rule: $($_.displayName)"
                }
            }
        }
    }
}

function Remove-AzSentinelAlertRuleAction {
    <#
      .SYNOPSIS
      Remove Azure Sentinel Alert rule Action
      .DESCRIPTION
      This function can be used to see if an action is attached to the alert rule, if so then the configuration will be returned
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER RuleName
      Enter the name of the Alert rule
      .PARAMETER RuleId
      Enter the Alert Rule ID that you want to configure
      .EXAMPLE
      Remove-AzSentinelAlertRuleAction -WorkspaceName "" -RuleName "AlertRule01"
      This example will get the Workspace ands return the full data object
      .NOTES
      NAME: Remove-AzSentinelAlertRuleAction
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleId
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($RuleName) {
            $result = Get-AzSentinelAlertRuleAction @arguments -RuleName $RuleName
        }
        elseif ($RuleId) {
            $result = Get-AzSentinelAlertRuleAction @arguments -RuleId $RuleId
        }
        else {
            Write-Error "No Alert Name or ID is provided"
        }

        if ($result) {
            $uri = "$($Script:baseUri)/providers/Microsoft.SecurityInsights/alertRules/$($result.id.split('asicustomalertsv3_')[-1])?api-version=2019-01-01-preview"
            Write-Verbose $uri

            if ($PSCmdlet.ShouldProcess("Do you want to remove Alert Rule action for rule: $($RuleName)")) {
                try {
                    $return = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader
                    Write-Verbose $return
                    Write-Verbose "Rule action $($result.properties.logicAppResourceId.Split('/')[-1]) removed for rule $($RuleName) with status: $($return.StatusCode)"
                    return $return.StatusCode
                }
                catch {
                    Write-Verbose $_
                    return $_.Exception.Message
                }
            }
        }
        else {
            Write-Output "No Alert Action found for Rule: $($RuleName)"
        }
    }
}

function Remove-AzSentinelAlertRule {
    <#
    .SYNOPSIS
    Remove Azure Sentinal Alert Rules
    .DESCRIPTION
    With this function you can remove Azure Sentinal Alert rules from Powershell, if you don't provide andy Rule name all rules will be removed
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER RuleName
    Enter the name of the rule that you wnat to remove
    .EXAMPLE
    Remove-AzSentinelAlertRule -WorkspaceName "" -RuleName ""
    In this example the defined rule will be removed from Azure Sentinel
    .EXAMPLE
    Remove-AzSentinelAlertRule -WorkspaceName "" -RuleName "","", ""
    In this example you can define multiple rules that will be removed
    .EXAMPLE
    Remove-AzSentinelAlertRule -WorkspaceName ""
    In this example no rule is specified, all rules will be removed one by one. For each rule you need to confirm the action
    #>


    [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$RuleName
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($RuleName) {
            # remove defined rules
            foreach ($rule in $RuleName) {

                try {
                    $item = Get-AzSentinelAlertRule @arguments -RuleName $rule -WarningAction SilentlyContinue -ErrorAction Stop
                }
                catch {
                    $return = $_.Exception.Message
                    Write-Error $return
                }

                if ($item) {
                    $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($item.name)?api-version=2019-01-01-preview"

                    if ($PSCmdlet.ShouldProcess("Do you want to remove: $rule")) {
                        Write-Output $item
                        try {
                            $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader
                            Write-Output "Successfully removed rule: $($rule) with status: $($result.StatusDescription)"
                        }
                        catch {
                            Write-Verbose $_
                            Write-Error "Unable to remove rule: $($rule) with error message: $($_.Exception.Message)" -ErrorAction Continue
                        }
                    }
                    else {
                        Write-Output "No change have been made for rule: $rule"
                    }
                }
                else {
                    Write-Warning "$rule not found in $WorkspaceName"
                }
            }
        }
        else {
            Write-Warning "No Rule selected, All rules will be removed one by one!"
            Get-AzSentinelAlertRule @arguments | ForEach-Object {
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($_.name)?api-version=2019-01-01-preview"

                if ($PSCmdlet.ShouldProcess("Do you want to remove: $($_.displayName)")) {
                    try {
                        $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader
                        Write-Output "Successfully removed rule: $($_.displayName) with status: $($result.StatusDescription)"
                    }
                    catch {
                        Write-Verbose $_
                        Write-Error "Unable to remove rule: $($_.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                    }
                }
                else {
                    Write-Output "No change have been made for rule: $($_.displayName)"
                }
            }
        }
    }
}

function New-AzSentinelHuntingRule {
    <#
    .SYNOPSIS
    Create Azure Sentinal Hunting Rule
    .DESCRIPTION
    Use this function to creates Azure Sentinal Hunting rule
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER DisplayName
    Enter the Display name for the hunting rule
    .PARAMETER Description
    Enter the Description for the hunting rule
    .PARAMETER Tactics
    Enter the Tactics, valid values: "InitialAccess", "Persistence", "Execution", "PrivilegeEscalation", "DefenseEvasion", "CredentialAccess", "LateralMovement", "Discovery", "Collection", "Exfiltration", "CommandAndControl", "Impact"
    .PARAMETER Query
    Enter the querry in KQL format
    .EXAMPLE
    New-AzSentinelHuntingRule -WorkspaceName "" -DisplayName "" -Description "" -Tactics "","" -Query ''
    In this example you create a new hunting rule by defining the rule properties from CMDLET
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter(Mandatory)]
        [string] $DisplayName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $Query,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $Description,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [Tactics[]] $Tactics

    )

    begin {
        precheck
    }
    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }


        $item = @{ }
        $content = $null
        $body = @{ }
        $compareResult1 = $null
        $compareResult2 = $null
        $compareResult = $null

        Write-Verbose -Message "Creating new Hunting rule: $($DisplayName)"

        try {
            Write-Verbose -Message "Get hunting rule $DisplayName"
            $content = Get-AzSentinelHuntingRule @arguments -RuleName $DisplayName -WarningAction SilentlyContinue

            if ($content) {
                Write-Verbose -Message "Hunting rule $($DisplayName) exists in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.eTag -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force


                $uri = "$script:baseUri/savedSearches/$($content.name)?api-version=2017-04-26-preview"
            }
            else {
                Write-Verbose -Message "Hunting rule $($DisplayName) doesn't exists in Azure Sentinel"

                $guid = (New-Guid).Guid

                $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/savedSearches/$guid" -Force

                $uri = "$script:baseUri/savedSearches/$($guid)?api-version=2017-04-26-preview"
            }
        }
        catch {
            Write-Verbose $_
            Write-Error "Unable to connect to APi to get Analytic rules with message: $($_.Exception.Message)" -ErrorAction Stop
        }

        [PSCustomObject]$body = @{
            "name"       = $item.name
            "eTag"       = $item.etag
            "id"         = $item.id
            "properties" = @{
                'Category'             = 'Hunting Queries'
                'DisplayName'          = $DisplayName
                'Query'                = $Query
                [pscustomobject]'Tags' = @(
                    @{
                        'Name'  = "description"
                        'Value' = $Description
                    },
                    @{
                        "Name"  = "tactics"
                        "Value" = $Tactics -join ','
                    },
                    @{
                        "Name"  = "createdBy"
                        "Value" = ""
                    },
                    @{
                        "Name"  = "createdTimeUtc"
                        "Value" = ""
                    }
                )
            }
        }

        #return $content
        if ($content) {
            $compareResult1 = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, Tags, Version) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, Tags, Version)
            $compareResult2 = Compare-Policy -ReferenceTemplate ($content.Tags | Where-Object { $_.name -eq "tactics" }) -DifferenceTemplate ($body.Properties.Tags | Where-Object { $_.name -eq "tactics" })
            $compareResult = [PSCustomObject]$compareResult1 + [PSCustomObject]$compareResult2

            if ($compareResult) {
                Write-Output "Found Differences for hunting rule: $($DisplayName)"
                Write-Output ($compareResult | Format-Table | Out-String)

                if ($PSCmdlet.ShouldProcess("Do you want to update hunting rule: $($DisplayName)")) {
                    try {
                        Write-Output ($body.properties | Format-Table)

                        $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                        Write-Output "Successfully updated hunting rule: $($DisplayName) with status: $($result.StatusDescription)"
                    }
                    catch {
                        Write-Verbose $_
                        Write-Error "Unable to invoke webrequest with error message: $($_.Exception.Message)" -ErrorAction Stop
                    }
                }
                else {
                    Write-Output "No change have been made for rule $($DisplayName), deployment aborted"
                }
            }
            else {
                Write-Output "Hunting rule $($DisplayName) is compliance, nothing to do"
                Write-Output ($body.properties | Format-Table)
            }
        }
        else {
            Write-Verbose "Creating new hunting rule: $($DisplayName)"

            try {

                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                Write-Output "Successfully created hunting rule: $($DisplayName) with status: $($result.StatusDescription)"
                Write-Output ($body.properties | Format-Table)
            }
            catch {
                Write-Verbose $_
                Write-Error "Unable to invoke webrequest with error message: $($_.Exception.Message)" -ErrorAction Stop
            }
        }
    }
}

function New-AzSentinelAlertRuleAction {
    <#
      .SYNOPSIS
      Create Azure Sentinal Alert Rule Action
      .DESCRIPTION
      Use this function to creates Azure Sentinal Alert rule action
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER PlayBookName
      Enter the Playbook name that you want to assign to the alert rule
      .PARAMETER RuleName
      Enter the Alert Rule name that you want to configure
      .PARAMETER RuleId
      Enter the Alert Rule ID that you want to configure
      .EXAMPLE
      New-AzSentinelAlertRuleAction -WorkspaceName "" -PlayBookName "Playbook01" -RuleName "AlertRule01"
      New-AzSentinelAlertRuleAction -WorkspaceName "" -PlayBookName "Playbook01" -RuleId 'b6103d42-d2fb-4f35-xxx-c76a7f31ee4e'
      In this example you you assign the playbook to the Alert rule
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$PlayBookName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleId
    )
    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($RuleName) {
            $alertId = (Get-AzSentinelAlertRule @arguments -RuleName $RuleName -ErrorAction SilentlyContinue).name
        }
        elseif ($RuleId) {
            $alertId = $RuleId
        }
        else {
            Write-Error "No Alert Name or ID is provided"
        }

        $action = $null


        if ($SubscriptionId) {
            $playBook = Get-AzSentinelPlayBook -SubscriptionId $SubscriptionId -Name $PlayBookName
        }
        else {
            $playBook = Get-AzSentinelPlayBook -Name $PlayBookName
        }

        $action = Get-AzSentinelAlertRuleAction @arguments -RuleId $alertId -ErrorAction SilentlyContinue

        if (($null -eq $action) -or ((($action.properties.logicAppResourceId).Split('/')[-1]) -ne $PlayBookName.Split('/')[-1])) {
            $guid = New-Guid

            $body = @{
                "id"         = "$($Script:baseUri)/providers/Microsoft.SecurityInsights/alertRules/$($alertId)/actions/$guid"
                "name"       = $guid
                "type"       = "Microsoft.SecurityInsights/alertRules/actions"
                "properties" = @{
                    "ruleId"             = $alertId
                    "triggerUri"         = $playBook.value
                    "logicAppResourceId" = $playBook.ResourceId
                }
            }

            $uri = "$($Script:baseUri)/providers/Microsoft.SecurityInsights/alertRules/$($alertId)/actions/$($guid)?api-version=2019-01-01-preview"

            try {
                $return = Invoke-WebRequest -Method Put -Uri $uri -Headers $Script:authHeader -Body ($body | ConvertTo-Json -Depth 10)
                Write-Verbose "Successfully created Action for Rule: $($RuleName) with Playbook $($PlayBookName) Status: $($return.StatusDescription)"
                return $return.StatusDescription
            }
            catch {
                Write-Error "Unable to create Action for Rule: $($RuleName) with Playbook $($PlayBookName) Error: $($_.Exception.Message)"
                Write-Verbose $_
                return $_.Exception.Message
            }
        }
        elseif ((($action.properties.logicAppResourceId).Split('/')[-1]) -eq $PlayBookName) {
            Write-Output "Alert Rule: $($alertId) is already assigned to playbook: $(($action.properties.logicAppResourceId).Split('/')[-1])"
        }

        else {
            #nothing?
        }
    }
}

function New-AzSentinelAlertRule {
    <#
    .SYNOPSIS
    Create Azure Sentinal Alert Rules
    .DESCRIPTION
    Use this function creates Azure Sentinal Alert rules from provided CMDLET
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER Kind
    The alert rule kind
    .PARAMETER DisplayName
    The display name for alerts created by this alert rule.
    .PARAMETER Description
    The description of the alert rule.
    .PARAMETER Severity
    Enter the Severity, valid values: Medium", "High", "Low", "Informational"
    .PARAMETER Enabled
    Determines whether this alert rule is enabled or disabled.
    .PARAMETER Query
    The query that creates alerts for this rule.
    .PARAMETER QueryFrequency
    Enter the query frequency, example: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER QueryPeriod
    Enter the query period, exmaple: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER TriggerOperator
    Select the triggert Operator, valid values are: "GreaterThan", "FewerThan", "EqualTo", "NotEqualTo"
    .PARAMETER TriggerThreshold
    Enter the trigger treshold
    .PARAMETER SuppressionDuration
    Enter the suppression duration, example: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER SuppressionEnabled
    Set $true to enable Suppression or $false to disable Suppression
    .PARAMETER Tactics
    Enter the Tactics, valid values: "InitialAccess", "Persistence", "Execution", "PrivilegeEscalation", "DefenseEvasion", "CredentialAccess", "LateralMovement", "Discovery", "Collection", "Exfiltration", "CommandAndControl", "Impact"
    .PARAMETER PlaybookName
    Enter the Logic App name that you want to configure as playbook trigger
    .PARAMETER CreateIncident
    Create incidents from alerts triggered by this analytics rule
    .PARAMETER GroupingConfigurationEnabled
    Group related alerts, triggered by this analytics rule, into incidents
    .PARAMETER ReopenClosedIncident
    Re-open closed matching incidents
    .PARAMETER LookbackDuration
    Limit the group to alerts created within the selected time frame
    .PARAMETER EntitiesMatchingMethod
    Group alerts triggered by this analytics rule into a single incident by
    .PARAMETER GroupByEntities
    Grouping alerts into a single incident if the selected entities match:
    .PARAMETER AggregationKind
    Configure how rule query results are grouped into alerts
    .PARAMETER AlertRuleTemplateName
    The Name of the alert rule template used to create this rule
    .PARAMETER ProductFilter
    The alerts' productName on which the cases will be generated
    .PARAMETER SeveritiesFilter
    The alerts' severities on which the cases will be generated
    .PARAMETER DisplayNamesFilter
    The alerts' displayNames on which the cases will be generated
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -DisplayName "" -Description "" -Severity -Enabled $true -Query '' -QueryFrequency "" -QueryPeriod "" -TriggerOperator -TriggerThreshold -SuppressionDuration "" -SuppressionEnabled $false -Tactics @("","") -PlaybookName ""
    Example on how to create a scheduled rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind Fusion -DisplayName "Advanced Multistage Attack Detection" -Enabled $true -AlertRuleTemplateName "f71aba3d-28fb-450b-b192-4e76a83015c8"
    Example on how to create a Fusion rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind MLBehaviorAnalytics -DisplayName "(Preview) Anomalous SSH Login Detection" -Enabled $true -AlertRuleTemplateName "fa118b98-de46-4e94-87f9-8e6d5060b60b"
    Example on how to create a MLBehaviorAnalytics rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind MicrosoftSecurityIncidentCreation -DisplayName "" -Description "" -Enabled $true -ProductFilter "" -SeveritiesFilter "","" -DisplayNamesFilter ""
    Example on how to create a MicrosoftSecurityIncidentCreation rule
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false)]
        [Kind]$Kind = 'Scheduled',

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

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

        [Parameter(Mandatory = $false)]
        [Severity]$Severity,

        [Parameter(Mandatory = $false)]
        [bool]$Enabled,

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

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

        [parameter(Mandatory = $false)]
        [string]$QueryPeriod,

        [Parameter(Mandatory = $false)]
        [TriggerOperator]$TriggerOperator,

        [Parameter(Mandatory = $false)]
        [Int]$TriggerThreshold,

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

        [Parameter(Mandatory = $false)]
        [bool]$SuppressionEnabled,

        [Parameter(Mandatory = $false)]
        #[Tactics[]] $Tactics,
        [string[]]$Tactics,

        [Parameter(Mandatory = $false)]
        [string[]]$PlaybookName = '',

        [Parameter(Mandatory = $false)]
        [bool]$CreateIncident,

        [Parameter(Mandatory = $false)]
        [bool]$GroupingConfigurationEnabled,

        [Parameter(Mandatory = $false)]
        [bool]$ReopenClosedIncident,

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

        [Parameter(Mandatory = $false)]
        [MatchingMethod]$EntitiesMatchingMethod,

        [Parameter(Mandatory = $false)]
        #[groupByEntities[]]$GroupByEntities,
        [string[]]$GroupByEntities,

        [Parameter(Mandatory = $false)]
        [AggregationKind]$AggregationKind,

        #Fusion & MLBehaviorAnalytics
        [Parameter(Mandatory = $false)]
        [string]$AlertRuleTemplateName,

        #MicrosoftSecurityIncidentCreation
        [Parameter(Mandatory = $false)]
        [string]$ProductFilter,

        [Parameter(Mandatory = $false)]
        [Severity[]]$SeveritiesFilter,

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

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        $item = @{ }

        Write-Verbose -Message "Creating new rule: $($DisplayName)"

        try {
            $content = Get-AzSentinelAlertRule @arguments -RuleName $DisplayName -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        if ($content) {
            Write-Verbose -Message "Rule $($DisplayName) exists in Azure Sentinel"

            $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
            $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.eTag -Force
            $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
        }
        else {
            Write-Verbose -Message "Rule $($DisplayName) doesn't exists in Azure Sentinel"

            $guid = (New-Guid).Guid

            $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
            $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
            $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force

            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
        }

        if ($Kind -eq 'Scheduled') {

            try {
                $groupingConfiguration = [GroupingConfiguration]::new(
                    $GroupingConfigurationEnabled,
                    $ReopenClosedIncident,
                    $LookbackDuration,
                    $EntitiesMatchingMethod,
                    $GroupByEntities
                )

                $incidentConfiguration = [IncidentConfiguration]::new(
                    $CreateIncident,
                    $groupingConfiguration
                )

                $bodyAlertProp = [ScheduledAlertProp]::new(
                    $item.name,
                    $DisplayName,
                    $Description,
                    $Severity,
                    $Enabled,
                    $Query,
                    $QueryFrequency,
                    $QueryPeriod,
                    $TriggerOperator,
                    $TriggerThreshold,
                    $SuppressionDuration,
                    $SuppressionEnabled,
                    $Tactics,
                    $PlaybookName,
                    $incidentConfiguration,
                    $AggregationKind
                )

                $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Scheduled')
            }
            catch {
                Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop
            }

            if ($content) {
                if ($PlaybookName -or $content.playbookName) {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name)
                }
                else {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, PlaybookName) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, PlaybookName)
                }

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                    if (($compareResult | Where-Object PropertyName -eq "playbookName").DiffValue) {
                        foreach ($playbook in ($body.Properties.PlaybookName)) {
                            $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName $playbook -RuleId $($body.Properties.Name) -confirm:$false
                            $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                        }
                    }
                    elseif (($compareResult | Where-Object PropertyName -eq "playbookName").RefValue) {
                        $PlaybookResult = Remove-AzSentinelAlertRuleAction @arguments -RuleId $($body.Name) -Confirm:$false
                        $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                    }
                    else {
                        #nothing
                    }

                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    return $return
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    return $return

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
            else {
                Write-Verbose "Creating new rule: $($DisplayName)"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                    if (($body.Properties.PlaybookName)) {
                        foreach ($playbook in ($body.Properties.PlaybookName)) {
                            New-AzSentinelAlertRuleAction @arguments -PlayBookName $playbook -RuleId $($body.Properties.Name) -confirm:$false
                            $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                        }
                    }

                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                    return $return
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                    return $return

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
        }

        if ($Kind -eq 'Fusion') {

            $bodyAlertProp = [Fusion]::new(
                $Enabled,
                $AlertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Fusion')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        if ($Kind -eq 'MLBehaviorAnalytics') {

            $bodyAlertProp = [MLBehaviorAnalytics]::new(
                $Enabled,
                $AlertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MLBehaviorAnalytics')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        if ($Kind -eq 'MicrosoftSecurityIncidentCreation') {

            $bodyAlertProp = [MicrosoftSecurityIncidentCreation]::new(
                $DisplayName,
                $Description,
                $Enabled,
                $ProductFilter,
                $SeveritiesFilter,
                $DisplayNamesFilter
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MicrosoftSecurityIncidentCreation')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }
    }
}

function Import-AzSentinelHuntingRule {
    <#
    .SYNOPSIS
    Import Azure Sentinal Hunting rule
    .DESCRIPTION
    This function imports Azure Sentinal Hunnting rules from JSON and YAML config files.
    This way you can manage your Hunting rules dynamic from JSON or multiple YAML files
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER SettingsFile
    Path to the JSON or YAML file for the Hunting rules
    .EXAMPLE
    Import-AzSentinelHuntingRule -WorkspaceName "infr-weu-oms-t-7qodryzoj6agu" -SettingsFile ".\examples\HuntingRules.json"
    In this example all the rules configured in the JSON file will be created or updated
    .EXAMPLE
    Import-AzSentinelHuntingRule -WorkspaceName "" -SettingsFile ".\examples\HuntingRules.yaml"
    In this example all the rules configured in the YAML file will be created or updated
    .EXAMPLE
    Get-Item .\examples\HuntingRules*.json | Import-AzSentinelHuntingRule -WorkspaceName ""
    In this example you can select multiple JSON files and Pipeline it to the SettingsFile parameter
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateScript( { (Test-Path -Path $_) -and ($_.Extension -in '.json', '.yaml', '.yml') })]
        [System.IO.FileInfo] $SettingsFile
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        $item = @{ }

        if ($SettingsFile.Extension -eq '.json') {
            try {
                $content = (Get-Content $SettingsFile -Raw | ConvertFrom-Json -ErrorAction Stop)
                if ($content.analytics){
                    $hunting = $content.analytics
                }
                else {
                    $hunting = $content.Hunting
                }

                Write-Verbose -Message "Found $($hunting.count) rules"
            }
            catch {
                Write-Verbose $_
                Write-Error -Message 'Unable to convert JSON file' -ErrorAction Stop
            }
        }
        elseif ($SettingsFile.Extension -in '.yaml', 'yml') {
            try {
                $hunting = [pscustomobject](Get-Content $SettingsFile -Raw | ConvertFrom-Yaml -ErrorAction Stop)
                $hunting | Add-Member -MemberType NoteProperty -Name DisplayName -Value $hunting.name
                Write-Verbose -Message 'Found compatibel yaml file'
            }
            catch {
                Write-Verbose $_
                Write-Error -Message 'Unable to convert yaml file' -ErrorAction Stop
            }
        }

        try {
            $allRulesContent = Get-AzSentinelHuntingRule @arguments -RuleName $($hunting.displayName) -WarningAction SilentlyContinue -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        foreach ($item in $hunting) {
            Write-Output "Started with Hunting rule: $($item.displayName)"

            try {
                Write-Verbose -Message "Get rule $($item.description)"

                $content = $allRulesContent | Where-Object displayName -eq $item.displayName

                if ($content) {
                    Write-Verbose -Message "Hunting rule $($item.displayName) exists in Azure Sentinel"

                    $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                    $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force
                    $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

                    $uri = "$script:baseUri/savedSearches/$($content.name)?api-version=2017-04-26-preview"
                }
                else {
                    Write-Verbose -Message "Hunting rule $($item.displayName) doesn't exists in Azure Sentinel"

                    $guid = (New-Guid).Guid

                    $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                    $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                    $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/savedSearches/$guid" -Force

                    $uri = "$script:baseUri/savedSearches/$($guid)?api-version=2017-04-26-preview"
                }
            }
            catch {
                Write-Verbose $_
                Write-Error "Unable to connect to APi to get Analytic rules with message: $($_.Exception.Message)" -ErrorAction Stop
            }

            [PSCustomObject]$body = @{
                "name"       = $item.name
                "eTag"       = $item.etag
                "id"         = $item.id
                "properties" = @{
                    'Category'             = 'Hunting Queries'
                    'DisplayName'          = [string]$item.displayName
                    'Query'                = [string]$item.query
                    [pscustomobject]'Tags' = @(
                        @{
                            'Name'  = "description"
                            'Value' = [string]$item.description
                        },
                        @{
                            "Name"  = "tactics"
                            "Value" = [Tactics[]] $item.tactics -join ','
                        },
                        @{
                            "Name"  = "createdBy"
                            "Value" = ""
                        },
                        @{
                            "Name"  = "createdTimeUtc"
                            "Value" = ""
                        }
                    )
                }
            }

            if ($content) {
                $compareResult1 = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, Tags, Version) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, Tags, Version)
                $compareResult2 = Compare-Policy -ReferenceTemplate ($content.Tags | Where-Object { $_.name -eq "tactics" }) -DifferenceTemplate ($body.Properties.Tags | Where-Object { $_.name -eq "tactics" })
                $compareResult = [PSCustomObject]$compareResult1 + [PSCustomObject]$compareResult2

                if ($compareResult) {
                    Write-Output "Found Differences for hunting rule: $($item.displayName)"
                    Write-Output ($compareResult | Format-Table | Out-String)

                    if ($PSCmdlet.ShouldProcess("Do you want to update hunting rule: $($body.Properties.DisplayName)")) {
                        try {
                            $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                            Write-Output "Successfully updated hunting rule: $($item.displayName) with status: $($result.StatusDescription)"
                            Write-Output ($body.Properties | Format-List | Format-Table | Out-String)
                        }
                        catch {
                            Write-Verbose $_
                            Write-Error "Unable to invoke webrequest with error message: $($_.Exception.Message)" -ErrorAction Continue
                        }
                    }
                    else {
                        Write-Output "No change have been made for hunting rule $($item.displayName), deployment aborted"
                    }
                }
                else {
                    Write-Output "Hunting rule $($item.displayName) is compliance, nothing to do"
                    Write-Output ($body.Properties | Format-List | Format-Table | Out-String)
                }
            }
            else {
                Write-Verbose "Creating new rule: $($item.displayName)"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                    Write-Output "Successfully created hunting rule: $($item.displayName) with status: $($result.StatusDescription)"
                    Write-Output ($body.Properties | Format-List | Format-Table | Out-String)
                }
                catch {
                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
        }
    }
}

function Import-AzSentinelDataConnector {
    <#
    .SYNOPSIS
    Import Azure Sentinel Data Connectors
    .DESCRIPTION
    This function imports Azure Sentinel Data Connectors
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER SettingsFile
    Path to the JSON file for the Data Connectors
    .EXAMPLE
    Import-AzSentinelDataConnector -WorkspaceName "" -SettingsFile ".\examples\DataConnectors.json"
    In this example all the Data Conenctors configured in the JSON file will be created or updated
    #>


    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateScript( { (Test-Path -Path $_) -and ($_.Extension -in '.json') })]
        [System.IO.FileInfo] $SettingsFile
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $script:SubscriptionId
                }
            }
        }

        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        if ($SettingsFile.Extension -eq '.json') {
            try {
                $connectors = Get-Content -Raw $SettingsFile | ConvertFrom-Json -Depth 99
            }
            catch {
                Write-Verbose $_
                Write-Error -Message 'Unable to import JSON file' -ErrorAction Stop
            }
        }
        else {
            Write-Error -Message 'Unsupported extension for SettingsFile' -ErrorAction Stop
        }

        <#
            Get all the DataConenctors
        #>

        $enabledDataConnectors = Get-AzSentinelDataConnector @arguments -ErrorAction SilentlyContinue

        <#
            Get AzureActivityLog connector data
        #>

        $azureActivityLog = Get-AzSentinelDataConnector @arguments -DataSourceName 'AzureActivityLog' -ErrorAction SilentlyContinue

        foreach ($item in $connectors.AzureActivityLog) {
            if ($null -ne $azureActivityLog){
                $azureActivity = $azureActivityLog | Where-Object { $_.properties.linkedResourceId.Split('/')[2] -eq $item.subscriptionId }
            }
            else {
                $azureActivity
            }

            if ($azureActivity) {
                Write-Host "AzureActivityLog is already enabled on '$($item.subscriptionId)'"
            }
            else {
                $name = ($item.subscriptionId).Replace('-', '')
                $connectorBody = @{
                    id         = "$script:Workspace/datasources/$name"
                    name       = $name
                    type       = 'Microsoft.OperationalInsights/workspaces/datasources'
                    kind       = 'AzureActivityLog'
                    properties = @{
                        linkedResourceId = "/subscriptions/$($item.subscriptionId)/providers/microsoft.insights/eventtypes/management"
                    }
                }

                $uri = "$baseUri/datasources/$($name)?api-version=2020-03-01-preview"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($connectorBody | ConvertTo-Json -Depth 4 -EnumsAsStrings)

                    Write-Host "Successfully enabled AzureActivityLog for: $($item.subscriptionId) with status: $($result.StatusDescription)"
                }
                catch {
                    $errorReturn = $_.Exception.Message
                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest with error message: $($errorReturn)" -ErrorAction Stop
                }
            }
        }

        #AzureSecurityCenter connector
        foreach ($item in $connectors.AzureSecurityCenter) {
            if ($null -ne $enabledDataConnectors){
                $azureSecurityCenter = $enabledDataConnectors | Where-Object { $_.Kind -eq "AzureSecurityCenter" -and $_.properties.subscriptionId -eq $item.subscriptionId }
            }
            else {
                $azureSecurityCenter
            }
            $skip = $false

            if ($null -ne $azureSecurityCenter) {
                if ($azureSecurityCenter.properties.dataTypes.alerts.state -eq $item.state) {
                    Write-Host "AzureSecurityCenter is already '$($item.state)' for subscription '$($azureSecurityCenter.properties.subscriptionId)'"
                    $skip = $true
                }
                else {
                    $connectorBody = @{
                        id         = $azureSecurityCenter.id
                        name       = $azureSecurityCenter.name
                        etag       = $azureSecurityCenter.etag
                        type       = 'Microsoft.SecurityInsights/dataConnectors'
                        kind       = 'AzureSecurityCenter'
                        properties = @{
                            subscriptionId = $azureSecurityCenter.properties.subscriptionId
                            dataTypes      = @{
                                alerts = @{
                                    state = $item.state
                                }
                            }
                        }
                    }
                }
            }
            else {
                $guid = (New-Guid).Guid

                $connectorBody = @{
                    id         = "$script:Workspace/providers/Microsoft.SecurityInsights/dataConnectors/$guid"
                    name       = $guid
                    type       = 'Microsoft.SecurityInsights/dataConnectors'
                    kind       = 'AzureSecurityCenter'
                    properties = @{
                        subscriptionId = $item.subscriptionId
                        dataTypes      = @{
                            alerts = @{
                                state = $item.state
                            }
                        }
                    }
                }
            }

            if ($skip -eq $false) {
                # Enable or update AzureSecurityCenter with http put method
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/dataConnectors/$($connectorBody.name)?api-version=2020-01-01"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($connectorBody | ConvertTo-Json -Depth 4 -EnumsAsStrings)

                    Write-Host "Successfully enabled AzureSecurityCenter for: $($item.subscriptionId) with status: $($result.StatusDescription)"

                }
                catch {
                    $errorReturn = $_
                    $errorResult = ($errorReturn | ConvertFrom-Json ).error
                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop
                }
            }
        }

        #ThreatIntelligenceTaxii
        foreach ($item in $connectors.ThreatIntelligenceTaxii) {
            if ($enabledDataConnectors){
                $threatIntelligenceTaxii = $enabledDataConnectors | Where-Object { $_.Kind -eq "ThreatIntelligenceTaxii" -and $_.properties.friendlyName -eq $item.friendlyName }
            }
            else {
                $threatIntelligenceTaxii
            }
            $skip = $false

            if ($null -ne $threatIntelligenceTaxii) {

                if ($threatIntelligenceTaxii.properties.dataTypes.taxiiClient.state -eq $item.state) {
                    Write-Host "ThreatIntelligenceTaxii is already $($item.state) for '$($item.friendlyName)'"
                    $skip = $true
                }
                else {
                    # Compose body for connector update scenario
                    $connectorBody = @{
                        id         = $threatIntelligenceTaxii.id
                        name       = $threatIntelligenceTaxii.name
                        etag       = $threatIntelligenceTaxii.etag
                        type       = 'Microsoft.SecurityInsights/dataConnectors'
                        kind       = 'ThreatIntelligenceTaxii'
                        properties = @{
                            tenantId     = $script:tenantId
                            workspaceId  = $script:workspaceId
                            friendlyName = $item.friendlyName
                            taxiiServer  = $item.taxiiServer
                            collectionId = $item.collectionId
                            username     = $item.username
                            password     = $item.password
                            taxiiClients = $null
                            dataTypes    = @{
                                taxiiClient = @{
                                    state = $item.state
                                }
                            }
                        }
                    }
                }
            }
            else {
                $guid = (New-Guid).Guid
                # Compose body for connector enable scenario
                $connectorBody = @{
                    id         = "$script:Workspace/providers/Microsoft.SecurityInsights/dataConnectors/$guid"
                    name       = $guid
                    type       = 'Microsoft.SecurityInsights/dataConnectors'
                    kind       = 'ThreatIntelligenceTaxii'
                    properties = @{
                        tenantId     = $script:tenantId
                        workspaceId  = $script:workspaceId
                        friendlyName = $item.friendlyName
                        taxiiServer  = $item.taxiiServer
                        collectionId = $item.collectionId
                        username     = $item.username
                        password     = $item.password
                        taxiiClients = $null
                        dataTypes    = @{
                            taxiiClient = @{
                                state = $item.state
                            }
                        }
                    }
                }
            }

            if ($skip -eq $false) {
                # Enable or update ThreatIntelligenceTaxii
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/dataConnectors/$($connectorBody.name)?api-version=2020-01-01"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($connectorBody | ConvertTo-Json -Depth 4 -EnumsAsStrings)

                    Write-Host "Successfully enabled ThreatIntelligenceTaxii for: $($item.friendlyName) with status: $($result.StatusDescription)"
                }
                catch {
                    $errorReturn = $_.Exception.Message
                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest with error message: $($errorReturn)" -ErrorAction Stop
                }
            }
        }
    }
}

function Import-AzSentinelAlertRule {
    <#
    .SYNOPSIS
    Import Azure Sentinal Alert rule
    .DESCRIPTION
    This function imports Azure Sentinal Alert rules from JSON and YAML config files.
    This way you can manage your Alert rules dynamic from JSON or multiple YAML files
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER SettingsFile
    Path to the JSON or YAML file for the AlertRules
    .EXAMPLE
    Import-AzSentinelAlertRule -WorkspaceName "" -SettingsFile ".\examples\AlertRules.json"
    In this example all the rules configured in the JSON file will be created or updated

    Performing the operation "Import-AzSentinelAlertRule" on target "Do you want to update profile: AlertRule01".
    [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Yes"):
    Successfully created Action for Rule: with Playbook pkmsentinel Status: Created
    Created
    Successfully updated rule: AlertRule01 with status: OK

    Name : b6103d42-xxx-4f35-xxx-c76a7f31ee4e
    DisplayName : AlertRule01
    Description :
    Severity : Medium
    Enabled : True
    Query : SecurityEvent | where EventID == "4688" | where CommandLine contains "-noni -ep bypass $"
    QueryFrequency : PT5H
    QueryPeriod : PT6H
    TriggerOperator : GreaterThan
    TriggerThreshold : 5
    SuppressionDuration : PT6H
    SuppressionEnabled : False
    Tactics : {Persistence, LateralMovement, Collection}
    PlaybookName : Playbook01

    .EXAMPLE
    Import-AzSentinelAlertRule -WorkspaceName "" -SettingsFile ".\examples\SuspectApplicationConsent.yaml"
    In this example all the rules configured in the YAML file will be created or updated
    .EXAMPLE
    Get-Item .\examples\*.json | Import-AzSentinelAlertRule -WorkspaceName ""
    In this example you can select multiple JSON files and Pipeline it to the SettingsFile parameter
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceName,

        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateScript( { (Test-Path -Path $_) -and ($_.Extension -in '.json', '.yaml', '.yml') })]
        [System.IO.FileInfo] $SettingsFile
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($SettingsFile.Extension -eq '.json') {
            try {
                $rulesRaw = Get-Content $SettingsFile -Raw
                $rules = $rulesRaw | ConvertFrom-Json -Depth 99
                Write-Verbose -Message "Found $($rules.count) rules"
            }
            catch {
                Write-Verbose $_
                Write-Error -Message 'Unable to import JSON file' -ErrorAction Stop
            }
        }
        elseif ($SettingsFile.Extension -in '.yaml', '.yml') {
            try {
                $rules = [pscustomobject](Get-Content $SettingsFile -Raw | ConvertFrom-Yaml -ErrorAction Stop)
                $rules | Add-Member -MemberType NoteProperty -Name DisplayName -Value $rules.name
                Write-Verbose -Message 'Found compatibel yaml file'
            }
            catch {
                Write-Verbose $_
                Write-Error -Message 'Unable to convert yaml file' -ErrorAction Stop
            }
        }
        else {
            Write-Error -Message 'Unsupported extension for SettingsFile' -ErrorAction Stop
        }

        $return = @()

        <#
        Test All rules first
        #>

        $allRules = $rules.analytics + $rules.Scheduled + $rules.fusion + $rules.MLBehaviorAnalytics + $rules.MicrosoftSecurityIncidentCreation | Select-Object displayName
        try {
            $allRulesContent = Get-AzSentinelAlertRule @arguments -RuleName $($allRules.displayName) -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }
        <#
            analytics rule
        #>

        if ($rules.analytics) {
            $scheduled = $rules.analytics
        }
        else {
            $scheduled = $rules.Scheduled
        }
        foreach ($item in $scheduled) {
            Write-Verbose -Message "Started with rule: $($item.displayName)"

            $guid = (New-Guid).Guid

            $content = $allRulesContent | Where-Object {$_.kind -eq 'Scheduled' -and $_.displayName -eq $item.displayName}

            Write-Verbose -Message "Get rule $($item.description)"

            if ($content) {
                Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
            }
            else {
                Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
            }

            try {
                $groupingConfiguration = [GroupingConfiguration]::new(
                    $item.groupingConfiguration.enabled,
                    $item.groupingConfiguration.reopenClosedIncident,
                    $item.groupingConfiguration.lookbackDuration,
                    $item.groupingConfiguration.entitiesMatchingMethod,
                    $item.groupingConfiguration.groupByEntities
                )
                $IncidentConfiguration = [IncidentConfiguration]::new(
                    $item.createIncident,
                    $groupingConfiguration
                )
                $bodyAlertProp = [ScheduledAlertProp]::new(
                    $item.name,
                    $item.displayName,
                    $item.description,
                    $item.severity,
                    $item.enabled,
                    $item.query,
                    $item.queryFrequency,
                    $item.queryPeriod,
                    $item.triggerOperator,
                    $item.triggerThreshold,
                    $item.suppressionDuration,
                    $item.suppressionEnabled,
                    $item.Tactics,
                    $item.playbookName,
                    $IncidentConfiguration,
                    $item.aggregationKind
                )
                $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Scheduled')
            }
            catch {
                Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop
            }

            if ($content) {
                if ($item.playbookName -or $content.playbookName) {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, incidentConfiguration, queryResultsAggregationSettings) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, incidentConfiguration, queryResultsAggregationSettings)
                }
                else {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, PlaybookName, incidentConfiguration, queryResultsAggregationSettings) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, PlaybookName, incidentConfiguration, queryResultsAggregationSettings)
                }
                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | Select-Object * -ExcludeProperty Properties.PlaybookName | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                    if (($compareResult | Where-Object PropertyName -eq "playbookName").DiffValue) {
                        $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName ($item.playbookName) -RuleId $($body.Name)
                        $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                    }
                    elseif (($compareResult | Where-Object PropertyName -eq "playbookName").RefValue) {
                        $PlaybookResult = Remove-AzSentinelAlertRuleAction @arguments -RuleId $body.Name -Confirm:$false
                        $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                    }
                    else {
                        #nothing
                    }
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
            else {
                Write-Verbose "Creating new rule: $($item.displayName)"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | Select-Object * -ExcludeProperty Properties.PlaybookName | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                    if ($body.Properties.playbookName) {
                        $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName $($body.Properties.playbookName) -RuleId $($body.Properties.Name) -confirm:$false
                        $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                    }

                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
        }

        <#
            Fusion rule
        #>

        foreach ($item in $rules.fusion) {
            Write-Verbose "Rule type is Fusion"

            $guid = (New-Guid).Guid

            $content = $allRulesContent | Where-Object {$_.kind -eq 'Fusion' -and $_.displayName -eq $item.displayName}

            Write-Verbose -Message "Get rule $($item.description)"

            if ($content) {
                Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
            }
            else {
                Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
            }

            $bodyAlertProp = [Fusion]::new(
                $item.enabled,
                $item.alertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Fusion')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        <#
            MLBehaviorAnalytics
        #>

        foreach ($item in $rules.MLBehaviorAnalytics) {
            Write-Verbose "Rule type is ML Behavior Analytics"

            $guid = (New-Guid).Guid

            $content = $allRulesContent | Where-Object {$_.kind -eq 'MLBehaviorAnalytics' -and $_.displayName -eq $item.displayName}

            Write-Verbose -Message "Get rule $($item.description)"

            if ($content) {
                Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
            }
            else {
                Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
            }

            $bodyAlertProp = [MLBehaviorAnalytics]::new(
                $item.enabled,
                $item.alertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MLBehaviorAnalytics')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force

                $return += $body.Properties
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force
                $return += $body.Properties

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        <#
            MicrosoftSecurityIncidentCreation
        #>

        foreach ($item in $rules.MicrosoftSecurityIncidentCreation) {
            Write-Verbose "Rule type is Microsoft Security"

            $guid = (New-Guid).Guid

            $content = $allRulesContent | Where-Object {$_.kind -eq 'MicrosoftSecurityIncidentCreation' -and $_.displayName -eq $item.displayName}

            Write-Verbose -Message "Get rule $($item.description)"
            $content = Get-AzSentinelAlertRule @arguments -RuleName $($item.displayName) -ErrorAction SilentlyContinue

            if ($content) {
                Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
            }
            else {
                Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel"

                $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
                $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
                $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force
                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
            }

            $bodyAlertProp = [MicrosoftSecurityIncidentCreation]::new(
                $item.displayName,
                $item.description,
                $item.enabled,
                $item.productFilter,
                $item.severitiesFilter,
                $item.displayNamesFilter
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MicrosoftSecurityIncidentCreation')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        return $return
    }
}

function Get-AzSentinelIncident {
    <#
    .SYNOPSIS
    Get Azure Sentinel Incident
    .DESCRIPTION
    With this function you can get a list of open incidents from Azure Sentinel.
    You can can also filter to Incident with speciefiek case namber or Case name
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER IncidentName
    Enter incident name, this is the same name as the alert rule that triggered the incident
    .PARAMETER CaseNumber
    Enter the case number to get specfiek details of a open case
    .PARAMETER All
    Use -All switch to get a list of all the incidents
    .EXAMPLE
    Get-AzSentinelIncident -WorkspaceName ""
    Get a list of the last 200 Incidents
    .EXAMPLE
    Get-AzSentinelIncident -WorkspaceName "" -All
    Get a list of all Incidents
    .EXAMPLE
    Get-AzSentinelIncident -WorkspaceName "" -CaseNumber
    Get information of a specifiek incident with providing the casenumber
    .EXAMPLE
    Get-AzSentinelIncident -WorkspaceName "" -IncidentName "", ""
    Get information of one or more incidents with providing a incident name, this is the name of the alert rule that triggered the incident
    #>


    [cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$IncidentName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [int[]]$CaseNumber,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [Switch]$All
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/Cases?api-version=2019-01-01-preview"
        Write-Verbose -Message "Using URI: $($uri)"

        try {
            $incidentRaw = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader)
            $incident += $incidentRaw.value

            if ($All){
                while ($incidentRaw.nextLink) {
                    $incidentRaw = (Invoke-RestMethod -Uri $($incidentRaw.nextLink) -Headers $script:authHeader -Method Get)
                    $incident += $incidentRaw.value
                }
            }
        }
        catch {
            Write-Verbose $_
            Write-Error "Unable to get incidents with error code: $($_.Exception.Message)" -ErrorAction Stop
        }

        $return = @()

        if ($incident) {
            Write-Verbose "Found $($incident.count) incidents"

            if ($IncidentName.Count -ge 1) {
                foreach ($rule in $IncidentName) {
                    [PSCustomObject]$temp = $incident | Where-Object { $_.properties.title -eq $rule }

                    if ($null -ne $temp) {
                        $temp.properties | Add-Member -NotePropertyName etag -NotePropertyValue $temp.etag -Force
                        $temp.properties | Add-Member -NotePropertyName name -NotePropertyValue $temp.name -Force
                        $return += $temp.properties
                    }
                    else {
                        Write-Error "Unable to find incident: $rule"
                    }
                }
                return $return
            }
            elseif ($CaseNumber.Count -ge 1) {
                foreach ($rule in $CaseNumber) {
                    [PSCustomObject]$temp = $incident | Where-Object { $_.properties.caseNumber -eq $rule }

                    if ($null -ne $temp) {
                        $temp.properties | Add-Member -NotePropertyName etag -NotePropertyValue $temp.etag -Force
                        $temp.properties | Add-Member -NotePropertyName name -NotePropertyValue $temp.name -Force
                        $return += $temp.properties
                    }
                    else {
                        Write-Error "Unable to find incident: $rule"
                    }
                }
                return $return
            }
            else {
                $incident | ForEach-Object {
                    $_.properties | Add-Member -NotePropertyName etag -NotePropertyValue $_.etag -Force
                    $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                }
                return $incident.properties
            }
        }
        else {
            Write-Warning "No incident found on $($WorkspaceName)"
        }
    }
}

function Get-AzSentinelHuntingRule {
    <#
    .SYNOPSIS
    Get Azure Sentinel Hunting rule
    .DESCRIPTION
    With this function you can get the configuration of the Azure Sentinel Hunting rule from Azure Sentinel
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER RuleName
    Enter the name of the Hunting rule name
    .PARAMETER Filter
    Select which type of Hunting rules you want to see. Option: HuntingQueries, GeneralExploration, LogManagement
    .EXAMPLE
    Get-AzSentinelHuntingRule -WorkspaceName "" -RuleName "",""
    In this example you can get configuration of multiple Hunting rules
    .EXAMPLE
    Get-AzSentinelHuntingRule -WorkspaceName ""
    In this example you can get a list of all the Hunting rules in once
    #>


    [cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$RuleName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [validateset("Hunting Queries", "Log Management", "General Exploration")]
        [string]$Filter
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        $uri = "$script:baseUri/savedSearches?api-version=2017-04-26-preview"

        Write-Verbose -Message "Using URI: $($uri)"


        try {
            if ($Filter) {
                $huntingRules = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader).value | Where-Object { $_.properties.Category -eq $Filter }
            }
            else {
                $huntingRules = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader).value
            }
        }
        catch {
            Write-Verbose $_
            Write-Error "Unable to get hunting rules with error code: $($_.Exception.Message)" -ErrorAction Stop
        }

        $return = @()

        if ($huntingRules) {
            Write-Verbose "Found $($huntingRules.count) hunting rules"
            if ($RuleName.Count -ge 1) {
                foreach ($rule in $RuleName) {
                    $temp = @()
                    [PSCustomObject]$temp = $huntingRules | Where-Object { ($_.properties).DisplayName -eq $rule }

                    if ($null -ne $temp) {
                        $temp.properties | Add-Member -NotePropertyName name -NotePropertyValue $temp.name -Force
                        $temp.properties | Add-Member -NotePropertyName id -NotePropertyValue $temp.id -Force
                        $temp.properties | Add-Member -NotePropertyName etag -NotePropertyValue $temp.etag -Force

                        $return += $temp.Properties
                    }
                }
                return $return
            }
            else {
                $huntingRules | ForEach-Object {

                    $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                    $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                    $_.properties | Add-Member -NotePropertyName etag -NotePropertyValue $_.etag -Force

                    $return += $_.properties
                }
                return $return
            }
        }
        else {
            Write-Verbose "No hunting rules found on $($WorkspaceName)"
        }
    }
}

function Get-AzSentinelDataConnector {
    <#
      .SYNOPSIS
      Get Azure Sentinel Data connector
      .DESCRIPTION
      With this function you can get Azure Sentinel data connectors that are enabled on the workspace
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER DataConnectorName
      Enter the Connector ID
      .EXAMPLE
      Get-AzSentinelDataConnector -WorkspaceName ""
      List all enabled dataconnector
      .EXAMPLE
      Get-AzSentinelDataConnector -WorkspaceName "" -DataConnectorName "",""
      Get specific dataconnectors
    #>


    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$DataConnectorName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [DataSourceName[]]$DataSourceName
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        if ($DataConnectorName) {
            $dataConnectors = @()

            foreach ($item in $DataConnectorName){

                $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/dataConnectors/$($item)?api-version=2020-01-01"

                try {
                    $result = Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader

                    $dataConnectors += $result
                }
                catch {
                    Write-Verbose $_
                    Write-Error "Unable to get alert rules with error code: $($_.Exception.Message)" -ErrorAction Stop
                }
            }
            return $dataConnectors
        }
        elseif ($DataSourceName) {
            $dataSources = @()

            foreach ($dataSource in $DataSourceName){
                $uri = $($script:baseUri)+ "/dataSources?"+'$'+"filter=kind+eq+'"+$dataSource+"'&api-version=2020-08-01"

                try {
                    $result = Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader

                    $dataSources += $result
                }
                catch {
                    Write-Verbose $_
                    Write-Error "Unable to get alert rules with error code: $($_.Exception.Message)" -ErrorAction Stop
                }
            }
            return $dataSources.value
        }
        else {
            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/dataConnectors?api-version=2020-01-01"

            try {
                $result = Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader
            }
            catch {
                Write-Verbose $_
                Write-Error "Unable to get alert rules with error code: $($_.Exception.Message)" -ErrorAction Stop
            }
            return $result.value
        }
    }
}

function Get-AzSentinelAlertRuleTemplates {
    <#
      .SYNOPSIS
      Get Azure Sentinel Alert Rules Templates
      .DESCRIPTION
      With this function you can get the configuration of the Azure Sentinel Alert Rules Templates from Azure Sentinel
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER Kind
      Enter the Kind to filter on the templates
      .EXAMPLE
      Get-AzSentinelAlertRuleTemplates -WorkspaceName ""
      In this example you can get Sentinel alert rules templates in once
      .EXAMPLE
      Get-AzSentinelAlertRuleTemplates -WorkspaceName "" -Kind Fusion, MicrosoftSecurityIncidentCreation
      Filter on the Kind
    #>


    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Kind[]]$Kind
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRuleTemplates?api-version=2019-01-01-preview"

        Write-Verbose -Message "Using URI: $($uri)"

        try {
            $alertRulesTemplates = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader).value
        }
        catch {
            Write-Verbose $_
            Write-Error "Unable to get alert rules with error code: $($_.Exception.Message)" -ErrorAction Stop
        }

        $return = @()

        if ($alertRulesTemplates) {
            Write-Verbose "Found $($alertRulesTemplates.count) Alert rules templates"

            if ($Kind) {
                foreach ($item in $Kind) {
                    $alertRulesTemplates | Where-Object Kind -eq $item | ForEach-Object {
                        $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                        $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                        $_.properties | Add-Member -NotePropertyName kind -NotePropertyValue $_.kind -Force

                        $return += $_.properties
                    }
                }
            }
            else {
                $alertRulesTemplates | ForEach-Object {
                    $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                    $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                    $_.properties | Add-Member -NotePropertyName kind -NotePropertyValue $_.kind -Force

                    $return += $_.properties
                }
            }

            return $return

        }
        else {
            Write-Host "No rules templates found on $($WorkspaceName)"
        }
    }
}

function Get-AzSentinelAlertRuleAction {
    <#
      .SYNOPSIS
      Get Azure Sentinel Alert rule Action
      .DESCRIPTION
      This function can be used to see if an action is attached to the alert rule, if so then the configuration will be returned
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER RuleName
      Enter the name of the Alert rule
      .PARAMETER RuleId
      Enter the Rule Id to skip Get-AzSentinelAlertRule step
      .EXAMPLE
      Get-AzSentinelAlertRuleAction -WorkspaceName "" -RuleName "testrule01"
      This example will get the Workspace ands return the full data object
      .NOTES
      NAME: Get-AzSentinelAlertRuleAction
    #>

    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RuleId
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        if ($RuleName) {
            try {
                $alertId = (Get-AzSentinelAlertRule @arguments -RuleName $RuleName -ErrorAction Stop).name
            }
            catch {
                Write-Error $_.Exception.Message
                break
            }
        }
        elseif ($RuleId) {
            $alertId = $RuleId
        }
        else {
            Write-Error "No Alert Name or ID is provided"
        }

        if ($alertId) {
            $uri = "$($Script:baseUri)/providers/Microsoft.SecurityInsights/alertRules/$($alertId)/actions?api-version=2019-01-01-preview"
            try {
                $return = (Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader).value
                return $return
            }
            catch {
                $return = $_.Exception.Message
                return $return
            }
        }
        else {
            $return = "No Alert found with provided: $($alertId)"
            return $return
        }
    }
}

function Get-AzSentinelAlertRule {
    <#
      .SYNOPSIS
      Get Azure Sentinel Alert Rules
      .DESCRIPTION
      With this function you can get the configuration of the Azure Sentinel Alert rule from Azure Sentinel
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER RuleName
      Enter the name of the Alert rule
      .PARAMETER Kind
      The alert rule kind
      .PARAMETER LastModified
      Filter for rules modified after this date/time
      .PARAMETER SkipPlaybook
      Use SkipPlaybook switch to only return the rule properties, this skips the Playbook resolve step.
      .EXAMPLE
      Get-AzSentinelAlertRule -WorkspaceName "" -RuleName "",""
      In this example you can get configuration of multiple alert rules in once
      .EXAMPLE
      Get-AzSentinelAlertRule -SubscriptionId "" -WorkspaceName "" -LastModified 2020-09-21
      In this example you can get configuration of multiple alert rules only if modified after the 21st September 2020. The datetime must be in ISO8601 format.
    #>


    [cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string[]]$RuleName,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [Kind[]]$Kind,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [DateTime]$LastModified,

        [Parameter(Mandatory = $false,
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [switch]$SkipPlaybook
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }
        try {
            Get-LogAnalyticWorkspace @arguments -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules?api-version=2020-01-01"
        Write-Verbose -Message "Using URI: $($uri)"

        try {
            $alertRules = Invoke-RestMethod -Uri $uri -Method Get -Headers $script:authHeader
        }
        catch {
            Write-Verbose $_
            Write-Error "Unable to get alert rules with error code: $($_.Exception.Message)" -ErrorAction Stop
        }

        $return = @()
        if ($alertRules.value -and $LastModified) {
            Write-Verbose "Filtering for rules modified after $LastModified"
            $alertRules.value = $alertRules.value | Where-Object { $_.properties.lastModifiedUtc -gt $LastModified }
        }
        if ($alertRules.value) {
            Write-Verbose "Found $($alertRules.value.count) Alert rules"

            if ($RuleName.Count -ge 1) {
                foreach ($rule in $RuleName) {
                    $alertRules.value | Where-Object { $_.properties.displayName -eq $rule } | ForEach-Object {

                        $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                        $_.properties | Add-Member -NotePropertyName etag -NotePropertyValue $_.etag -Force
                        $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                        $_.properties | Add-Member -NotePropertyName kind -NotePropertyValue $_.kind -Force

                        if (! $SkipPlaybook) {

                            $playbook = Get-AzSentinelAlertRuleAction @arguments -RuleId $_.name

                            if ($playbook) {
                                $playbookName = ($playbook.properties.logicAppResourceId).Split('/')[-1]
                            }
                            else {
                                $playbookName = ""
                            }

                            $_.properties | Add-Member -NotePropertyName playbookName -NotePropertyValue $playbookName -Force
                        }

                        $return += $_.properties
                    }
                }
                return $return
            }
            elseif ($Kind.Count -ge 1) {
                foreach ($rule in $Kind) {
                    $alertRules.value | Where-Object { $_.Kind -eq $rule } | ForEach-Object {

                        $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                        $_.properties | Add-Member -NotePropertyName etag -NotePropertyValue $_.etag -Force
                        $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                        $_.properties | Add-Member -NotePropertyName kind -NotePropertyValue $_.kind -Force

                        if (! $SkipPlaybook) {

                            $playbook = Get-AzSentinelAlertRuleAction @arguments -RuleId $_.name

                            if ($playbook) {
                                $playbookName = ($playbook.properties.logicAppResourceId).Split('/')[-1]
                            }
                            else {
                                $playbookName = ""
                            }

                            $_.properties | Add-Member -NotePropertyName playbookName -NotePropertyValue $playbookName -Force
                        }

                        $return += $_.properties
                    }
                }
                return $return
            }
            else {
                $alertRules.value | ForEach-Object {

                    $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force
                    $_.properties | Add-Member -NotePropertyName id -NotePropertyValue $_.id -Force
                    $_.properties | Add-Member -NotePropertyName kind -NotePropertyValue $_.kind -Force

                    if (! $SkipPlaybook) {

                        $playbook = Get-AzSentinelAlertRuleAction @arguments -RuleId $_.name

                        if ($playbook) {
                            $playbookName = ($playbook.properties.logicAppResourceId).Split('/')[-1]
                        }
                        else {
                            $playbookName = ""
                        }

                        $_.properties | Add-Member -NotePropertyName playbookName -NotePropertyValue $playbookName -Force
                    }

                    $return += $_.properties
                }
                return $return
            }
        }
        else {
            Write-Verbose "No rules found on $($WorkspaceName)"
        }
    }
}

function Export-AzSentinel {
    <#
      .SYNOPSIS
      Export Azure Sentinel
      .DESCRIPTION
      With this function you can export Azure Sentinel configuration
      .PARAMETER SubscriptionId
      Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
      .PARAMETER WorkspaceName
      Enter the Workspace name
      .PARAMETER Kind
      Select what you want to export: Alert, Hunting, Templates or All
      .PARAMETER OutputFolder
      The Path where you want to export the JSON files
      .PARAMETER TemplatesKind
      Select which Kind of templates you want to export, if empy all Templates will be exported
      .EXAMPLE
      Export-AzSentinel -WorkspaceName '' -Path C:\Temp\ -Kind All
      In this example you export Alert, Hunting and Template rules
      .EXAMPLE
      Export-AzSentinel -WorkspaceName '' -Path C:\Temp\ -Kind Templates
      In this example you export only the Templates
      .EXAMPLE
      Export-AzSentinel -WorkspaceName '' -Path C:\Temp\ -Kind Alert
      In this example you export only the Scheduled Alert rules
    #>


    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory)]
        [System.IO.FileInfo]$OutputFolder,

        [Parameter(Mandatory,
            ValueFromPipeline)]
        [ExportType[]]$Kind,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Kind[]]$TemplatesKind
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        $date = Get-Date -Format HHmmss_ddMMyyyy

        <#
        Test export path
        #>

        if (Test-Path $OutputFolder) {
            Write-Verbose "Path Exists"
        }
        else {
            try {
                $null = New-Item -Path $OutputFolder -Force -ItemType Directory -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message
                Write-Error $ErrorMessage
                Write-Verbose $_
                Break
            }
        }

        <#
        Export Alert rules section
        #>

        if (($Kind -like 'Alert') -or ($Kind -like 'All')) {

            try {
                $rules = Get-AzSentinelAlertRule @arguments -ErrorAction Stop
            }
            catch {
                $return = $_.Exception.Message
                Write-Error $return
            }

            if ($rules) {
                $output = @{
                    Scheduled                         = @(
                        $rules | Where-Object kind -eq Scheduled
                    )
                    Fusion                            = @(
                        $rules | Where-Object kind -eq Fusion
                    )
                    MLBehaviorAnalytics               = @(
                        $rules | Where-Object kind -eq MLBehaviorAnalytics
                    )
                    MicrosoftSecurityIncidentCreation = @(
                        $rules | Where-Object kind -eq MicrosoftSecurityIncidentCreation
                    )
                }

                try {
                    $fullPath = "$($OutputFolder)AlertRules_$date.json"
                    $output | ConvertTo-Json -EnumsAsStrings -Depth 15 | Out-File $fullPath -ErrorAction Stop
                    Write-Output "Alert rules exported to: $fullPath"
                }
                catch {
                    $ErrorMessage = $_.Exception.Message
                    Write-Error $ErrorMessage
                    Write-Verbose $_
                    Break
                }
            }
        }

        <#
        Export Hunting rules section
        #>

        if (($Kind -like 'Hunting') -or ($Kind -like 'All')) {
            try {
                $rules = Get-AzSentinelHuntingRule @arguments -ErrorAction Stop
  &