Public/Build-LMFilter.ps1
|
<# .SYNOPSIS Builds a filter expression for Logic Monitor API queries. .DESCRIPTION The Build-LMFilter function creates a filter expression by interactively prompting for conditions and operators. It supports basic filtering for single fields and advanced filtering for property-based queries. Multiple conditions can be combined using AND/OR operators. When a ResourcePath is provided, the wizard validates field names and provides suggestions. .PARAMETER PassThru When specified, returns the filter expression as a string instead of displaying it in a panel. .PARAMETER ResourcePath Optional. The API endpoint path (e.g., '/device/devices') to provide field validation and suggestions. .EXAMPLE #Build a basic filter expression Build-LMFilter This example launches the interactive filter builder wizard. .EXAMPLE #Build a filter with field validation for devices Build-LMFilter -ResourcePath "/device/devices" This example launches the wizard with field validation and suggestions for the device endpoint. .EXAMPLE #Build a filter and return the expression Build-LMFilter -PassThru This example builds a filter and returns the expression as a string. .NOTES The filter expression is saved to the global $LMFilter variable. When ResourcePath is provided, field names are validated against the API schema. .INPUTS None. You cannot pipe objects to this command. .OUTPUTS [String] Returns a PowerShell filter expression when using -PassThru. #> function Build-LMFilter { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Required for the function to work')] param( [Switch]$PassThru, [String]$ResourcePath ) # Helper function for getting user selection function Get-UserSelection { param( [Parameter(Mandatory = $true)] [string]$Prompt, [Parameter(Mandatory = $true)] [array]$Choices, [Parameter(Mandatory = $true)] [string]$ChoiceLabelProperty ) Write-Host "$Prompt" for ($i = 0; $i -lt $Choices.Count; $i++) { Write-Host (" " + ($i + 1) + ". " + $Choices[$i].($ChoiceLabelProperty)) } $choiceNumber = 0 do { $choiceInput = Read-Host "Enter selection number (1-$($Choices.Count))" if ([int]::TryParse($choiceInput, [ref]$choiceNumber) -and $choiceNumber -ge 1 -and $choiceNumber -le $Choices.Count) { # Valid input } else { Write-Host "Invalid input. Please enter a number between 1 and $($Choices.Count)." -ForegroundColor Red $choiceNumber = 0 # Reset to ensure loop continues } } while ($choiceNumber -eq 0) return $Choices[$choiceNumber - 1] } # Helper function for getting user confirmation function Get-UserConfirmation { param( [Parameter(Mandatory = $true)] [string]$Prompt, [string]$DefaultAnswer = "n", [string]$ConfirmSuccess = "Proceeding...", [string]$ConfirmFailure = "Stopping..." ) $answer = "" $validAnswers = @("y", "n") do { $choiceInput = Read-Host "$Prompt (y/n) [$DefaultAnswer]" if ([string]::IsNullOrEmpty($choiceInput)) { $choiceInput = $DefaultAnswer } if ($validAnswers -contains $choiceInput.ToLower()) { $answer = $choiceInput.ToLower() } else { Write-Host "Invalid input. Please enter 'y' or 'n'." -ForegroundColor Red } } while ([string]::IsNullOrEmpty($answer)) if ($answer -eq 'y') { Write-Host $ConfirmSuccess return $true } else { Write-Host $ConfirmFailure return $false } } # Load validation config if ResourcePath is provided $ValidFields = @() $ValidationEnabled = $false if ($ResourcePath) { $ConfigPath = Join-Path $PSScriptRoot "../Private/LMFilterValidationConfig.psd1" if (Test-Path $ConfigPath) { try { $ValidationConfig = Import-PowerShellDataFile -Path $ConfigPath $NormalizedPath = $ResourcePath -replace '/\{[^}]+\}', '/{id}' if ($ValidationConfig.ContainsKey($NormalizedPath)) { $ValidFields = $ValidationConfig[$NormalizedPath] | Where-Object { $_ -notin @('customProperties', 'systemProperties', 'autoProperties', 'inheritedProperties') } $ValidationEnabled = $true Write-Host "Field validation enabled for endpoint: $ResourcePath" -ForegroundColor Green Write-Host "Available fields: $($ValidFields.Count)" -ForegroundColor Gray } } catch { Write-Warning "Could not load validation config: $_" } } } $conditions = @() $operators = @( @{ Name = "Equals"; Value = "-eq" }, @{ Name = "Not Equals"; Value = "-ne" }, @{ Name = "Greater Than"; Value = "-gt" }, @{ Name = "Less Than"; Value = "-lt" }, @{ Name = "Contains"; Value = "-contains" }, @{ Name = "Not Contains"; Value = "-notcontains" } ) $valueTypes = @( @{ Name = "Basic Filtering"; Value = "B" }, @{ Name = "Advanced Property Filtering"; Value = "A" } ) $logicalOperators = @( @{ Name = "AND"; Value = "-and" }, @{ Name = "OR"; Value = "-or" } ) $propertyTypes = @( @{ Name = "System Property"; Value = "systemProperties" }, @{ Name = "Auto Property"; Value = "autoProperties" }, @{ Name = "Inherited Property"; Value = "inheritedProperties" }, @{ Name = "Custom Property"; Value = "customProperties" } ) Write-Host "Welcome to the Logic Monitor API Filter Builder! Use this wizard to build a filter expression for the Get-LM* cmdlets." #Explain the different types of filters Write-Host "" Write-Host "There are two types of filters: Basic and Advanced:" Write-Host " - Basic filters are used to filter based on a single field and a value such as displayName." Write-Host " - Advanced filters are used to filter based on a property and a value that is within that property. Applies to auto, system, inherited and custom properties." Write-Host "" Write-Host "Note: You can combine basic/advanced filters within the same filter equation using the AND or OR logical operators." Write-Host "Note: Currently, only devices, device groups, and alerts support advanced property filtering." Write-Host "" while ($true) { # Get value type first $selectedValueType = Get-UserSelection ` -Prompt "Select filter type:" ` -Choices $valueTypes ` -ChoiceLabelProperty "Name" $keyChar = $selectedValueType.Value # Get property name based on filter type switch ($keyChar) { "B" { # Show available fields if validation is enabled if ($ValidationEnabled -and $ValidFields.Count -gt 0) { Write-Host "" Write-Host "Available fields for this endpoint ($($ValidFields.Count) total):" -ForegroundColor Cyan # Calculate column width based on longest field name $maxFieldLength = ($ValidFields | Measure-Object -Property Length -Maximum).Maximum $columnWidth = $maxFieldLength + 4 # Add padding between columns # Calculate how many columns fit in the terminal width $terminalWidth = $Host.UI.RawUI.WindowSize.Width $columnsPerRow = [Math]::Floor(($terminalWidth - 4) / $columnWidth) if ($columnsPerRow -lt 1) { $columnsPerRow = 1 } if ($columnsPerRow -gt 4) { $columnsPerRow = 4 } # Cap at 4 columns for readability $currentColumn = 0 $line = " " foreach ($field in $ValidFields) { $paddedField = $field.PadRight($columnWidth) $line += $paddedField $currentColumn++ if ($currentColumn -ge $columnsPerRow) { Write-Host $line -ForegroundColor Gray $line = " " $currentColumn = 0 } } # Print remaining fields if any if ($currentColumn -gt 0) { Write-Host $line -ForegroundColor Gray } Write-Host "" } $validInput = $false do { $property = Read-Host "Enter attribute name" # Validate field if validation is enabled if ($ValidationEnabled -and $property -and $ValidFields.Count -gt 0) { if ($ValidFields -ccontains $property) { Write-Host " [VALID] Field accepted" -ForegroundColor Green $validInput = $true } else { Write-Host " [INVALID] Field not found: '$property'" -ForegroundColor Red # Try to find similar fields $suggestions = @() # Case-insensitive match $caseMatch = $ValidFields | Where-Object { $_ -ieq $property } if ($caseMatch) { $suggestions += $caseMatch } else { # Starts with $startsWith = $ValidFields | Where-Object { $_ -like "$property*" } if ($startsWith) { $suggestions += $startsWith | Select-Object -First 3 } else { # Contains $contains = $ValidFields | Where-Object { $_ -like "*$property*" } if ($contains) { $suggestions += $contains | Select-Object -First 3 } } } if ($suggestions.Count -gt 0) { Write-Host " Did you mean: $($suggestions -join ', ')?" -ForegroundColor Yellow } $retry = Read-Host " Try again? (y/n) [y]" if ($retry -eq 'n') { Write-Host " Warning: Using unvalidated field name" -ForegroundColor Yellow $validInput = $true } } } else { $validInput = $true } } while (-not $validInput) # Get operator $selectedOperator = Get-UserSelection ` -Prompt "Select operator:" ` -Choices $operators ` -ChoiceLabelProperty "Name" $operator = $selectedOperator.Value $valueInput = Read-Host "Enter value" # Ensure string values are quoted for the filter $value = "`"$valueInput`"" } "A" { $selectedProperty = Get-UserSelection ` -Prompt "Select property type:" ` -Choices $propertyTypes ` -ChoiceLabelProperty "Name" $property = $selectedProperty.Value # Get operator $selectedOperator = Get-UserSelection ` -Prompt "Select operator:" ` -Choices $operators ` -ChoiceLabelProperty "Name" $operator = $selectedOperator.Value $jsonProperty = Read-Host "Enter property name" $jsonValue = Read-Host "Enter property value" # Convert to JSON string suitable for LM API. Needs double conversion for proper escaping in the final filter string. $jsonObject = [ordered]@{ name = $jsonProperty; value = $jsonValue } | ConvertTo-Json -Compress | ConvertTo-Json -Compress $value = $jsonObject } } $conditions += "$property $operator $value" # First ask if they want to continue $continue = Get-UserConfirmation ` -Prompt "Would you like to add another condition?" ` -DefaultAnswer "n" ` -ConfirmSuccess "Adding another condition..." ` -ConfirmFailure "Finishing filter equation..." if (-not $continue) { break } # If they want to continue, ask for the logical operator $selectedLogicalOperator = Get-UserSelection ` -Prompt "Select logical operator" ` -Choices $logicalOperators ` -ChoiceLabelProperty "Name" $conditions += $selectedLogicalOperator.Value } $filterEquation = $conditions -join ' ' Set-Variable -Name "LMFilter" -Value $filterEquation -Scope global if (!$PassThru) { Write-Host "--- LM API Filter Equation ---" Write-Host "'$filterEquation'" Write-Host "-----------------------------" Write-Host "Filter equation has been saved to the `$LMFilter variable." -ForegroundColor Green return # Return nothing visually when not using PassThru } else { Write-Host "Filter equation has been saved to the `$LMFilter variable." -ForegroundColor Green return $filterEquation # Return the string when using PassThru } } |