Private/EAI/Build-EAISdtFilter.ps1
|
$Script:EAISdtFilterSchemaName = 'filterCondition' $Script:EAISdtFilterSchemaVersion = '4' $Script:EAISdtUnaryFilterOperators = @('IS_EMPTY', 'NOT_EMPTY') $Script:EAISdtFilterOperatorLabels = @{ EQUALS = 'Equals' NOT_EQUALS = 'Not equals' CONTAINS = 'Contains' NOT_CONTAINS = 'Not contains' IN = 'In' NOT_IN = 'Not in' GREATER_THAN = 'Greater than' GREATER_THAN_EQUAL = 'Greater than equal' LESS_THAN = 'Less than' LESS_THAN_EQUAL = 'Less than equal' OLDER_THAN = 'Older than' WITHIN = 'Within' IS_EMPTY = 'Is Empty' NOT_EMPTY = 'Not empty' } $Script:EAISdtFilterOperatorsByFieldType = @{ string = @('EQUALS', 'NOT_EQUALS', 'CONTAINS', 'NOT_CONTAINS', 'IN', 'NOT_IN', 'IS_EMPTY', 'NOT_EMPTY') integer = @('EQUALS', 'NOT_EQUALS', 'GREATER_THAN', 'GREATER_THAN_EQUAL', 'LESS_THAN', 'LESS_THAN_EQUAL', 'IN', 'NOT_IN', 'IS_EMPTY', 'NOT_EMPTY') long = @('GREATER_THAN', 'LESS_THAN', 'OLDER_THAN', 'WITHIN', 'IS_EMPTY', 'NOT_EMPTY') } function New-EAISdtFilterFieldReference { [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Field, [Parameter(Mandatory)] [ValidateSet('string', 'integer', 'long')] [String]$Type ) return @{ field = $Field type = $Type } } function New-EAISdtFilterObject { [CmdletBinding()] param ( [Parameter(Mandatory)] $Expression ) return [PSCustomObject]@{ schemaName = $Script:EAISdtFilterSchemaName schemaVersion = $Script:EAISdtFilterSchemaVersion expression = $Expression } } function New-EAISdtFilterCondition { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateSet( 'EQUALS', 'NOT_EQUALS', 'GREATER_THAN', 'GREATER_THAN_EQUAL', 'LESS_THAN', 'LESS_THAN_EQUAL', 'IN', 'NOT_IN', 'CONTAINS', 'NOT_CONTAINS', 'WITHIN', 'OLDER_THAN', 'IS_EMPTY', 'NOT_EMPTY' )] [String]$Operator, [Parameter(Mandatory)] [hashtable]$FieldReference, $Value ) if ($Operator -in $Script:EAISdtUnaryFilterOperators) { return @{ $Operator = @($FieldReference) } } if ($null -eq $Value) { throw "Operator '$Operator' requires a value." } return @{ $Operator = @( $FieldReference ,$Value ) } } function Test-EAISdtUnaryFilterOperator { [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Operator ) return $Operator -in $Script:EAISdtUnaryFilterOperators } function ConvertTo-EAISdtFilterEpochMillis { [CmdletBinding()] param ( [Parameter(Mandatory)] [Datetime]$DateTime ) $unspecified = [Datetime]::SpecifyKind($DateTime, [System.DateTimeKind]::Unspecified) return [Int64]([DateTimeOffset]::new($unspecified, (Get-TimeZone).BaseUtcOffset)).ToUnixTimeMilliseconds() } function Read-EAISdtFilterFieldSelection { [CmdletBinding()] param () $choices = @( Get-EAISdtCommonFilterFieldsFromCatalog | ForEach-Object { @{ Name = $_.Name Value = $_ } } ) + @( @{ Name = 'Custom field...'; Value = 'Custom' } ) $selected = Get-LMUserSelection -Prompt 'Select the event field to match:' -Choices $choices -ChoiceLabelProperty 'Name' if ($selected.Value -eq 'Custom') { $fieldName = Read-LMWizardHost 'Enter field name (e.g. cf.eventCI or meta.alertKey)' if ([string]::IsNullOrWhiteSpace($fieldName)) { throw 'Field name is required.' } $typeChoices = @( @{ Name = 'String'; Value = 'string' } @{ Name = 'Integer'; Value = 'integer' } @{ Name = 'Long (timestamp)'; Value = 'long' } ) $typeChoice = Get-LMUserSelection -Prompt 'Select field type:' -Choices $typeChoices -ChoiceLabelProperty 'Name' return New-EAISdtFilterFieldReference -Field $fieldName -Type $typeChoice.Value } return New-EAISdtFilterFieldReference -Field $selected.Value.Field -Type $selected.Value.Type } function Read-EAISdtFilterOperatorSelection { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateSet('string', 'integer', 'long')] [String]$FieldType ) $operators = $Script:EAISdtFilterOperatorsByFieldType[$FieldType] | ForEach-Object { @{ Name = $Script:EAISdtFilterOperatorLabels[$_] Value = $_ } } $selected = Get-LMUserSelection -Prompt 'Select comparison operator:' -Choices $operators -ChoiceLabelProperty 'Name' return $selected.Value } function Read-EAISdtFilterRelativeDurationValue { [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Prompt ) $unitChoices = @( @{ Name = 'Minutes'; Value = 'minute' } @{ Name = 'Hours'; Value = 'hour' } @{ Name = 'Days'; Value = 'day' } @{ Name = 'Weeks'; Value = 'week' } @{ Name = 'Months'; Value = 'month' } ) $unitChoice = Get-LMUserSelection -Prompt 'Select time unit:' -Choices $unitChoices -ChoiceLabelProperty 'Name' $duration = Read-LMIntInRange -Prompt $Prompt -Minimum 1 -Maximum 10000 return @{ unit = $unitChoice.Value duration = $duration } } function Read-EAISdtFilterListValue { [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Prompt, [Parameter(Mandatory)] [ValidateSet('string', 'integer')] [String]$FieldType ) $rawValue = Read-LMWizardHost $Prompt $parts = @($rawValue -split ',' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($parts.Count -eq 0) { throw 'At least one value is required.' } if ($FieldType -eq 'integer') { return @($parts | ForEach-Object { [int]$_ }) } return @($parts) } function Read-EAISdtFilterValue { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateSet( 'EQUALS', 'NOT_EQUALS', 'GREATER_THAN', 'GREATER_THAN_EQUAL', 'LESS_THAN', 'LESS_THAN_EQUAL', 'IN', 'NOT_IN', 'CONTAINS', 'NOT_CONTAINS', 'WITHIN', 'OLDER_THAN', 'IS_EMPTY', 'NOT_EMPTY' )] [String]$Operator, [Parameter(Mandatory)] [ValidateSet('string', 'integer', 'long')] [String]$FieldType ) switch ($Operator) { { $_ -in @('IS_EMPTY', 'NOT_EMPTY') } { return $null } { $_ -in @('WITHIN', 'OLDER_THAN') } { return Read-EAISdtFilterRelativeDurationValue -Prompt 'Duration' } { $_ -in @('IN', 'NOT_IN') } { if ($FieldType -eq 'long') { throw "Operator '$Operator' is not supported for long fields." } return Read-EAISdtFilterListValue -Prompt 'Enter values separated by commas' -FieldType $FieldType } { $_ -in @('CONTAINS', 'NOT_CONTAINS') } { return Read-EAISdtFilterListValue -Prompt 'Enter substrings separated by commas' -FieldType 'string' } { $_ -in @('GREATER_THAN', 'GREATER_THAN_EQUAL', 'LESS_THAN', 'LESS_THAN_EQUAL') } { if ($FieldType -eq 'long') { $dateTime = Read-LMDateTimeValue -Prompt 'Date/time' -Default (Get-Date) return ConvertTo-EAISdtFilterEpochMillis -DateTime $dateTime } return [int](Read-LMWizardHost 'Enter value') } { $_ -in @('EQUALS', 'NOT_EQUALS') } { $rawValue = Read-LMWizardHost 'Enter value' if ($FieldType -eq 'integer') { return [int]$rawValue } return [string]$rawValue } } throw "Unsupported operator '$Operator'." } function Test-EAISdtFilterConditionExpression { [CmdletBinding()] param ( [Parameter(Mandatory)] $Expression ) if ($null -eq $Expression) { return $false } if ($Expression -is [hashtable] -or $Expression -is [PSCustomObject]) { $keys = if ($Expression -is [hashtable]) { @($Expression.Keys) } else { @($Expression.PSObject.Properties.Name) } if ($keys.Count -eq 1 -and $keys[0] -in @('AND', 'OR')) { return $true } foreach ($operator in $Script:EAISdtFilterOperatorsByFieldType.Values | ForEach-Object { $_ } | Select-Object -Unique) { if ($keys -contains $operator) { return $true } } } return $false } function Read-EAISdtFilterCondition { [CmdletBinding()] param () $fieldReference = Read-EAISdtFilterFieldSelection $operator = Read-EAISdtFilterOperatorSelection -FieldType $fieldReference.type if (Test-EAISdtUnaryFilterOperator -Operator $operator) { return New-EAISdtFilterCondition -Operator $operator -FieldReference $fieldReference } $value = Read-EAISdtFilterValue -Operator $operator -FieldType $fieldReference.type return New-EAISdtFilterCondition -Operator $operator -FieldReference $fieldReference -Value $value } function Read-EAISdtFilterOrGroup { [CmdletBinding()] param () $conditions = [System.Collections.Generic.List[object]]::new() do { $conditions.Add((Read-EAISdtFilterCondition)) } while (Get-LMUserConfirmation -Prompt 'Add another condition to this OR group?' -DefaultAnswer 'n') if ($conditions.Count -eq 1) { return $conditions[0] } return @{ OR = @($conditions) } } function Read-EAISdtFilterExpressionInteractive { [CmdletBinding()] param () $combinatorChoices = @( @{ Name = 'Match ALL conditions (AND)'; Value = 'AND' } @{ Name = 'Match ANY condition (OR)'; Value = 'OR' } ) $combinator = Get-LMUserSelection -Prompt 'How should conditions be combined?' -Choices $combinatorChoices -ChoiceLabelProperty 'Name' $conditions = [System.Collections.Generic.List[object]]::new() do { $entryChoices = @( @{ Name = 'Add a condition'; Value = 'Condition' } @{ Name = 'Add an OR subgroup (any of...)'; Value = 'OrGroup' } ) $entryChoice = Get-LMUserSelection -Prompt 'What would you like to add?' -Choices $entryChoices -ChoiceLabelProperty 'Name' if ($entryChoice.Value -eq 'OrGroup') { $conditions.Add((Read-EAISdtFilterOrGroup)) } else { $conditions.Add((Read-EAISdtFilterCondition)) } } while (Get-LMUserConfirmation -Prompt 'Add another filter rule?' -DefaultAnswer 'y') if ($conditions.Count -eq 0) { return @{ $combinator.Value = @() } } if ($conditions.Count -eq 1 -and $combinator.Value -eq 'AND') { $single = $conditions[0] if ($single.ContainsKey('OR') -or (Test-EAISdtFilterConditionExpression -Expression $single)) { return $single } } return @{ $combinator.Value = @($conditions) } } function Write-EAISdtFilterPreview { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Required for interactive wizards')] param ( [Parameter(Mandatory)] $Filter ) $preview = $Filter | ConvertTo-Json -Depth 20 Write-Host $preview } |