Private/ConvertTo-CustomDetectionAutomatedActions.ps1
|
function ConvertTo-CustomDetectionAutomatedActions { <# .SYNOPSIS Builds the detectionAction.automatedActions object. .DESCRIPTION Accepts the YAML actions list (actionType plus optional additionalFields) or the legacy Graph responseActions collection and returns a dictionary keyed by automatedActions collection, each holding a list of action items. #> [CmdletBinding(DefaultParameterSetName = 'Actions')] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(ParameterSetName = 'Actions')] [AllowNull()] [object[]]$Actions, [Parameter(ParameterSetName = 'ResponseActions')] [AllowNull()] [object[]]$ResponseActions ) $actionMap = Get-CustomDetectionActionMap if ($PSCmdlet.ParameterSetName -eq 'ResponseActions') { $translated = [System.Collections.Generic.List[object]]::new() foreach ($responseAction in @($ResponseActions)) { $map = ConvertTo-CustomDetectionHashtable -InputObject $responseAction if (-not $map) { continue } $legacy = Get-CustomDetectionLegacyAction -ResponseAction $map -ActionMap $actionMap if (-not $legacy.Entry) { throw "Unsupported response action type '$($legacy.OdataType)'." } $action = [ordered]@{ actionType = $legacy.Entry.ActionType } if ($map['isolationType']) { $action.additionalFields = @{ isolationType = "$($map['isolationType'])" } } $translated.Add($action) } $Actions = $translated.ToArray() } $result = [ordered]@{} foreach ($action in @($Actions)) { $map = ConvertTo-CustomDetectionHashtable -InputObject $action if (-not $map) { throw "Each item in actions must be a mapping with an actionType. Got '$action'." } $actionType = "$($map['actionType'])".Trim() $entry = $actionMap | Where-Object { $_.ActionType -eq $actionType } | Select-Object -First 1 if (-not $entry) { $supported = ($actionMap | ForEach-Object { $_.ActionType }) -join ', ' throw "Unsupported response action type '$actionType'. Supported types are: $supported" } $item = [ordered]@{} foreach ($key in $entry.Defaults.Keys) { $item[$key] = $entry.Defaults[$key] } $additional = ConvertTo-CustomDetectionHashtable -InputObject $map['additionalFields'] if ($null -ne $map['additionalFields'] -and $null -eq $additional) { throw "The additionalFields value of action '$actionType' must be a mapping of field name to column name." } if ($additional) { # Fields are matched without case and written under their documented name $fields = [ordered]@{} foreach ($key in @($additional.Keys)) { if ("$key".StartsWith('@')) { continue } $field = @($entry.Fields | Where-Object { $_ -eq $key }) if ($field.Count -eq 0) { throw "Field '$key' is not documented for action '$actionType'. Documented fields are: $($entry.Fields -join ', ')" } $fields[$field[0]] = $additional[$key] } $hasSha1 = $fields.Contains('sha1Column') -and "$($fields['sha1Column'])" -ne '' $hasSha256 = $fields.Contains('sha256Column') -and "$($fields['sha256Column'])" -ne '' if ($hasSha1 -and $hasSha256) { throw "Action '$actionType' names sha1Column and sha256Column. A file action carries one hash column." } # A file action carries one hash column, so an explicit sha256Column replaces the sha1Column default if ($hasSha256 -and -not $hasSha1 -and $item.Contains('sha1Column')) { $item.Remove('sha1Column') } $populated = Get-CustomDetectionPopulatedEntry -Map $fields foreach ($key in @($populated.Keys)) { $value = $populated[$key] if ($key -eq 'isolationType') { $isolationType = "$value".ToLowerInvariant() if ($isolationType -notin @('full', 'selective')) { throw "Isolation type '$value' is not supported. Use 'Full' or 'Selective'." } $item[$key] = $isolationType } elseif ($key -eq 'deviceGroupNames') { $groups = @($value) foreach ($group in $groups) { if ($group -isnot [string] -or [string]::IsNullOrWhiteSpace($group)) { throw "Action '$actionType' names a device group that is not a single name." } } $item[$key] = [object[]]@($groups | ForEach-Object { "$_" }) } else { if ($value -isnot [string]) { throw "Field '$key' of action '$actionType' must be a single column name." } $item[$key] = $value } } } # Fields follow the documented order, so the same entries give the same item whatever their order in the file $item = ConvertTo-CustomDetectionOrderedMap -Map $item -Order $entry.Fields # File input only. The API keeps every item it is sent, so an exact repeat is a mistake and a repeat with other fields gets a warning if ($PSCmdlet.ParameterSetName -eq 'Actions' -and $result.Contains($entry.Collection)) { $itemJson = $item | ConvertTo-Json -Compress -Depth 5 foreach ($existing in $result[$entry.Collection]) { if (($existing | ConvertTo-Json -Compress -Depth 5) -ceq $itemJson) { throw "Action '$actionType' is listed twice with the same fields. List each action once." } } Write-Warning "Action '$actionType' is listed more than once. The rule keeps every item, so check that both are intended." } if (-not $result.Contains($entry.Collection)) { $result[$entry.Collection] = [System.Collections.Generic.List[object]]::new() } $result[$entry.Collection].Add($item) } if ($result.Count -eq 0) { return $null } foreach ($collection in @($result.Keys)) { $result[$collection] = [object[]]$result[$collection].ToArray() } return $result } |