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('ContentPolicy','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 '(?<!Content)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
        try {
          ConvertTo-Json @($Config) -Depth 32 | Out-File $ConfigFile -Append
          $ConfigFile
        } catch {
          throw "Unable to write to '$((Get-Location).Path)'. Try 'Export-FalconConfig' in a new location."
        }
      }
    }
    # 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({$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('ContentPolicy','DeviceControlPolicy','PreventionPolicy','ResponsePolicy','SensorUpdatePolicy')]
    [string[]]$ModifyDefault,
    [ValidateSet('ContentPolicy','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 '(^all$|,)' -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) {
        $Platform = @{}
        @('platform','platform_name').foreach{
          if ($Config.$Item.Cid.$_) {
            @($Config.$Item.Cid.$_ | Select-Object -Unique).foreach{ $Platform[$_] = @{} }
          }
        }
        if ($Platform.Count -gt 0) {
          foreach ($Key in $Platform.Keys) {
            # Define properties for comparison between imported and existing items (by platform)
            @('Cid','Import').foreach{
              $Platform.$Key[$_] = @($Config.$Item.$_).Where({$_.platform -eq $Key -or $_.platform_name -eq $Key})
            }
            [string[]]$Available = ($Platform.$Key.Cid | Get-Member -MemberType NoteProperty |
              Select-Object -Unique).Name
            [string[]]$Compare = @('name','type','value').Where({$Available -contains $_})
            Write-Log 'Import-FalconConfig' "Evaluating $Key $Item using '$($Compare -join ',')'"
            $FilterScript = [scriptblock]::Create(
              (@($Compare).foreach{ "`$Platform.`$Key.Cid.$_ -notcontains `$_.$_" }) -join ' -and '
            )
            @($Platform.$Key.Import | Where-Object -FilterScript $FilterScript).foreach{
              # Capture items for import (by platform)
              Write-Log 'Import-FalconConfig' "Selecting '$(
                if ($_.value) {
                  if ($_.type) { $_.type,$_.value -join ':' } else { $_.value }
                } elseif ($_.precedence -and $Item -eq 'FileVantageRule') {
                  $_.precedence
                } else {
                  $_.name
                }
              )' for import"

              $_
            }
            if ($ModifyExisting -contains $Item) {
              # Capture (non-policy) items to modify
              $FilterScript = [scriptblock]::Create(
                (@($Compare).foreach{ "`$Platform.`$Key.Cid.$_ -eq `$_.$_" }) -join ' -and '
              )
              @($Platform.$Key.Import | Where-Object -FilterScript $FilterScript).foreach{
                Write-Log 'Import-FalconConfig' "Selecting '$(
                  if ($_.value) {
                    if ($_.type) { $_.type,$_.value -join ':' } else { $_.value }
                  } elseif ($_.precedence -and $Item -eq 'FileVantageRule') {
                    $_.precedence
                  } else {
                    $_.name
                  }
                )' for modification"

                $Config.$Item.Modify.Add($_)
              }
            }
          }
        } else {
          # Define properties for comparison between imported and existing items
          [string[]]$Available = ($Config.$Item.Cid | Get-Member -MemberType NoteProperty |
            Select-Object -Unique).Name
          [string[]]$Compare = @('name','type','value').Where({$Available -contains $_})
          Write-Log 'Import-FalconConfig' "Evaluating $Item using '$($Compare -join ',')'"
          $FilterScript = [scriptblock]::Create(
            (@($Compare).foreach{ "`$Config.$Item.Cid.$_ -notcontains `$_.$_" }) -join ' -and '
          )
          @($Config.$Item.Import | Where-Object -FilterScript $FilterScript).foreach{
            # Capture items for import
            Write-Log 'Import-FalconConfig' "Selecting '$(
              if ($_.value) {
                if ($_.type) { $_.type,$_.value -join ':' } else { $_.value }
              } elseif ($_.precedence -and $Item -eq 'FileVantageRule') {
                $_.precedence
              } else {
                $_.name
              }
            )' for import"

            $_
          }
          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{
              Write-Log 'Import-FalconConfig' "Selecting '$(
                if ($_.value) {
                  if ($_.type) { $_.type,$_.value -join ':' } else { $_.value }
                } elseif ($_.precedence -and $Item -eq 'FileVantageRule') {
                  $_.precedence
                } else {
                  $_.name
                }
              )' for modification"

              $Config.$Item.Modify.Add($_)
            }
          }
        }
      } elseif ($Config.$Item.Import) {
        # Capture all items for import when none are present in target environment
        @($Config.$Item.Import)
      }
    }
    function Compare-Setting ([object]$New,[object]$Old,[string]$Type,[string]$Property,[switch]$Result) {
      if ($Type -eq 'ContentPolicy') {
        [string[]]$Select = foreach ($Ras in $New.settings.ring_assignment_settings) {
          foreach ($i in $Ras.id) {
            # Check each 'ring_assignment_settings' for modified values using 'id' and 'ring_assignment'
            $NewRas = @($Ras).Where({$_.id -eq $i}).ring_assignment
            $OldRas = @($Old.settings.ring_assignment_settings).Where({$_.id -eq $i}).ring_assignment
            if ($NewRas -ne $OldRas) {
              # Capture result or output modified property name
              if ($Result) { Add-Result Modified $New $Type $i $OldRas $NewRas } else { $i }
            }
          }
        }
        # Output settings for modification
        if ($Select) { $New.settings }
      } elseif ($Type -eq 'DeviceControlPolicy') {
        [string[]]$Select = if ($Old) {
          foreach ($i in @($New.settings.PSObject.Properties)) {
            if ($i.Name -match '^(enforcement_mode|end_user_notification|enhanced_file_metadata)$') {
              if ($i.Value -ne $Old.settings.($i.Name)) {
                if ($Result) {
                  # Capture result
                  Add-Result Modified $New $Type $i.Name $Old.settings.($i.Name) $i.Value
                } else {
                  # Output modified property name
                  $i.Name
                }
              }
            }
          }
        }
        # Output settings to be modified by property name
        if ($Select) { $New.settings | Select-Object $Select }
      } elseif ($Type -eq 'SensorUpdatePolicy') {
        [string[]]$Select = if ($Old) {
          foreach ($i in @($New.settings.PSObject.Properties)) {
            # Check SensorUpdatePolicy settings sub-properties for changes
            if ($i.Name -match '^(scheduler|variants)$') {
              # Check 'scheduler' and 'variants' sub-properties
              if ($Result -and $null -eq $New.settings.($i.Name)) {
                @($Old.settings.($i.Name)).foreach{
                  @($_.PSObject.Properties).foreach{
                    Add-Result Modified $New $Type ($i.Name,$_.Name -join '.') $_.Value $null
                  }
                }
              } else {
                [boolean[]]$SubProp = @($i.Value).foreach{
                  @($_.PSObject.Properties).foreach{
                    if ($_.Value -ne $Old.settings.($i.Name).($_.Name)) {
                      if ($Result) {
                        # Capture sub-property result
                        Add-Result Modified $New $Type ($i.Name,$_.Name -join '.') $Old.settings.($i.Name).(
                          $_.Name) $_.Value
                      } else {
                        # Output true for modified sub-property
                        $true
                      }
                    }
                  }
                }
                # Output property name when modified sub-properties are present
                if ($SubProp -eq $true) { $i.Name }
              }
            } else {
              if ($i.Value -ne $Old.settings.($i.Name)) {
                if ($Result) {
                  # Capture result
                  Add-Result Modified $New $Type $i.Name $Old.settings.($i.Name) $i.Value
                } else {
                  # Output modified property name
                  $i.Name
                }
              }
            }
          }
        }
        # Output settings to be modified by property name
        if ($Select) { $New.settings | Select-Object $Select }
      } elseif ($Type -match 'Policy$') {
        # Compare modified policy settings with 'id' and 'value' sub-properties
        $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({$_.name -eq $Name}).values | ConvertTo-Json -Compress
            $NewValue = @($New.$Property).Where({$_.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 Get-CurrentBuild ([string]$String,[object[]]$List,[string]$Platform) {
      if ($String -match '\|') {
        # Match by sensor build tag, replacing suffix with wildcard for cloud disparities
        if ($String -match '^n\|tagged\|\d{1,}$') { $String = $String -replace '\d{1,}$','*' }
        @($List).Where({$_.platform -eq $Platform -and $_.build -like "*|$String"}) |
          Select-Object build,sensor_version
      } elseif ($String) {
        # Check for exact sensor build version match
        @($List).Where({$_.platform -eq $Platform -and $_.build -eq $String}) |
          Select-Object build,sensor_version,stage
      } else {
        $null
      }
    }
    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' }
        [object[]]$Req = foreach ($Id in $Object.$Property) {
          if ($Cid.$Property -notcontains $Id) {
            @(Invoke-PolicyAction $Type $Invoke $Object.id $Id).foreach{
              # Output entire result if object is returned, otherwise return '$Property.$Id'
              if ($_.$Property) { $_ } else { $Id }
            }
          }
        }
        if ($Req) {
          if ($Req[-1].$Property.id) {
            # Capture result if entire object is returned
            Add-Result Modified $Req[-1] $Type $Property ($Cid.$Property -join ',') (
              $Req[-1].$Property.id -join ',')
          } else {
            # Combine '$Property.$Id' values
            Add-Result Modified $Object $Type $Property ($Cid.$Property -join ',') ($Req -join ',')
          }
        }
      }
    }
    function Update-Id ([object]$Item,[string]$Type) {
      if ($Config.Ids.$Type) {
        # Add 'new_id' to 'Ids'
        [string[]]$Compare = @('cid','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 ',')]."

      }
    }
    # Attempt to retrieve CID using 'Get-FalconCcid' for evaluation
    [string]$Local:HomeCid = Confirm-CidValue (Get-FalconCcid -EA 0)
    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 cid,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)'..."
          $Param = @{ Detailed = $true; All = $true }
          # Include 'settings' with 'FirewallPolicy'
          if ($Pair.Key -eq 'FirewallPolicy') { $Param['Include'] = 'settings' }
          @(& "Get-Falcon$($Pair.Key)" @Param).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({$_.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 build versions
        [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 sensor builds with current available build values
          if ($Item.settings.build) {
            [string]$pBuild = if ($Item.settings.build -match '|') {
              ($Item.settings.build -split '\|',2)[-1]
            } else {
              $Item.settings.build
            }
            $pNew = Get-CurrentBuild $pBuild $BuildList $Item.platform_name
            if ($pNew -and $pNew.build -ne $Item.settings.build) {
              # Replace build with current tagged version
              Write-Log 'Import-FalconConfig' (
                'Replaced build "{0}" with "{1}" for {2} policy "{3}"' -f $Item.settings.build,$pNew.build,
                $Item.platform_name,$Item.name)
              @('build','sensor_version','stage').foreach{ Set-Property $Item.settings $_ $pNew.$_ }
            } elseif (!$pNew) {
              # Strip build if build match is not available
              Set-Property $Item.settings build $null
            }
          }
          if (!$Item.settings.build) {
            # Strip build, sensor_version and stage if 'build' is not present in policy
            Write-Log 'Import-FalconConfig' (
              'Removed build values from {0} policy "{1}"' -f $Item.platform_name,$Item.name)
            @('build','sensor_version','stage').foreach{ Set-Property $Item.settings $_ $null }
          }
          if ($Item.settings.variants) {
            foreach ($Variant in $Item.settings.variants) {
              # Update sensor variants with current available variant build values
              [string]$vBuild = if ($Variant.build -match '|') {
                ($Variant.build -split '\|',2)[-1]
              } else {
                $Variant.build
              }
              $vNew = Get-CurrentBuild $vBuild $BuildList $Variant.platform
              if ($vNew -and $vNew.build -ne $Variant.build) {
                # Replace build with current tagged version
                Write-Log 'Import-FalconConfig' (
                  'Replaced build "{0}" with "{1}" for {2} variant for policy "{3}"' -f $Variant.build,
                  $vNew.build,$Variant.platform,$Item.name)
                @('build','sensor_version','stage').foreach{ Set-Property $Variant $_ $vNew.$_ }
              } elseif (!$vNew) {
                # Strip build if match is not available
                Set-Property $Variant build $null
              }
              if (!$Variant.build) {
                # Strip build and sensor_version if 'build' is not present
                Write-Log 'Import-FalconConfig' (
                  'Removed build values from {0} variant for policy "{1}"' -f $Variant.platform,$Item.name)
                @('build','sensor_version','stage').foreach{ Set-Property $Variant $_ $null }
              }
            }
          }
        }
      }
    }
    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 }
              }
              if ($Rules) {
                @($Rules).foreach{
                  # Trim rule names to 64 characters and use 'rules' as 'rule_ids'
                  if ($_.name.length -gt 64) { $_.name = ($_.name).SubString(0,63) }
                }
                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
          if (!$Item.comment) { Set-Property $Item comment ($UserAgent,"Import-FalconConfig" -join ': ') }
          [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
                if (!$Rule.comment) { Set-Property $Rule comment ($UserAgent,"Import-FalconConfig" -join ': ') }
                $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) {
          @($Item | & "Send-Falcon$($Pair.Key)").foreach{
            # Create Script
            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({$_.cid -eq $Policy.cid -and $_.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') {
          # Check 'FirewallPolicy' for changes
          if ($Policy.settings.policy_id) { $Policy.settings.policy_id = $Policy.id }
          foreach ($Id in $Policy.settings.rule_group_ids) {
            [object]$Group = @($Config.Ids.FirewallGroup).Where({$_.old_id -eq $Id})
            [string[]]$Policy.settings.rule_group_ids = if ($Group) {
              # Update 'rule_group_ids' with new id values
              Write-Log 'Import-FalconConfig' ('Updated FirewallGroup "{0}" to "{1}" under {2} "{3}"' -f
                $Id,$Group.id,$Pair.Key,$Policy.id)
              $Policy.settings.rule_group_ids -replace $Id,$Group.new_id
            } else {
              # Remove unmatched 'rule_group_ids' values
              Write-Log 'Import-FalconConfig' ('Removed unmatched FirewallGroup "{0}" from {1} "{2}"' -f
                $Id,$Pair.Key,$Policy.id)
              $Policy.settings.rule_group_ids -replace $Id,$null
            }
          }
          if (!$Policy.settings.rule_group_ids) {
            # Remove empty 'rule_group_ids' value before submission of 'settings'
            [void]$Policy.settings.PSObject.Properties.Remove('rule_group_ids')
          }
          if ($Policy.settings) {
            @($Policy.settings | Edit-FalconFirewallSetting).foreach{
              # Apply FirewallSetting
              Set-Property $Policy settings $Policy.settings
              @('platform_id','default_inbound','default_outbound','enforce','test_mode','local_logging').foreach{
                if ($Cid.settings.$_ -ne $Policy.settings.$_) {
                  # Add individual modified settings to output
                  Add-Result Modified $Policy $Pair.Key $_ $Cid.settings.$_ $Policy.settings.$_
                }
              }
              if (Compare-Object $Cid.settings.rule_group_ids $Policy.settings.rule_group_ids) {
                # Add 'rule_group_ids' to output when modified
                Add-Result Modified $Policy $Pair.Key rule_group_ids $Cid.settings.rule_group_ids (
                  $Policy.settings.rule_group_ids)
              }
            }
          }
        } elseif ($Policy.id -and ($Policy.prevention_settings -or $Policy.settings)) {
          if (!$HomeCid -or ($HomeCid -and $Policy.cid -and $Policy.cid -eq $HomeCid)) {
            # Compare Policy settings for non-inherited policies if 'HomeCid' is available
            $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 ((!$HomeCid -or ($HomeCid -and $Policy.cid -and $Policy.cid -eq $HomeCid)) -and $Cid.enabled -ne
            $Policy.enabled) {
              # Enable/disable non-inherited policies if 'HomeCid' is available
              [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({$_.action -ne 'Ignored'})) {
      # Output warning for existing policy precedence
      foreach ($Item in (@($Config.Result).Where({$_.action -eq 'Created' -and $_.type -match 'Policy$'}) |
      Select-Object type,platform -Unique)) {
        if (@($Config.($Item.type).Cid).Where({$_.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 }
    }
  }
}