AzSentinel.psm1
enum Severity { Medium High Low Informational } enum Tactics { InitialAccess Persistence Execution PrivilegeEscalation DefenseEvasion CredentialAccess LateralMovement Discovery Collection Exfiltration CommandAndControl Impact } enum TriggerOperator { GreaterThan FewerThan EqualTo NotEqualTo } class AlertProp { [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [guid] $Name [Parameter(Mandatory)] [string] $DisplayName [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Description [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Severity] $Severity [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [bool] $Enabled [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Query [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $QueryFrequency [ValidateNotNullOrEmpty()] [string] $QueryPeriod [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [TriggerOperator] $TriggerOperator [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Int] $TriggerThreshold [Parameter(Mandatory)] [AllowEmptyString()] [string] $SuppressionDuration [Parameter(Mandatory)] [bool] $SuppressionEnabled [Parameter(Mandatory)] [AllowEmptyCollection()] [Tactics[]] $Tactics AlertProp ($Name, $DisplayName, $Description, $Severity, $Enabled, $Query, $QueryFrequency, $QueryPeriod, $TriggerOperator, $TriggerThreshold, $suppressionDuration, $suppressionEnabled, $Tactics) { $this.name = $Name $this.DisplayName = $DisplayName $this.Description = $Description $this.Severity = $Severity $this.Enabled = $Enabled $this.Query = $Query $this.QueryFrequency = ("PT" + $QueryFrequency).ToUpper() $this.QueryPeriod = ("PT" + $QueryPeriod).ToUpper() $this.TriggerOperator = $TriggerOperator $this.TriggerThreshold = $TriggerThreshold $this.SuppressionDuration = if (! ($null -eq $suppressionDuration) -or ! ($null -eq $suppressionEnabled)) { ("PT" + $suppressionDuration).ToUpper() } else { "PT1H" } $this.SuppressionEnabled = if ($suppressionEnabled) { $suppressionEnabled } else { $false } $this.Tactics = $Tactics } } class AlertRule { [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [guid] $Name [Parameter(Mandatory)] [string] $Etag [Parameter(Mandatory = $false)] [string]$type [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [AlertProp]$Properties [Parameter(Mandatory)] [string]$Id AlertRule ([guid]$Name, [string]$Etag, [AlertProp]$Properties, $Id) { $this.id = $Id $this.type = 'Microsoft.SecurityInsights/alertRules' $this.Name = $Name $this.Etag = $Etag $this.Properties = $Properties } } #requires -version 6.2 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) } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 function Get-AuthToken { <# .SYNOPSIS Get Authorization Token .DESCRIPTION This function is used to generate the Authtoken for API Calls .EXAMPLE Get-AuthToken #> [CmdletBinding()] param ( ) try { $azContext = Get-AzContext if ($null -ne $azContext) { Write-Verbose -Message "Using Subscription: $($azContext.Subscription.Name) from tenant $($azContext.Tenant.Id)" $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $profileClient = [Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient]::new($azProfile) $script:accessToken = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId) $script:subscriptionId = $azContext.Subscription.Id $script:tenantId = $azContext.Tenant.Id } else { throw 'No subscription available, Please use Connect-AzAccount to login and select the right subscription' } } catch { $PSCmdlet.ThrowTerminatingError($_) } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 } $workspaces = Invoke-webrequest -Uri $uri -Method get -Headers $script:authHeader $workspaceObject = ($workspaces.Content | ConvertFrom-Json).value | Where-Object { $_.name -eq $WorkspaceName } if ($workspaceObject) { $Script:workspace = ($workspaceObject.id).trim() $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)" -ErrorAction Stop } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 function precheck { <# .SYNOPSIS precheck .DESCRIPTION This function will test the authentication token before executing call to the Azure API .EXAMPLE precheck This will run the precheck function #> if ($null -eq $script:accessToken) { Get-AuthToken } elseif ([datetime]::UtcNow.AddMinutes(5) -lt $script:accessToken.ExpiresOn.DateTime ) { # if token expires within 5 minutes, request a new one Get-AuthToken } $script:authHeader = @{ 'Content-Type' = 'application/json' Authorization = 'Bearer ' + $script:accessToken.AccessToken } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 .EXAMPLE Get-AzSentinelAlertRule -WorkspaceName "" -RuleName "","" In this example you can get configuration of multiple alert 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 ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } Get-LogAnalyticWorkspace @arguments $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules?api-version=2019-01-01-preview" Write-Verbose -Message "Using URI: $($uri)" $alertRules = Invoke-webrequest -Uri $uri -Method get -Headers $script:authHeader Write-Verbose "Found $((($alertRules.Content | ConvertFrom-Json).value).count) Alert rules" $return = @() if ($alertRules) { if ($RuleName.Count -ge 1) { foreach ($rule in $RuleName) { [PSCustomObject]$temp = ($alertRules.Content | ConvertFrom-Json).value | 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 etag -NotePropertyValue $temp.etag -Force $temp.properties | Add-Member -NotePropertyName id -NotePropertyValue $temp.id -Force $return += $temp.properties } else { Write-Error "Unable to find Rule: $rule" } } return $return } else { ($alertRules.Content | ConvertFrom-Json).value | ForEach-Object { $_.properties | Add-Member -NotePropertyName name -NotePropertyValue $_.name -Force return $_.properties } } } else { Write-Warning "No rules found on $($WorkspaceName)" } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 rule you want to get .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)] [validateset("HuntingQueries", "GeneralExploration", "LogManagement")] [string]$Filter ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } Get-LogAnalyticWorkspace @arguments $uri = "$script:baseUri/savedSearches?api-version=2017-04-26-preview" Write-Verbose -Message "Using URI: $($uri)" $alertRules = Invoke-webrequest -Uri $uri -Method get -Headers $script:authHeader Write-Verbose "Found $((($alertRules.Content | ConvertFrom-Json).value).count) Alert rules" $return = @() if ($alertRules) { if ($RuleName.Count -ge 1) { foreach ($rule in $RuleName) { [PSCustomObject]$temp = ($alertRules.Content | ConvertFrom-Json).value | 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 } else { Write-Warning "Unable to find Rule: $rule" } } return $return } else { ($alertRules.Content | ConvertFrom-Json).value | 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 } } } else { Write-Warning "No rules found on $($WorkspaceName)" } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 .EXAMPLE Get-AzSentinelIncident -WorkspaceName "" Get a list of all open 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 ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } Get-LogAnalyticWorkspace @arguments $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/Cases?api-version=2019-01-01-preview" Write-Verbose -Message "Using URI: $($uri)" $incident = Invoke-webrequest -Uri $uri -Method get -Headers $script:authHeader Write-Verbose "Found $((($incident.Content | ConvertFrom-Json).value).count) incidents" $return = @() if ($incident) { if ($IncidentName.Count -ge 1) { foreach ($rule in $IncidentName) { [PSCustomObject]$temp = ($incident.Content | ConvertFrom-Json).value | Where-Object { $_.properties.title -eq $rule } if ($null -ne $temp) { $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.Content | ConvertFrom-Json).value | Where-Object { $_.properties.caseNumber -eq $rule } if ($null -ne $temp) { $return += $temp.properties } else { Write-Error "Unable to find incident: $rule" } } return $return } else { ($incident.Content | ConvertFrom-Json).value | ForEach-Object { return $_.properties } } } else { Write-Warning "No incident found on $($WorkspaceName)" } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -module @{ModuleNAme = 'powershell-yaml'; ModuleVersion = '0.4.0'} #requires -version 6.2 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 .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 } } } Get-LogAnalyticWorkspace @arguments $errorResult = '' if ($SettingsFile.Extension -eq '.json') { try { $analytics = (Get-Content $SettingsFile -Raw | ConvertFrom-Json -ErrorAction Stop).analytics Write-Verbose -Message "Found $($analytics.count) rules" } catch { Write-Verbose $_ Write-Error -Message 'Unable to convert JSON file' -ErrorAction Stop } } elseif ($SettingsFile.Extension -in '.yaml', 'yml') { try { $analytics = [pscustomobject](Get-Content $SettingsFile -Raw | ConvertFrom-Yaml -ErrorAction Stop) $analytics | Add-Member -MemberType NoteProperty -Name DisplayName -Value $analytics.name Write-Verbose -Message 'Found compatibel yaml file' } catch { Write-Verbose $_ Write-Error -Message 'Unable to convert yaml file' -ErrorAction Stop } } foreach ($item in $analytics) { Write-Verbose -Message "Started with rule: $($item.displayName)" $guid = (New-Guid).Guid try { Write-Verbose -Message "Get rule $($item.description)" $content = Get-AzSentinelAlertRule @arguments -RuleName $($item.displayName) -ErrorAction SilentlyContinue if ($content) { Write-Verbose -Message "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 exists 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" } } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_ Write-Error "Unable to connect to APi to get Analytic rules with message: $($errorResult.message)" -ErrorAction Stop } try { $bodyAlertProp = [AlertProp]::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 ) $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id) } catch { Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop } if ($content) { $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name) if ($compareResult) { Write-Output "Found Differences for rule: $($item.displayName)" Write-Output ($compareResult | Format-Table | Out-String) if ($PSCmdlet.ShouldProcess("Do you want to update profile: $($body.Properties.DisplayName)")) { try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json) Write-Output "Successfully updated rule: $($item.displayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } else { Write-Output "No change have been made for rule $($item.displayName), deployment aborted" } } else { Write-Output "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) Write-Output "Successfully created rule: $($item.displayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -module @{ModuleNAme = 'powershell-yaml'; ModuleVersion = '0.4.0'} #requires -version 6.2 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 } } } Get-LogAnalyticWorkspace @arguments $errorResult = '' $item = @{ } if ($SettingsFile.Extension -eq '.json') { try { $analytics = (Get-Content $SettingsFile -Raw | ConvertFrom-Json -ErrorAction Stop).analytics Write-Verbose -Message "Found $($analytics.count) rules" } catch { Write-Verbose $_ Write-Error -Message 'Unable to convert JSON file' -ErrorAction Stop } } elseif ($SettingsFile.Extension -in '.yaml', 'yml') { try { $analytics = [pscustomobject](Get-Content $SettingsFile -Raw | ConvertFrom-Yaml -ErrorAction Stop) $analytics | Add-Member -MemberType NoteProperty -Name DisplayName -Value $analytics.name Write-Verbose -Message 'Found compatibel yaml file' } catch { Write-Verbose $_ Write-Error -Message 'Unable to convert yaml file' -ErrorAction Stop } } foreach ($item in $analytics) { Write-Verbose -Message "Started with Hunting rule: $($item.displayName)" try { Write-Verbose -Message "Get rule $($item.description)" $content = Get-AzSentinelHuntingRule @arguments -RuleName $($item.displayName) -WarningAction SilentlyContinue 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 { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_ Write-Error "Unable to connect to APi to get Analytic rules with message: $($errorResult.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) Write-Output "Successfully updated hunting rule: $($item.displayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } 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) Write-Output "Successfully created hunting rule: $($item.displayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 DisplayName Enter the Display name for the Alert rule .PARAMETER Description Enter the Description for the Alert rule .PARAMETER Severity Enter the Severity, valid values: Medium", "High", "Low", "Informational" .PARAMETER Enabled Set $true to enable the Alert Rule or $false to disable Alert Rule .PARAMETER Query Enter the Query that you want to use .PARAMETER QueryFrequency Enter the query frequency, example: 5H or 5M (H stands for Hour and M stands for Minute) .PARAMETER QueryPeriod Enter the quury period, exmaple: 5H or 5M (H stands for Hour and M stands for Minute) .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 or 5M (H stands for Hour and M stands for Minute) .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" .EXAMPLE New-AzSentinelAlertRule -WorkspaceName "" -DisplayName "" -Description "" -Severity "" -Enabled -Query '' -QueryFrequency "" -QueryPeriod "" -TriggerOperator "" -TriggerThreshold -SuppressionDuration "" -SuppressionEnabled $false -Tactics @("","") In this example you create a new Alert 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] $Description, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Severity] $Severity, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [bool] $Enabled, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Query, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $QueryFrequency, [ValidateNotNullOrEmpty()] [string] $QueryPeriod, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [TriggerOperator] $TriggerOperator, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Int] $TriggerThreshold, [Parameter(Mandatory)] [AllowEmptyString()] [string] $SuppressionDuration, [Parameter(Mandatory)] [bool] $SuppressionEnabled, [Parameter(Mandatory)] [AllowEmptyCollection()] [Tactics[]] $Tactics ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } Get-LogAnalyticWorkspace @arguments $errorResult = '' $item = @{ } Write-Verbose -Message "Creating new rule: $($DisplayName)" try { Write-Verbose -Message "Get rule $DisplayName" $content = Get-AzSentinelAlertRule @arguments -RuleName $DisplayName -WarningAction SilentlyContinue 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" } } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_ Write-Error "Unable to connect to APi to get Analytic rules with message: $($errorResult.message)" -ErrorAction Stop } try { $bodyAlertProp = [AlertProp]::new( $item.name, $DisplayName, $Description, $Severity, $Enabled, $Query, $QueryFrequency, $QueryPeriod, $TriggerOperator, $TriggerThreshold, $SuppressionDuration, $SuppressionEnabled, $Tactics ) $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id) } catch { Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop } if ($content) { $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name) if ($compareResult) { Write-Output "Found Differences for rule: $($DisplayName)" Write-Output ($compareResult | Format-Table | Out-String) if ($PSCmdlet.ShouldProcess("Do you want to update profile: $($body.Properties.DisplayName)")) { try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json) Write-Output "Successfully updated rule: $($DisplayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } else { Write-Output "No change have been made for rule $($DisplayName), deployment aborted" } } else { Write-Output "Rule $($DisplayName) is compliance, nothing to do" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } } else { Write-Verbose "Creating new rule: $($DisplayName)" try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json) Write-Output "Successfully created rule: $($DisplayName) with status: $($result.StatusDescription)" Write-Output ($body.Properties | Format-List | Format-Table | Out-String) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 } } } Get-LogAnalyticWorkspace @arguments $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 { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_ Write-Error "Unable to connect to APi to get Analytic rules with message: $($errorResult.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) Write-Output "Successfully updated hunting rule: $($DisplayName) with status: $($result.StatusDescription)" } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.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) Write-Output "Successfully created hunting rule: $($DisplayName) with status: $($result.StatusDescription)" Write-Output ($body.properties | Format-Table) } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Verbose $_.Exception.Message Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 } } } Get-LogAnalyticWorkspace @arguments if ($RuleName) { # remove defined rules foreach ($rule in $RuleName) { $item = Get-AzSentinelHuntingRule @arguments -RuleName $rule -WarningAction SilentlyContinue 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 $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader Write-Output "Successfully removed rule: $($rule) with status: $($result.StatusDescription)" } 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-AzSentinelHuntingRule @arguments | ForEach-Object { $uri = "$script:baseUri/savedSearches/$($_.name)?api-version=2017-04-26-preview" if ($PSCmdlet.ShouldProcess("Do you want to remove: $($_.displayName)")) { $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader Write-Output "Successfully removed rule: $($_.displayName) with status: $($result.StatusDescription)" } else { Write-Output "No change have been made for rule: $($_.displayName)" } } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 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 } } } Get-LogAnalyticWorkspace @arguments if ($RuleName) { # remove defined rules foreach ($rule in $RuleName) { $item = Get-AzSentinelHuntingRule @arguments -RuleName $rule 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 $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader Write-Output "Successfully removed hunting rule: $($rule) with status: $($result.StatusDescription)" } 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 | ForEach-Object { $uri = "$script:baseUri/savedSearches/$($_.name)?api-version=2017-04-26-preview" if ($PSCmdlet.ShouldProcess("Do you want to remove: $($_.displayName)")) { $result = Invoke-WebRequest -Uri $uri -Method DELETE -Headers $script:authHeader Write-Output "Successfully removed hunting rule: $($_.displayName) with status: $($result.StatusDescription)" } else { Write-Output "No change have been made for hunting rule: $($_.displayName)" } } } } } #requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 function Set-AzSentinel { <# .SYNOPSIS Enable Azure Sentinel .DESCRIPTION This function enables Azure Sentinel on a existing 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 .EXAMPLE Set-AzSentinel -WorkspaceName "" This example will enable Azure Sentinel for the provided workspace #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $false, ParameterSetName = "Sub")] [ValidateNotNullOrEmpty()] [string] $SubscriptionId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$WorkspaceName ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } $workspaceResult = Get-LogAnalyticWorkspace @arguments -FullObject # Variables $errorResult = '' Write-Output $Script:workspace if ($workspaceResult.properties.provisioningState -eq 'Succeeded') { $body = @{ 'id' = '' 'etag' = '' 'name' = '' 'type' = '' 'location' = $workspaceResult.location 'properties' = @{ 'workspaceResourceId' = $workspaceResult.id } 'plan' = @{ 'name' = 'SecurityInsights($workspace)' 'publisher' = 'Microsoft' 'product' = 'OMSGallery/SecurityInsights' 'promotionCode' = '' } } $uri = "$(($Script:baseUri).Split('microsoft.operationalinsights')[0])Microsoft.OperationsManagement/solutions/SecurityInsights($WorkspaceName)?api-version=2015-11-01-preview" try { $solutionResult = Invoke-webrequest -Uri $uri -Method Get -Headers $script:authHeader Write-Output "Azure Sentinel is already enabled on $WorkspaceName and status is: $($solutionResult.StatusDescription)" } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error if ($errorResult.Code -eq 'ResourceNotFound') { Write-Output "Azure Sentinetal is not enabled on workspace: $($WorkspaceName)" try { if ($PSCmdlet.ShouldProcess("Do you want to enable Sentinel for Workspace: $workspace")) { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json) Write-Output "Successfully enabled Sentinel on workspae: $WorkspaceName" return $result } else { Write-Output "No change have been made for rule $WorkspaceName, deployment aborted" } } catch { $errorReturn = $_ $errorResult = ($errorReturn | ConvertFrom-Json ).error Write-Error "Unable to enable Sentinel on $WorkspaceName with error message: $($errorResult.message)" } } else { Write-Verbose $_ Write-Error "Unable to invoke webrequest with error message: $($errorResult.message)" -ErrorAction Stop } } } else { Write-Error "Workspace $WorkspaceName is currently in $($workspaceResult.properties.provisioningState) status, setup canceled" } } } |