public/psf-config.ps1

function Export-FalconConfig {
<#
.SYNOPSIS
Create an archive containing Falcon configuration files
.DESCRIPTION
Uses various PSFalcon commands to gather and export groups, policies and exclusions as a collection of Json files
within a zip archive. The exported files can be used with 'Import-FalconConfig' to restore configurations to your
existing CID or create them in another CID.
.PARAMETER Select
Selected items to export from your current CID, or leave unspecified to export all available items
.PARAMETER Force
Overwrite an existing file when present
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Export-FalconConfig
#>

  [CmdletBinding(DefaultParameterSetName='ExportItem',SupportsShouldProcess)]
  param(
    [Parameter(ParameterSetName='ExportItem',Position=1)]
    [ValidateSet('DeviceControlPolicy','FileVantagePolicy','FileVantageRuleGroup','FirewallGroup',
      'FirewallPolicy','HostGroup','IoaExclusion','IoaGroup','Ioc','MlExclusion','PreventionPolicy',
      'ResponsePolicy','Script','SensorUpdatePolicy','SvExclusion')]
    [Alias('Items')]
    [string[]]$Select,
    [Parameter(ParameterSetName='ExportItem')]
    [switch]$Force
  )
  begin {
    function Get-ItemContent ([string]$String) {
      # Request content for provided 'Item'
      Write-Host "[Export-FalconConfig] Exporting '$String'..."
      $ConfigFile = Join-Path $Location "$String.json"
      $Config = if ($String -match '^FileVantage(Policy|RuleGroup)$') {
        [string]$Filter = if ($String -eq 'FileVantagePolicy') {
          # Filter to user-created FileVantagePolicy
          '$_.created_by -ne "cs-cloud-provisioning" -and $_.name -notmatch "^Default Policy \((Linux|Mac|' +
            'Windows)\)$"'
        } else {
          # Filter to user-created FileVantageRuleGroup
          '$_.created_by -ne "internal"'
        }
        $Param = @{ Detailed = $true; All = $true }
        if ($String -eq 'FileVantagePolicy' ) { $Param['include'] = 'exclusions' }
        @((Get-Command "Get-Falcon$String").Parameters.Type.Attributes.ValidValues).foreach{
          # Retrieve FileVantagePolicy/RuleGroup for each 'Type'
          & "Get-Falcon$String" @Param -Type $_ 2>$null |
            Where-Object -FilterScript ([scriptblock]::Create($Filter))
        }
      } elseif ($String -match 'Policy$') {
        @('Windows','Mac','Linux').foreach{
          # Create policy exports in 'platform_name' order to retain precedence
          & "Get-Falcon$String" -Filter "platform_name:'$_'" -Detailed -All 2>$null
        }
      } else {
        & "Get-Falcon$String" -Detailed -All 2>$null
      }
      if ($Config) {
        if ($String -eq 'FirewallPolicy') {
          # Export firewall settings
          Write-Host "[Export-FalconConfig] Exporting 'FirewallSetting'..."
          $Setting = Get-FalconFirewallSetting -Id $Config.id 2>$null
          foreach ($i in $Setting) {
            ($Config | Where-Object { $_.id -eq $i.policy_id }).PSObject.Properties.Add((
              New-Object PSNoteProperty('settings',$i)
            ))
          }
        } elseif ($String -eq 'FileVantageRuleGroup') {
          # Update 'assigned_rules' with rule content inside FileVantage rule groups
          foreach ($i in $Config) {
            $RuleId = $i.assigned_rules.id | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
            if ($RuleId) {
              Write-Host "[Export-FalconConfig] Exporting rules for $($i.type) group '$($i.name)'..."
              $i.assigned_rules = @(Get-FalconFileVantageRule -RuleGroupId $i.id -Id $RuleId)
            }
          }
        }
        # Export results to json file and output created file name
        ConvertTo-Json @($Config) -Depth 32 | Out-File $ConfigFile -Append
        $ConfigFile
      }
    }
    # Get current location and set output archive path
    $Location = (Get-Location).Path
    $ExportFile = Join-Path $Location "FalconConfig_$(Get-Date -Format FileDateTime).zip"
  }
  process {
    $OutPath = Test-OutFile $ExportFile
    if ($OutPath.Category -eq 'WriteError' -and !$Force) {
      Write-Error @OutPath
    } else {
      if (!$Select) {
        # Use items in 'ValidateSet' when not provided
        [string[]]$Select = @((Get-Command $MyInvocation.MyCommand.Name).ParameterSets.Where({ $_.Name -eq
          'ExportItem' }).Parameters.Where({ $_.Name -eq 'Select' }).Attributes.ValidValues).foreach{ $_ }
      }
      if ($Select -contains 'FileVantagePolicy' -and $Select -notcontains 'FileVantageRuleGroup') {
        # Force 'FileVantageRuleGroup' when exporting 'FileVantagePolicy' for 'rule_groups'
        [string[]]$Select = @($Select + 'FileVantageRuleGroup')
      }
      if ($Select -contains 'FirewallGroup') {
        # Force 'FirewallRule' when exporting 'FirewallGroup'
        [string[]]$Select = @($Select + 'FirewallRule')
      }
      if ($Select -match '^((Ioa|Ml|Sv)Exclusion|FileVantagePolicy|Ioc)$' -and $Select -notcontains 'HostGroup') {
        # Force 'HostGroup' when exporting exclusions or IOCs
        [string[]]$Select = @($Select + 'HostGroup')
      }
      # Retrieve results, export to Json and capture file name
      [string[]]$JsonFiles = foreach ($String in $Select) { ,(Get-ItemContent $String) }
      if ($JsonFiles -and $PSCmdlet.ShouldProcess($ExportFile,'Compress-Archive')) {
        # Archive Json exports with content and remove them when complete
        $Param = @{
          Path = (Get-ChildItem | Where-Object { $JsonFiles -contains $_.FullName -and $_.Length -gt 0 }).FullName
          DestinationPath = $ExportFile
          Force = $Force
        }
        Compress-Archive @Param
        @($JsonFiles).foreach{
          if (Test-Path $_) {
            Write-Log 'Export-FalconConfig' "Removing '$_'"
            Remove-Item $_ -Force
          }
        }
      }
      # Display created archive
      if (Test-Path $ExportFile) { Get-ChildItem $ExportFile | Select-Object FullName,Length,LastWriteTime }
    }
  }
}
function Import-FalconConfig {
<#
.SYNOPSIS
Import items from a 'FalconConfig' archive into your Falcon environment
.DESCRIPTION
Creates groups, policies, exclusions, rules and scripts within a 'FalconConfig' archive within your authenticated
Falcon environment.
 
Anything that already exists will be ignored and no existing items will be modified unless the relevant switch
parameters are included.
.PARAMETER Path
FalconConfig archive path
.PARAMETER AssignExisting
Assign existing host groups with identical names to imported items
.PARAMETER ModifyDefault
Modify specified 'platform_default' policies to match import
.PARAMETER ModifyExisting
Modify existing specified items to match import
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Import-FalconConfig
#>

  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Mandatory,Position=1)]
    [ValidatePattern('\.zip$')]
    [ValidateScript({
      if (Test-Path $_ -PathType Leaf) {
        $true
      } else {
        throw "Cannot find path '$_' because it does not exist or is not a file."
      }
    })]
    [string]$Path,
    [Alias('Force')]
    [switch]$AssignExisting,
    [ValidateSet('DeviceControlPolicy','FirewallPolicy','PreventionPolicy','ResponsePolicy','SensorUpdatePolicy')]
    [string[]]$ModifyDefault,
    [ValidateSet('DeviceControlPolicy','FileVantagePolicy','FileVantageRuleGroup','FirewallGroup','FirewallPolicy',
      'HostGroup','IoaExclusion','IoaGroup','Ioc','MlExclusion','PreventionPolicy','ResponsePolicy','Script',
      'SensorUpdatePolicy','SvExclusion')]
    [string[]]$ModifyExisting
  )
  begin {
    function Add-Result {
      # Create result object for CSV output
      param(
        [ValidateSet('Created','Modified','Ignored')]
        [string]$Action,
        [object]$Item,
        [string]$Type,
        [string]$Property,
        [string]$Old,
        [string]$New,
        [string]$Comment
      )
      $Obj = [PSCustomObject]@{
        time = Get-Date -Format o
        api_client_id = $Script:Falcon.ClientId
        type = $Type
        id = if ($Action -eq 'Ignored') {
          $null
        } elseif ($Item.instance_id) {
          $Item.instance_id
        } else {
          $Item.id
        }
        name = if ($Item.value) {
          if ($Item.type) { $Item.type,$Item.value -join ':' } else { $Item.value }
        } elseif ($Item.precedence -and $Type -eq 'FileVantageRule') {
          $Item.precedence
        } else {
          $Item.name
        }
        platform = if ($Item.platform) {
          if ($Item.platform -is [string[]]) { $Item.platform -join ',' } else { $Item.platform }
        } elseif ($Item.platforms) {
          $Item.platforms -join ','
        } elseif ($Item.platform_name) {
          $Item.platform_name
        } elseif ($Type -match '^FileVantageRule(Group)?$') {
          $Item.type
        } elseif ($Type -eq 'FileVantageExclusion' -and $Item.policy_id) {
          @($Config.FileVantagePolicy.PSObject.Properties.Value).Where({ $_.id -eq $Item.policy_id }).platform |
            Select-Object -Unique
        } else {
          $null
        }
        action = $Action
        property = $Property
        old_value = $Old
        new_value = $New
        comment = if ($Comment) {
          $Comment
        } elseif ($Item.rule_group_id -and $Item.id) {
          'rule_group_id',$Item.rule_group_id -join ':'
        } elseif ($Item.policy_id -and $Item.id) {
          'policy_id',$Item.policy_id -join ':'
        }
      }
      $Config.Result.Add($Obj)
      if ($Action -match '^(Created|Modified)$') {
        # Notify when items are created or modified
        [System.Collections.Generic.List[string]]$Notify = @('[Import-FalconConfig]',$Action)
        if ($Property) { $Notify.Add("'$Property' for") }
        if ($Obj.platform -and $Obj.platform -notmatch ',' -and $Type -ne 'FileVantageRule') {
          $Notify.Add($Obj.platform)
        }
        $Notify.Add($Type)
        if ($Type -eq 'FileVantageRule') {
          $Notify.Add("$($Obj.name) in '$(@($Config.Ids.FileVantageRuleGroup).Where({
            $_.new_id -eq $Item.rule_group_id }).name)'."
)
        } elseif ($Type -eq 'FileVantageExclusion') {
          $Notify.Add("'$($Obj.name)' in '$(@($Config.Ids.FileVantagePolicy).Where({
            $_.new_id -eq $Item.policy_id }).name)'."
)
        } else {
          $Notify.Add("'$($Obj.name)'.")
        }
        Write-Host ($Notify -join ' ')
      }
    }
    function Compare-ImportData ([string]$Item) {
      if ($Config.$Item.Cid) {
        # Define properties for comparison between imported and existing items
        [string[]]$Compare = @('name','platform','platform_name','type','value').Where({
          ($Config.$Item.Cid | Get-Member -MemberType NoteProperty).Name -contains $_
        })
        # Capture non-existing items for future modification
        $FilterScript = [scriptblock]::Create((@($Compare).foreach{
          "`$Config.$($Item).Cid.$($_) -notcontains `$_.$($_)" }) -join ' -and ')
        @($Config.$Item.Import | Where-Object -FilterScript $FilterScript).foreach{ $_ }
        if ($ModifyExisting -contains $Item) {
          # Capture (non-policy) items to modify
          $FilterScript = [scriptblock]::Create((@($Compare).foreach{
            "`$Config.$($Item).Cid.$($_) -eq `$_.$($_)" }) -join ' -and ')
          @($Config.$Item.Import | Where-Object -FilterScript $FilterScript).foreach{
            $Config.$Item.Modify.Add($_)
          }
        }
      } elseif ($Config.$Item.Import) {
        # Output all items
        @($Config.$Item.Import)
      }
    }
    function Compare-Setting ([object]$New,[object]$Old,[string]$Type,[string]$Property,[switch]$Result) {
      if ($Type -match 'Policy$') {
        # Compare modified policy settings
        $NewArr = if ($New.prevention_settings) { $New.prevention_settings } else { $New.settings }
        $OldArr = if ($Old.prevention_settings) { $Old.prevention_settings } else { $Old.settings }
        if ($OldArr -or $Result) {
          foreach ($Item in $NewArr) {
            if ($Item.value.PSObject.Properties.Name -eq 'enabled') {
              if ($OldArr.Where({ $_.id -eq $Item.id }).value.enabled -ne $Item.value.enabled) {
                if ($Result) {
                  # Capture modified result for boolean settings
                  Add-Result Modified $New $Type $Item.id ($OldArr.Where({ $_.id -eq
                    $Item.id }).value.enabled) $Item.value.enabled
                } else {
                  # Output setting to be modified
                  $Item | Select-Object id,value
                }
              }
            } else {
              foreach ($Name in $Item.value.PSObject.Properties.Name) {
                if ($OldArr.Where({ $_.id -eq $Item.id }).value.$Name -ne $Item.value.$Name) {
                  if ($Result) {
                    # Capture modified result for sub-settings
                    Add-Result Modified $New $Type ($Item.id,$Name -join ':') (($OldArr | Where-Object {
                      $_.id -eq $Item.id }).value.$Name) $Item.value.$Name
                  } else {
                    # Output setting to be modified
                    $Item | Select-Object id,value
                  }
                }
              }
            }
          }
        } else {
          # Output new settings
          if ($NewArr.id) { $NewArr | Select-Object id,value } else { $NewArr }
        }
      } elseif ($Result) {
        # Compare other modified item properties
        if ($Property -eq 'field_values') {
          foreach ($Name in $New.$Property.name) {
            # Track 'field_values' for IoaRule for each modified value
            $OldValue = ($Old.$Property | Where-Object { $_.name -eq $Name }).values | ConvertTo-Json -Compress
            $NewValue = ($New.$Property | Where-Object { $_.name -eq $Name }).values | ConvertTo-Json -Compress
            if ($NewValue -ne $OldValue) { Add-Result Modified $New $Type $Name $OldValue $NewValue }
          }
        } elseif ($Property) {
          if ($New.$Property -ne $Old.$Property) {
            Add-Result Modified $New $Type $Property $Old.$Property $New.$Property
          }
        } else {
          @($New.PSObject.Properties.Name).Where({ $_ -notmatch '^(id|comment)$' }).foreach{
            if ($New.$_ -ne $Old.$_) { Add-Result Modified $New $Type $_ $Old.$_ $New.$_ }
          }
        }
      }
    }
    function Compress-Property ([object]$Object) {
      # Remove unnecessary properties and values
      if ($Object.applied_globally -eq $true -and $Object.PSObject.Properties.Name -contains 'groups') {
        Set-Property $Object groups @('all')
      }
      if ($Object.prevention_settings.settings) {
        [object[]]$Object.prevention_settings = $Object.prevention_settings.settings | Select-Object id,value
      }
      if ($Object.settings.settings) {
        [object[]]$Object.settings = $Object.settings.settings | Select-Object id,value
      }
      @('groups','host_groups','ioa_rule_groups','policy_assignments','rule_group','rule_groups').foreach{
        if ($Object.$_.id) { [string[]]$Object.$_ = $Object.$_.id }
      }
      return $Object
    }
    function Import-ConfigData ([string]$FilePath) {
      # Load 'FalconConfig' archive into memory
      [hashtable]$Output = @{ Ids = @{}; Result = [System.Collections.Generic.List[object]]@() }
      $ByteStream = if ($PSVersionTable.PSVersion.Major -ge 6) {
        Get-Content $FilePath -AsByteStream
      } else {
        Get-Content $FilePath -Encoding Byte -Raw
      }
      [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null
      $FileStream = New-Object System.IO.MemoryStream
      $FileStream.Write($ByteStream,0,$ByteStream.Length)
      $ConfigArchive = New-Object System.IO.Compression.ZipArchive($FileStream)
      [string[]]$Msg = foreach ($FullName in $ConfigArchive.Entries.FullName) {
        # Import Json, exclude unnecessary properties, add to output and notify
        $Filename = $ConfigArchive.GetEntry($FullName)
        $Item = ($FullName | Split-Path -Leaf).Split('.')[0]
        $Import = ConvertFrom-Json (New-Object System.IO.StreamReader($Filename.Open())).ReadToEnd()
        $Output[$Item] = @{ Import = $Import; Modify = [System.Collections.Generic.List[object]]@() }
        $Output.Ids[$Item] = [System.Collections.Generic.List[object]]@()
        $Item
      }
      if ($FileStream) { $FileStream.Dispose() }
      if ($Msg) { Write-Host "[Import-FalconConfig] Imported from $($FilePath): $($Msg -join ', ')." }
      $Output
    }
    function Invoke-CreateIoc ([object]$Object) {
      foreach ($i in ($Object.Value.Import | & "New-Falcon$($Object.Key)")) {
        if ($i.id) {
          # Track created Ioc
          Update-Id $i $Object.Key
          Add-Result Created $i $Object.Key
        } elseif ($i.type -and $i.value -and $i.message) {
          @($Object.Value.Import).Where({ $_.type -eq $i.type -and $_.value -eq $i.value }).foreach{
            # Ignore failed Ioc
            Add-Result Ignored $_ $Object.Key -Comment $i.message
          }
        }
        # Remove created and failed Ioc from 'Import' using 'id' value
        [string[]]$Remove = @($Object.Value.Import).Where({ $_.type -eq $i.type -and $_.value -eq $i.value }).id
        $Object.Value.Import = @($Object.Value.Import).Where({ $Remove -notcontains $_.id })
      }
      # Repeat until 'Import' is empty
      if ($Object.Value.Import) { Invoke-CreateIoc $Object }
    }
    function Invoke-PolicyAction ([string]$Type,[string]$Action,[string]$PolicyId,[string]$GroupId) {
      try {
        # Perform an action on a policy and output result
        if ($GroupId -and $PolicyId) {
          $PolicyId | & "Invoke-Falcon$($Type)Action" -Name $Action -GroupId $GroupId
        } elseif ($PolicyId) {
          $PolicyId | & "Invoke-Falcon$($Type)Action" -Name $Action
        }
      } catch {
        Write-Error $_
      }
    }
    function Submit-Group ([string]$Type,[string]$Property,[object]$Object,[object]$Cid) {
      if ($Type -eq 'FileVantagePolicy') {
        if ($Property -eq 'rule_groups' -and $Object.rule_groups) {
          # Assign missing 'rule_groups'
          foreach ($OldId in $Object.rule_groups) {
            # Update rule_groups with new identifier(s)
            $Object.rule_groups = $Object.rule_groups -replace $OldId,
              @($Config.Ids.FileVantageRuleGroup).Where({ $_.old_id -eq $OldId }).new_id
          }
          if ($Object.rule_groups) {
            # Filter to rule_groups that are missing
            [string[]]$Add = @($Object.rule_groups).Where({ $Cid.rule_groups -notcontains $_ -and
              ![string]::IsNullOrWhiteSpace($_) })
            if ($Add) {
              # Assign and capture result
              @(Add-FalconFileVantageRuleGroup -PolicyId $Object.id -Id $Add).foreach{
                Add-Result Modified $_ $Pair.Key rule_groups ($Cid.rule_groups -join ',') (
                  $_.rule_groups.id -join ',')
              }
              
            }
          }
        } elseif ($Property -eq 'host_groups' -and $Object.host_groups) {
          # Filter to host_groups that are missing
          [string[]]$Add = @($Object.host_groups).Where({ $Cid.host_groups -notcontains $_ })
          if ($Add) {
            # Assign and capture result
            @(Add-FalconFileVantageHostGroup -PolicyId $Object.id -Id $Add).foreach{
              Add-Result Modified $_ $Pair.Key host_groups ($Cid.host_groups -join ',') (
                $_.host_groups.id -join ',')
            }
          }
        }
      } else {
        # Assign group(s) to target object
        [string]$Invoke = if ($Property -eq 'ioa_rule_groups') { 'add-rule-group' } else { 'add-host-group' }
        $Req = foreach ($Id in $Object.$Property) {
          if ($Cid.$Property -notcontains $Id) { @(Invoke-PolicyAction $Type $Invoke $Object.id $Id).foreach{ $_ }}
        }
        if ($Req) {
          # Capture result
          Add-Result Modified $Req[-1] $Type $Property ($Cid.$Property -join ',') ($Req[-1].$Property.id -join ',')
        }
      }
    }
    function Update-Id ([object]$Item,[string]$Type) {
      if ($Config.Ids.$Type) {
        # Add 'new_id' to 'Ids'
        [string[]]$Compare = @('platform_name','platform','type','value','name').foreach{ if ($Item.$_) { $_ }}
        [string]$Filter = (@($Compare).foreach{ "`$_.$($_) -eq '$($Item.$_)'" }) -join ' -and '
        @($Config.Ids.$Type | Where-Object -FilterScript ([scriptblock]::Create($Filter))).foreach{
          $_.new_id = if ($Item.family) { $Item.family } else { $Item.id }
        }
      }
    }
    [string[]]$Allowed = (Get-Command Export-FalconConfig).Parameters.Select.Attributes.ValidValues
    [string]$ArchivePath = $Script:Falcon.Api.Path($PSBoundParameters.Path)
    [string]$OutputFile = Join-Path (Get-Location).Path "FalconConfig_$(Get-Date -Format FileDateTime).csv"
    [regex]$PolicyDefault = '^(platform_default|Default Policy \((Linux|Mac|Windows)\))$'
    [string]$UserAgent = (Show-FalconModule).UserAgent
    [string[]]$ValidModify = (Get-Command Import-FalconConfig).Parameters.ModifyExisting.Attributes.ValidValues
  }
  process {
    # Import configuration files and capture identifiers for comparison
    if (!$ArchivePath) { throw "Failed to resolve '$($PSBoundParameters.Path)'." }
    $Config = Import-ConfigData $ArchivePath
    @($Config.Keys).Where({ $_ -notmatch '^(Ids|FirewallRule|Result)$' }).foreach{
      if ($Allowed -notcontains $_) {
        throw "'$($_,'json' -join '.')' is not a valid Json file name. Ensure that all files within '$(
          $ArchivePath)' correspond with output from 'Export-FalconConfig' [$(($Allowed | ForEach-Object {
            "'$_'"
          }) -join ',')]."

      }
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Import })) {
      foreach ($Import in $Pair.Value.Import) {
        # Create a record of identifiers within CID to compare with imports
        $Import = Compress-Property $Import
        @($Import | Select-Object name,platform,platforms,platform_name,type,value).foreach{
          $Id = if ($Import.family) { $Import.family } else { $Import.id }
          Set-Property $_ old_id $Id
          Set-Property $_ new_id $null
          $Config.Ids.($Pair.Key).Add($_)
        }
      }
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch '^(Ids|Result)$' })) {
      $Pair.Value['Cid'] = try {
        if ($Pair.Key -match '^FileVantage(Policy|RuleGroup)$') {
          [string]$Property = if ($Pair.Key -eq 'FileVantagePolicy') { 'platform' } else { 'type' }
          [string]$Filter = if ($Pair.Key -eq 'FileVantagePolicy') {
            # Filter to user-created FileVantagePolicy
            '$_.created_by -ne "cs-cloud-provisioning" -and $_.name -notmatch "' + $PolicyDefault + '"'
          } else {
            # Filter to user-created FileVantageRuleGroup
            '$_.created_by -ne "internal"'
          }
          $Param = @{ Detailed = $true; All = $true }
          if ($Pair.Key -eq 'FileVantagePolicy' -and $Config.($Pair.Key).Import.exclusions) {
            # Include 'exclusions' if present in imported policies
            $Param['include'] = 'exclusions'
          }
          @($Pair.Value.Import.$Property | Select-Object -Unique).foreach{
            # Retrieve FileVantagePolicy/RuleGroup for each 'Type' and update identifiers
            Write-Host "[Import-FalconConfig] Retrieving $_ '$($Pair.Key)'..."
            foreach ($i in (& "Get-Falcon$($Pair.Key)" @Param -Type $_ | Where-Object -FilterScript (
            [scriptblock]::Create($Filter)))) {
              if ($Pair.Key -eq 'FileVantageRuleGroup' -and $i.assigned_rules.id -and
              @($Pair.Value.Import).Where({ $_.type -eq $i.type -and $_.name -eq $i.name })) {
                # Update FileVantageRuleGroup 'assigned_rules' with rule content when matching import is present
                $RuleId = $i.assigned_rules.id | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
                if ($RuleId) {
                  Write-Host "[Import-FalconConfig] Retrieving rules for $($i.type) group '$($i.name)'..."
                  $i.assigned_rules = Get-FalconFileVantageRule -RuleGroupId $i.id -Id $RuleId
                }
              }
              Update-Id $i $Pair.Key
              Compress-Property $i
            }
          }
        } else {
          # Retrieve existing items from CID and update identifiers
          Write-Host "[Import-FalconConfig] Retrieving '$($Pair.Key)'..."
          @(& "Get-Falcon$($Pair.Key)" -Detailed -All).foreach{
            Update-Id $_ $Pair.Key
            Compress-Property $_
          }
        }
      } catch {
        throw $_
      }
      if ($Pair.Key -match 'Policy$') {
        $Pair.Value.Import = foreach ($Policy in $Pair.Value.Import) {
          if (!($Config.($Pair.Key).Cid | Where-Object { $_.name -eq $Policy.name -and
          (($Policy.platform_name -and $_.platform_name -eq $Policy.platform_name) -or ($Policy.platform -and
          $_.platform -eq $Policy.platform)) })) {
            # Keep only missing policy items for each OS under 'Import'
            $Policy
            $Pair.Value.Modify.Add($Policy.PSObject.Copy())
          } else {
            # Add to relevant 'Modify' list or add result as 'Ignored'
            if ($Policy.name -match $PolicyDefault) {
              if ($ModifyDefault -contains $Pair.Key) {
                $Pair.Value.Modify.Add($Policy.PSObject.Copy())
              } else {
                Add-Result Ignored $Policy $Pair.Key -Comment NotModifyDefault
              }
            } elseif ($ModifyExisting -contains $Pair.Key) {
              $Pair.Value.Modify.Add($Policy.PSObject.Copy())
            } else {
              Add-Result Ignored $Policy $Pair.Key -Comment NotModifyExisting
            }
          }
        }
      } elseif ($Pair.Key -ne 'FirewallRule') {
        foreach ($Item in $Pair.Value.Import) {
          # Track 'Ignored' items for final output
          [string]$Comment = if ($Item.deleted -eq $true) {
            'Deleted'
          } elseif ($Item.type -and $Item.value -and @($Pair.Value.Cid).Where({ $_.type -eq $Item.type -and
          $_.value -eq $Item.value })) {
            'Exists'
          } elseif ($Item.type -and $Item.name -and @($Pair.Value.Cid).Where({ $_.type -eq $Item.type -and
          $_.name -eq $Item.name })) {
            'Exists'
          } elseif ($Item.value -and @($Pair.Value.Cid).Where({ $_.value -eq $Item.value })) {
            'Exists'
          } elseif ($Item.name -and @($Pair.Value.Cid).Where({ $_.name -eq $Item.name })) {
            'Exists'
          }
          if ($Comment -and $ModifyExisting -notcontains $Pair.Key) {
            # If 'Exists' but it could be modified, update comment
            if ($Comment -eq 'Exists' -and $ValidModify -contains $Pair.Key) { $Comment = 'NotModifyExisting' }
            Add-Result Ignored $Item $Pair.Key -Comment $Comment
          }
        }
        if ($Pair.Key -eq 'FileVantageRuleGroup') {
          $Pair.Value.Import = foreach ($i in $Pair.Value.Import) {
            if (!@($Pair.Value.Cid).Where({ $_.type -eq $i.type -and $_.name -eq $i.name })) {
              # Remove rule groups that will not be created from 'Import'
              $i
            } elseif ($ModifyExisting -contains $Pair.Key) {
              # Add rule groups for modification
              $Config.($Pair.Key).Modify.Add($i)
            }
          }
        } else {
          $Pair.Value.Import = Compare-ImportData $Pair.Key
        }
      }
      if ($Pair.Key -eq 'SensorUpdatePolicy' -and ($Pair.Value.Import -or $Pair.Value.Modify)) {
        # Retrieve available sensor build versions to update 'tags'
        [object[]]$BuildList = try {
          Write-Host "[Import-FalconConfig] Retrieving available sensor builds..."
          Get-FalconBuild
        } catch {
          throw "Failed to retrieve available sensor builds for '$(
            $Pair.Key)' import. Verify 'Sensor update policies: Write' permission."

        }
        foreach ($Item in @(@($Pair.Value.Import) + @($Pair.Value.Modify))) {
          # Update tagged builds with current tagged build versions
          if ($Item.settings.build -match '^\d+\|') {
            [string]$Tag = ($Item.settings.build -split '\|',2)[-1]
            # Replace 'latest' tagged build suffix digits with wildcard for imports into different clouds
            if ($Tag -match '^n|tagged|\d{1,}$') { $Tag = $Tag -replace '\d{1,}$','*' }
            $Current = @($BuildList).Where({ $_.build -like "*|$Tag" -and $_.platform -eq
              $Item.platform_name }).build
            if ($Current -and $Item.settings.build -ne $Current) {
              $Item.settings.build = $Current
              Write-Log 'Import-FalconConfig' "Updated build from '$($Item.settings.build)' to '$Current'"
            } elseif (!$Current) {
              Write-Log 'Import-FalconConfig' "Failed to match '$Tag' to current build for '$(
                $Item.platform_name)'"

            }
          }
          if ($Item.settings.variants) {
            # Update tagged 'variant' builds with current tagged build versions
            foreach ($Variant in @($Item.settings.variants | Where-Object { $_.build -match '^\d+\|' })) {
              $Tag = ($Variant.build -split '\|',2)[-1]
              $Current = ($BuildList | Where-Object { $_.build -like "*|$Tag" -and $_.platform -eq
                $Variant.platform }).build
              if ($Current -and $Variant.build -ne $Current) {
                $Variant.build = $Current
                Write-Log 'Import-FalconConfig' "Updated variant build from '$($Variant.build)' to '$Current'"
              } elseif (!$Current) {
                Write-Log 'Import-FalconConfig' "Failed to match '$Tag' to current build for variant '$(
                  $Variant.platform)'"

              }
            }
          }
        }
      }
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -eq 'HostGroup' -and $_.Value.Import })) {
      foreach ($HostGroup in ($Pair.Value.Import | & "New-Falcon$($Pair.Key)")) {
        # Create HostGroup to prepare for assignment
        Update-Id $HostGroup $Pair.Key
        Add-Result Created $HostGroup $Pair.Key
      }
      [void]$Pair.Value.Remove('Import')
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Import -or $_.Value.Modify })) {
      # Update 'Import' and 'Modify' HostGroup ids
      @('Import','Modify').foreach{
        foreach ($Item in $Pair.Value.$_) {
          @('groups','host_groups').foreach{
            foreach ($OldId in $Item.$_) {
              [string]$NewId = @($Config.Ids.HostGroup).Where({ $_.old_id -eq $OldId }).new_id
              if ($NewId) {
                [string[]]$Item.$_ = $Item.$_ -replace $OldId,$NewId
                if ($NewId -ne $OldId) {
                  Write-Log 'Import-FalconConfig' ('Updated {0} "{1}" id "{2}" to "{3}"' -f $Pair.Key,$_,$OldId,
                    $NewId)
                }
              }
            }
          }
        }
      }
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Import })) {
      @($Pair.Value.Import | & "New-Falcon$($Pair.Key)").foreach{
        # Create Policy
        Update-Id $_ $Pair.Key
        Add-Result Created $_ $Pair.Key
      }
      [void]$Pair.Value.Remove('Import')
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -ne 'FirewallRule' -and $_.Value.Import })) {
      if ($Pair.Key -eq 'FileVantageRuleGroup') {
        foreach ($Item in $Pair.Value.Import) {
          @($Item | & "New-Falcon$($Pair.Key)").foreach{
            # Create FileVantageRuleGroup
            Set-Property $Item id $_.id
            Update-Id $_ $Pair.Key
            Add-Result Created $_ $Pair.Key
          }
          if ($Item.assigned_rules) {
            foreach ($FvRule in $Item.assigned_rules) {
              # Update FileVantageRule with new RuleGroup identifier, create rule and notify
              $FvRule.rule_group_id = $Item.id
              @($FvRule | New-FalconFileVantageRule).foreach{ Add-Result Created $_ FileVantageRule }
            }
          }
        }
      } elseif ($Pair.Key -eq 'FirewallGroup') {
        foreach ($Item in $Pair.Value.Import) {
          [object]$FwGroup = $Item | Select-Object name,enabled,description,comment,rule_ids,platform
          if ($FwGroup) {
            if ($FwGroup.rule_ids) {
              # Select FirewallRule from import using 'family' as 'id' value
              [object[]]$Rules = foreach ($Id in $FwGroup.rule_ids) {
                $Config.FirewallRule.Import | Where-Object { $_.family -eq $Id -and $_.deleted -eq $false }
              }
              @($Rules).foreach{
                # Trim rule names to 64 characters and use 'rules' as 'rule_ids'
                if ($_.name.length -gt 64) { $_.name = ($_.name).SubString(0,63) }
              }
              if ($Rules) {
                Set-Property $FwGroup rules $Rules
                [void]$FwGroup.PSObject.Properties.Remove('rule_ids')
              }
            }
            @($FwGroup | & "New-Falcon$($Pair.Key)").foreach{
              # Create FirewallGroup
              Set-Property $FwGroup id $_
              Update-Id $FwGroup $Pair.Key
              Add-Result Created $FwGroup $Pair.Key
            }
          }
        }
      } elseif ($Pair.Key -eq 'IoaGroup') {
        foreach ($Item in $Pair.Value.Import) {
          # Create IoaGroup
          [object]$IoaGroup = $Item | & "New-Falcon$($Pair.Key)"
          if ($IoaGroup) {
            Update-Id $IoaGroup $Pair.Key
            Add-Result Created $IoaGroup $Pair.Key
            if ($Item.rules) {
              # Create IoaRule
              [object[]]$IoaGroup.rules = foreach ($Rule in $Item.rules) {
                $Rule.rulegroup_id = $IoaGroup.id
                $Req = try { $Rule | New-FalconIoaRule } catch { Write-Error $_ }
                if ($Req) {
                  Add-Result Created $Req IoaRule
                  if ($Req.enabled -eq $false -and $Rule.enabled -eq $true) { $Req.enabled = $true }
                  $Req
                }
              }
              if ($IoaGroup.rules.enabled -eq $true) {
                @($IoaGroup | Edit-FalconIoaRule).foreach{
                  @($_.rules).Where({ $_.enabled -eq $true }).foreach{
                    # Enable IoaRule
                    Add-Result Modified $_ IoaRule enabled $false $_.enabled
                  }
                }
              }
            }
            if ($Item.enabled -eq $true -and $IoaGroup.enabled -ne $true) {
              @(& "Edit-Falcon$($Pair.Key)" -Id $IoaGroup.id -Enabled $true).foreach{
                # Enable IoaGroup
                Add-Result Modified $Item $Pair.Key enabled $false $_.enabled
              }
            }
          }
        }
      } elseif ($Pair.Key -eq 'Ioc') {
        # Create Ioc
        Invoke-CreateIoc $Pair
      } elseif ($Pair.Key -eq 'Script') {
        foreach ($Item in $Pair.Value.Import) {
          # Create Script
          @($Item | & "Send-Falcon$($Pair.Key)").foreach{
            Add-Result Created ($Item | Select-Object name,platform) $Pair.Key
          }
        }
      } elseif ($Pair.Key -match '^((Ioa|Ml|Sv)Exclusion)$') {
        foreach ($Item in $Pair.Value.Import) {
          # Create Exclusion
          @($Item | & "New-Falcon$($Pair.Key)").foreach{
            Update-Id $_ $Pair.Key
            Add-Result Created $_ $Pair.Key
          }
        }
      }
      [void]$Pair.Value.Remove('Import')
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch 'Policy$' -and $_.Value.Modify })) {
      if ($Pair.Key -eq 'FileVantageRuleGroup') {
        foreach ($Group in $Pair.Value.Modify) {
          $CidGroup = @($Config.($Pair.Key).Cid).Where({ $_.type -eq $Group.type -and $_.name -eq $Group.name })
          if ($CidGroup) {
            foreach ($Rule in $Group.assigned_rules) {
              $CidRule = @($CidGroup.assigned_rules).Where({ $_.type -eq $Rule.type -and $_.name -eq $Rule.name })
              if ($CidRule) {
                # Evaluate and update each FileVantageRule when required
                [string[]]$Modified = @($Rule.PSObject.Properties.Name).Where({ $_ -notmatch
                '^(type|(rule_group_)?id)$' }).foreach{
                  if (!$CidRule.$_ -or $CidRule.$_ -ne $Rule.$_) {
                    if (($_ -eq 'content_files' -and ![string]::IsNullOrWhiteSpace($CidRule.$_) -or
                    ![string]::IsNullOrWhiteSpace($Rule.$_)) -or $_ -ne 'content_files') {
                      $_
                    }
                  }
                }
                if ($Modified) {
                  @('id','rule_group_id').foreach{ $Rule.$_ = $CidRule.$_ }
                  $Req = $Rule | Edit-FalconFileVantageRule
                  if ($Req) {
                    @($Modified).foreach{ Add-Result Modified $Req FileVantageRule $_ $CidRule.$_ $Req.$_ }
                  }
                }
              } else {
                # Add rules that don't exist at the bottom of the existing FileVantageRuleGroup
                $Rule.rule_group_id = $CidGroup.id
                $Rule.precedence = $CidGroup.assigned_rules.precedence[-1] + 1
                $Req = $Rule | New-FalconFileVantageRule
                if ($Req) { Add-Result Created $Req FileVantageRule }
              }
            }
            if (!$Modified -and $Rule.group_id -ne $CidGroup.id) {
              # Add 'Ignored' result for unmodified FileVantageRuleGroup
              Add-Result Ignored $Group $Pair.Key -Comment Identical
            }
          }
        }
      } else {
        [string[]]$Select = switch ($Pair.Key) {
          # Select required properties for comparison (other than 'id')
          'FirewallGroup' { 'name','enabled','rule_ids' }
          'HostGroup' { 'group_type','name','assignment_rule' }
          'IoaGroup' { 'enabled','name','platform','rules' }
          'IoaExclusion' { 'name','pattern_id','pattern_name','cl_regex','ifn_regex','groups','applied_globally' }
          'Ioc' {
            'applied_globally','action','deleted','expiration','host_groups','mobile_action','platforms',
            'severity','tags','type','value'
          }
          'MlExclusion' { 'value','excluded_from','groups','applied_globally' }
          'Script' { 'platform','permission_type','name','content' }
          'SvExclusion' { 'value','groups','applied_globally' }
        }
        if ($Select) {
          [object[]]$EditList = foreach ($Item in ($Pair.Value.Modify | Select-Object @($Select + 'id'))) {
            # Compare each 'Modify' item against CID (excluding non-dynamic HostGroup)
            [string[]]$Compare = @('name','type','value').foreach{ if ($Select -contains $_) { $_ }}
            [string]$Filter = (@($Compare).foreach{ "`$_.$($_) -eq `$Item.$($_)" }) -join ' -and '
            [object]$Cid = $Config.($Pair.Key).Cid | Select-Object $Select | Where-Object -FilterScript (
              [scriptblock]::Create($Filter))
            if ($Cid) {
              [System.Collections.Generic.List[string]]$Modify = @('id')
              @($Select).Where({ $_ -ne 'id' }).foreach{
                [object]$Diff = if ($null -ne $Item.$_ -and $null -ne $Cid.$_) {
                  # Compare properties that exist in both 'Modify' and CID
                  if ($Pair.Key -eq 'FirewallGroup' -and $_ -eq 'rule_ids') {
                    <#
                    if ($Item.rule_ids) {
                      # Select FirewallRule from import using 'family' as 'id' value
                      [object[]]$FwRule = foreach ($Rule in $Item.rule_ids) {
                        $Config.FirewallRule.Import | Where-Object { $_.family -eq $Rule -and
                          $_.deleted -eq $false }
                      }
                      if ($FwRule) {
                        # Evaluate rules for modification
                      }
                    }
                    #>

                  } elseif ($Pair.Key -eq 'IoaGroup' -and $_ -eq 'rules') {
                    foreach ($Rule in $Item.$_) {
                      # Evaluate each IoaRule
                      [object]$CidRule = $Cid.$_ | Where-Object { $_.ruletype_id -eq $Rule.ruletype_id -and
                        $_.name -eq $Rule.name -and $Rule.deleted -ne $true }
                      [string[]]$RuleDiff = if ($CidRule) {
                        @('enabled','pattern_severity','action_label').foreach{
                          if (Compare-Object $Rule.$_ $CidRule.$_) { $_ }
                        }
                        foreach ($FieldValue in $Rule.field_values) {
                          # Evaluate 'field_value' as a Json string for each IoaRule
                          [object]$CidFieldValue = $CidRule.field_values | Where-Object {
                            $_.name -eq $FieldValue.name -and $_.type -eq $FieldValue.type }
                          if ($CidFieldValue) {
                            if (Compare-Object ($FieldValue.values | ConvertTo-Json) ($CidFieldValue.values |
                            ConvertTo-Json)) {
                              'field_values'
                            }
                          }
                        }
                      }
                      if ($RuleDiff) {
                        # Copy existing rule and modify properties
                        [object]$RuleEdit = $CidRule.PSObject.Copy()
                        @($RuleDiff).foreach{ $RuleEdit.$_ = $Rule.$_ }
                        @(Edit-FalconIoaRule -RuleUpdate $RuleEdit -RuleGroupId $Item.id).foreach{
                          # Capture result for each updated setting against original
                          @($RuleDiff).foreach{ Compare-Setting $RuleEdit $CidRule IoaRule $_ -Result }
                        }
                      }
                    }
                  } else {
                    Compare-Object $Item.$_ $Cid.$_
                  }
                }
                # Output properties that differ, or are not present in CID
                if ($Diff -or ($null -ne $Item.$_ -and $null -eq $Cid.$_)) { $Modify.Add($_) }
              }
              # Output items with properties to be modified and remove from 'Modify' list
              if ($Modify.Count -gt 1) { $Item | Select-Object $Modify }
            }
          }
          if ($EditList) {
            foreach ($Edit in $EditList) {
              # Update with current 'id' and 'comment' when appropriate
              Set-Property $Edit id ($Config.Ids.($Pair.Key) | Where-Object { $_.old_id -eq $Edit.id }).new_id
              if ($Pair.Key -ne 'HostGroup') {
                Set-Property $Edit comment ($UserAgent,"Import-FalconConfig" -join ': ')
              }
            }
            if ($Pair.Key -eq 'FirewallGroup') {
              [hashtable[]]$DiffOp = @($EditList).foreach{
                # Create 'DiffOperations' for FirewallGroup changes
                if ($null -ne $_.enabled) { @{ op = 'replace'; path = "/enabled"; value = $_.enabled }}
              }
              if ($DiffOp) {
                # Modify FirewallGroup
                $Req = $EditList | Edit-FalconFirewallGroup -DiffOperation $DiffOp
                if ($Req) {
                  Compare-Setting $Item ($Config.($Pair.Key).Cid | Where-Object { $_.id -eq
                    $Item.id }) $Pair.Key -Result
                }
              }
            } else {
              foreach ($Item in ($EditList | & "Edit-Falcon$($Pair.Key)")) {
                foreach ($Result in ($EditList | Where-Object { $_.id -eq $Item.id })) {
                  @($Result.PSObject.Properties.Name).Where({ $_ -ne 'id' -and $_ -ne 'comment' }).foreach{
                    # Modify item and capture result
                    Compare-Setting $Item ($Config.($Pair.Key).Cid | Where-Object { $_.id -eq
                      $Item.id }) $Pair.Key $_ -Result
                  }
                }
              }
            }
          }
          foreach ($Item in $Pair.Value.Modify) {
            if (($EditList -and $EditList.id -notcontains $Item.id) -or !$EditList) {
              # Record result for items that don't need modification
              [string]$Comment = if ($Pair.Key -eq 'HostGroup' -and $Item.group_type -ne 'dynamic') {
                'Static'
              } else {
                'Identical'
              }
              Add-Result Ignored $Item $Pair.Key -Comment $Comment
            }
          }
        }
      }
      [void]$Pair.Value.Remove('Modify')
    }
    foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Modify })) {
      foreach ($Policy in $Pair.Value.Modify) {
        # Update policy with current id value and use CID value for comparison
        [string]$Policy.id = @($Config.Ids.($Pair.Key)).Where({ $_.name -eq $Policy.name -and
          $_.platform_name -eq $Policy.platform_name }).new_id
        [object]$Cid = @($Config.($Pair.Key).cid).Where({ $_.id -eq $Policy.id })
        if ($Policy.id -and $Pair.Key -eq 'FirewallPolicy') {
          if ($Policy.settings.policy_id) { $Policy.settings.policy_id = $Policy.id }
          foreach ($Id in $Policy.rule_group_ids) {
            # Update 'rule_group_ids' with new id values
            [object]$Group = $Config.Ids.FirewallGroup | Where-Object { $_.old_id -eq $Id }
            if ($Group -and $Policy.rule_group_ids -contains $Id) {
              [string[]]$Policy.rule_group_ids = $Policy.rule_group_ids -replace $Id,$Group.new_id
            }
          }
          if ($Policy.settings) {
            # Apply FirewallSetting
            @($Policy.settings | Edit-FalconFirewallSetting).foreach{
              Set-Property $Policy settings $Policy.settings
            }
          }
        } elseif ($Policy.id -and $Policy.prevention_settings -or $Policy.settings) {
          # Compare Policy settings
          $Setting = Compare-Setting $Policy $Cid $Pair.Key
          if ($Setting) {
            try {
              # Modify Policy
              @(& "Edit-Falcon$($Pair.Key)" -Id $Policy.id -Setting $Setting).foreach{
                Compare-Setting (Compress-Property $_) $Cid $Pair.Key -Result
              }
            } catch {
              Write-Error $_
            }
          }
        }
        if ($Policy.id -and $Policy.name -notmatch $PolicyDefault) {
          if ($Pair.Key -eq 'FileVantagePolicy') {
            if ($Policy.exclusions) {
              foreach ($Exclusion in $Policy.exclusions) {
                # Check for existing matching exclusion
                $Existing = @($Cid.exclusions).Where({ $_.name -eq $Exclusion.name })
                if ($null -eq $Exclusion.repeated.PSObject.Properties.Name) {
                  # Remove 'repeated' from imported exclusion when empty to prevent submission error
                  $Exclusion.PSObject.Properties.Remove('repeated')
                }
                if ($Existing) {
                  [string[]]$Modified = @($Exclusion.PSObject.Properties.Name).Where({ $_ -notmatch
                  '^((policy_)?id|\w+_timestamp)$' }).foreach{
                    # Compare existing exclusion against import to find new or modified properties
                    if ($_ -eq 'repeated') {
                      foreach ($i in $Exclusion.repeated.PSObject.Properties.Name) {
                        if (!$Existing.repeated.$i -or $Existing.repeated.$i -ne $Exclusion.repeated.$i) {
                          # Check each sub-property under 'repeated'
                          'repeated'
                        }
                      }
                    } elseif (!$Existing.$_ -or $Exclusion.$_ -ne $Existing.$_) {
                      $_
                    }
                  } | Select-Object -Unique
                  if ($Modified) {
                    # Update identifiers and modify exclusion
                    @('id','policy_id').foreach{ $Exclusion.$_ = $Existing.$_ }
                    $Req = $Exclusion | Edit-FalconFileVantageExclusion
                    if ($Req) {
                      @($Modified).foreach{
                        if ($_ -eq 'repeated') {
                          # Convert 'repeated' to a string for CSV output
                          Add-Result Modified $Req FileVantageExclusion $_ ($Existing.$_ | Format-List |
                            Out-String).Trim() ($Req.$_ | Format-List | Out-String).Trim()
                        } else {
                          Add-Result Modified $Req FileVantageExclusion $_ $Existing.$_ $Req.$_
                        }
                      }
                    }
                  }
                } else {
                  # Create FileVantageExclusion
                  $Exclusion.policy_id = $Policy.id
                  $Req = $Exclusion | New-FalconFileVantageExclusion
                  if ($Req) { Add-Result Created $Req FileVantageExclusion }
                }
              }
            }
            # Assign rule_groups and host_groups
            @('rule_groups','host_groups').foreach{ if ($Policy.$_) { Submit-Group $Pair.Key $_ $Policy $Cid }}
            if ($Cid.enabled -ne $Policy.enabled) {
              # Enable/disable FileVantagePolicy
              $Req = $Policy | Edit-FalconFileVantagePolicy
              if ($Req) { Add-Result Modified $Req $Pair.Key enabled $Cid.enabled $Policy.enabled }
            }
          } else {
            # Assign IoaGroup and HostGroup
            if ($Policy.ioa_rule_groups) { Submit-Group $Pair.Key ioa_rule_groups $Policy $Cid }
            if ($Policy.groups) { Submit-Group $Pair.Key groups $Policy $Cid }
            if ($Policy.host_groups) { Submit-Group $Pair.Key host_groups $Policy $Cid }
            if ($Cid.enabled -ne $Policy.enabled) {
              # Enable/disable policy
              [string]$Action = if ($Policy.enabled -eq $true) { 'enable' } else { 'disable' }
              $Req = Invoke-PolicyAction $Pair.Key $Action $Policy.id
              if ($Req) { Add-Result Modified $Req $Pair.Key enabled $Cid.enabled $Policy.enabled }
            }
          }
        }
      }
      [void]$Pair.Value.Remove('Modify')
    }
  }
  end {
    if ($Config.Result | Where-Object { $_.action -ne 'Ignored' }) {
      # Output warning for existing policy precedence
      foreach ($Item in ($Config.Result | Where-Object { $_.action -eq 'Created' -and $_.type -match 'Policy$' } |
      Select-Object type,platform -Unique)) {
        if ($Config.($Item.type).Cid | Where-Object { $_.platform_name -eq $Item.platform -and $_.name -ne
        'platform_default' }) {
          $PSCmdlet.WriteWarning("[Import-FalconConfig] Existing $($Item.platform) $(
            $Item.type) items were found. Verify precedence!"
)
        }
      }
    }
    if ($Config.Result) {
      # Output results to CSV
      @($Config.Result).foreach{ try { $_ | Export-Csv $OutputFile -NoTypeInformation -Append } catch { $_ }}
      if (Test-Path $OutputFile) { Get-ChildItem $OutputFile | Select-Object FullName,Length,LastWriteTime }
    }
  }
}