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 blank to export all available items
.PARAMETER Force
Overwrite an existing file when present
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Configuration-Import-Export
#>

    [CmdletBinding(DefaultParameterSetName='ExportItem')]
    param(
        [Parameter(ParameterSetName='ExportItem',Position=1)]
        [ValidateSet('HostGroup','IoaGroup','FirewallGroup','DeviceControlPolicy','FirewallPolicy',
            'PreventionPolicy','ResponsePolicy','SensorUpdatePolicy','Ioc','IoaExclusion','MlExclusion',
            'Script','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 -Path $Location -ChildPath "$String.json"
            $Param = @{ Detailed = $true; All = $true}
            $Config = if ($String -match '^(DeviceControl|Firewall|Prevention|Response|SensorUpdate)Policy$') {
                # Create policy exports in 'platform_name' order to retain precedence
                @('Windows','Mac','Linux').foreach{
                    & "Get-Falcon$String" @Param -Filter "platform_name:'$_'+name:!'platform_default'" 2>$null
                }
            } else {
                & "Get-Falcon$String" @Param 2>$null
            }
            if ($Config -and $String -eq 'FirewallPolicy') {
                # Export firewall settings
                Write-Host "[Export-FalconConfig] Exporting 'FirewallSetting'..."
                $Settings = Get-FalconFirewallSetting -Id $Config.id 2>$null
                foreach ($Result in $Settings) {
                    ($Config | Where-Object { $_.id -eq $Result.policy_id }).PSObject.Properties.Add(
                        (New-Object PSNoteProperty('settings',$Result)))
                }
            }
            if ($Config) {
                # Export results to json file and output created file name
                ConvertTo-Json @($Config) -Depth 32 | Out-File $ConfigFile -Append
                $ConfigFile
            }
        }
        # Get current location
        $Location = (Get-Location).Path
        
        # Set output archive 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 (!$PSBoundParameters.Select) {
                # Use items in 'ValidateSet' when not provided
                $PSBoundParameters.Select = @((Get-Command $MyInvocation.MyCommand.Name).ParameterSets.Where({
                    $_.Name -eq 'ExportItem' }).Parameters.Where({ $_.Name -eq
                    'Select' }).Attributes.ValidValues).foreach{ $_ }
            }
            if ($PSBoundParameters.Select -match '^((Ioa|Ml|Sv)Exclusion|Ioc)$' -and
            $PSBoundParameters.Select -notcontains 'HostGroup') {
                # Force 'HostGroup' when exporting Exclusions or IOCs
                $PSBoundParameters.Select += ,'HostGroup'
            }
            if ($PSBoundParameters.Select -contains 'FirewallGroup') {
                # Force 'FirewallRule' when exporting 'FirewallGroup'
                $PSBoundParameters.Select += ,'FirewallRule'
            }
            # Retrieve results, export to Json and capture file name
            $JsonFiles = foreach ($String in $PSBoundParameters.Select) { ,(Get-ItemContent $String) }
            if ($JsonFiles) {
                # 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-Verbose "Deleting '$_'."
                        Remove-Item $_ -Force
                    }
                }
            }
            # Display created archive
            if (Test-Path $ExportFile) { Get-ChildItem $ExportFile | Select-Object FullName,Length,LastWriteTime }
        }
    }
}
function Import-FalconConfig {
<#
.SYNOPSIS
Import configurations from a 'FalconConfig' archive into your Falcon environment
.DESCRIPTION
Creates groups, policies, exclusions and rules within a 'FalconConfig' archive within your authenticated
Falcon environment.
 
Anything that already exists will be ignored and no existing items will be modified.
 
The '-Force' parameter forces the script to assign exceptions, policies and rules to existing host groups
with the same names as the ones provided in the configuration file.
.PARAMETER Path
'FalconConfig' archive path
.PARAMETER Force
Assign imported items to existing host groups
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Configuration-Import-Export
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=1)]
        [ValidatePattern('\.zip$')]
        [ValidateScript({
            if (Test-Path $_) { $true } else { throw "Cannot find path '$_' because it does not exist." }
        })]
        [string]$Path,
        [switch]$Force
    )
    begin {
        function Add-ListId ([object]$Item,[string]$Type,[string]$Reason) {
            $Obj = if ($Reason) {
                [PSCustomObject]@{ id = $Item.id; reason = $Reason }
            } else {
                [PSCustomObject]@{ old_id = $Item.id; new_id = $null }
            }
            @('platform_name','platforms','platform','type','value','name').foreach{
                Set-Property $Obj $_ $Item.$_
            }
            if ($Obj.reason) {
                # Add excluded item to list for final output
                $Config.Excluded.$Type.Add($Obj)
            } else {
                # Capture item 'id' for assignment
                $Config.Ids.$Type.Add($Obj)
            }
        }
        function Compare-ImportData ([string]$Item) {
            if ($Config.$Item.Cid) {
                # Define properties for comparison between imported and existing items
                [string[]]$Properties = ($Config.$Item.Cid | Get-Member -MemberType NoteProperty).Name
                [string[]]$Compare = @('name','type','value').foreach{ if ($Properties -contains $_) { $_ }}
                $FilterScript = [scriptblock]::Create((@($Compare).foreach{
                    "`$Config.$($Item).Cid.$($_) -notcontains `$_.$($_)" }) -join ' -and ')
                Get-ConfigItem $Item Import $FilterScript
            } elseif ($Config.$Item.Import) {
                # Output all items
                @($Config.$Item.Import).foreach{
                    Write-Verbose "[Compare-ImportData] $($Item).Import: $($_.id)"
                    $_
                }
            }
        }
        function Get-CidValue ([string]$Item) {
            try {
                # Retrieve existing configurations from CID, excluding 'platform_default'
                Write-Host "[Import-FalconConfig] Retrieving '$Item'..."
                $Param = @{ Detailed = $true; All = $true }
                if ($Item -match 'Policy$') { $Param['Filter'] = "name:!'platform_default'" }
                & "Get-Falcon$($Item)" @Param
            } catch {
                throw $_
            }
        }
        function Get-ConfigItem ([string]$Item,[string]$Type,[scriptblock]$FilterScript) {
            @($Config.$Item.$Type | Where-Object -FilterScript $FilterScript).foreach{
                Write-Verbose "[Get-ConfigItem] $($Item).$($Type): $($_.id)"
                $_
            }
        }
        function Import-ConfigData ($FilePath) {
            # Load 'FalconConfig' archive into memory
            $Output = @{ Excluded = @{}; Ids = @{}}
            $ByteStream = if ($PSVersionTable.PSVersion.Major -ge 6) {
                Get-Content -Path $FilePath -AsByteStream
            } else {
                Get-Content -Path $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, filter to required properties, add to output, then output name for notification
                $Filename = $ConfigArchive.GetEntry($FullName)
                $Item = ($FullName | Split-Path -Leaf).Split('.')[0]
                $Import = ConvertFrom-Json -InputObject ((New-Object System.IO.StreamReader(
                    $Filename.Open())).ReadToEnd())
                $Import = $Import | Select-Object $ConfigFields.$Item.Import
                $Output[$Item] = @{ Import = $Import }
                @('Excluded','Ids').foreach{ $Output.$_[$Item] = [System.Collections.Generic.List[object]]@() }
                $Item
            }
            if ($FileStream) { $FileStream.Dispose() }
            if ($Msg) { Write-Host "[Import-FalconConfig] Imported from $($FilePath): $($Msg -join ', ')." }
            $Output
        }
        function New-ResultObject ([object]$Item,[string]$Type) {
            if ($Item -and $Type) {
                # Create object for CSV output
                [PSCustomObject]@{
                    status = if ($Item.reason) { 'Excluded' } else { 'Created' }
                    type = $Type
                    id = if ($Item.reason -eq 'Excluded') {
                        # Exclude 'id' when item has been excluded
                        $null
                    } elseif ($Item.instance_id) {
                        $Item.instance_id
                    } else {
                        $Item.id
                    }
                    platform = if ($Item.platform_name) {
                        $Item.platform_name
                    } elseif ($Item.platforms) {
                        $Item.platforms -join ','
                    } elseif ($Item.platform) {
                        if ($Item.platform -is [array]) { $Item.platform -join ',' } else { $Item.platform }
                    } else {
                        $null
                    }
                    name = if ($Item.type -and $Item.value) {
                        "$($Item.type):$($Item.value)"
                    } elseif ($Item.value) {
                        $Item.value
                    } else {
                        $Item.name
                    }
                    reason = if ($Item.reason) { $Item.reason } else { $null }
                }
            }
        }
        function Update-ListId ([object]$Item,[string]$Type) {
            if ($Config.Ids.$Type) {
                # Update 'Ids' with 'new_id'
                [string[]]$Compare = @('platform_name','platform','type','value','name').foreach{
                    if ($Item.$_) { $_ }
                }
                [string]$Filter = (@($Compare).foreach{"`$_.$($_) -eq '$($Item.$_)'" }) -join ' -and '
                $FilterScript = [scriptblock]::Create($Filter)
                $Config.Ids.$Type | Where-Object -FilterScript $FilterScript | ForEach-Object {
                    $_.new_id = $Item.id
                }
            }
        }
        # Convert 'Path' to absolute and set 'OutputFile'
        $ArchivePath = $Script:Falcon.Api.Path($PSBoundParameters.Path)
        $ForceEnabled = if ($PSBoundParameters.Force) { $true } else { $false }
        $OutputFile = Join-Path (Get-Location).Path "FalconConfig_$(Get-Date -Format FileDateTime).csv"
    }
    process {
        # Create 'ConfigData', import configuration files and create object to contain 'id' values for comparison
        $Config = Import-ConfigData -FilePath $ArchivePath
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Import })) {
            foreach ($i in $Pair.Value.Import) {
                # Capture relevant assignment detail for imported items
                Add-ListId $i $Pair.Key
                if ($Pair.Key -match '^(Ml|Sv)Exclusion$' -and $i.applied_globally -eq $true) {
                    # Set 'groups' to 'all' for global exclusion
                    [string[]]$i.groups = 'all'
                }
                # Filter 'groups' to 'id' values
                if ($i.groups.id) { [string[]]$i.groups = $i.groups.id }
                # Filter 'rule_group' to 'id' values
                if ($i.rule_group.id) { [string[]]$i.rule_group = $i.rule_group.id }
                # Filter 'ioa_rule_groups' to 'id' values
                if ($i.ioa_rule_groups.id) { [string[]]$i.ioa_rule_groups = $i.ioa_rule_groups.id }
            }
        }
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch '^(Ids|Excluded)$' })) {
            # Retrieve existing items from CID
            $Pair.Value['Cid'] = [object[]](Get-CidValue $Pair.Key)
            if ($Pair.Value.Cid -and $ForceEnabled -eq $true) {
                # Add existing item 'ids' for assignment when '-Force' is enabled
                foreach ($i in $Pair.Value.Cid) { Update-ListId $i $Pair.Key }
            }
            if ($Pair.Key -match 'Policy$') {
                $Pair.Value.Import = foreach ($i in $Pair.Value.Import) {
                    # Keep only non-existing policy items for each OS under 'Import'
                    if (!($Config.($Pair.Key).Cid.Where({ $_.platform_name -eq $i.platform_name -and
                        $_.name -eq $i.name }))) { $i } else { Add-ListId $i $Pair.Key Exists }
                }
            } else {
                if ($Pair.Key -ne 'FirewallRule') {
                    # Track excluded items for final output, excluding 'FirewallRule' which can be duplicated
                    foreach ($i in $Pair.Value.Import) {
                        [string]$Reason = if ($i.deleted -and $i.deleted -eq $true) {
                            'Deleted'
                        } elseif ($i.type -and $i.value -and $Pair.Value.Cid.Where({ $_.type -eq $i.type -and
                        $_.value -eq $i.value })) {
                            'Exists'
                        } elseif ($i.value -and $Pair.Value.Cid.Where({ $_.value -eq $i.value })) {
                            'Exists'
                        } elseif ($Pair.Value.Cid.Where({ $_.name -eq $i.name })) {
                            'Exists'
                        }
                        if ($Reason) { Add-ListId $i $Pair.Key $Reason }
                    }
                    # Remove excluded items from 'Import'
                    $Pair.Value.Import = Compare-ImportData $Pair.Key
                }
            }
            if ($Pair.Key -eq 'SensorUpdatePolicy' -and $Pair.Value.Import) {
                # Retrieve available sensor build versions to update 'tags'
                $Builds = 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 ($i in $Pair.Value.Import) {
                    # Update tagged builds with current tagged build versions
                    if ($i.settings.build -match '^\d+\|') {
                        $Tag = ($i.settings.build -split '\|',2)[-1]
                        $Current = ($Builds | Where-Object { $_.build -like "*|$Tag" -and $_.platform -eq
                            $i.platform_name }).build
                        if ($i.settings.build -ne $Current) { $i.settings.build = $Current }
                    }
                    if ($i.settings.variants) {
                        # Update tagged 'variant' builds with current tagged build versions
                        @($i.settings.variants | Where-Object { $_.build }).foreach{
                            $Tag = ($_.build -split '\|',2)[-1]
                            $Current = ($Builds | Where-Object { $_.build -like "*|$Tag" -and $_.platform -eq
                                $i.platform_name }).build
                            if ($_.build -ne $Current) { $_.build = $Current }
                        }
                    }
                }
            }
        }
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -eq 'HostGroup' -and $_.Value.Import })) {
            # Create host groups
            ($Pair.Value)['Created'] = $Pair.Value.Import | & "New-Falcon$($Pair.Key)"
            foreach ($Group in $Pair.Value.Created) {
                # Notify and capture new 'id' values
                Write-Host "[Import-FalconConfig] Created $($Pair.Key) '$($Group.name)'."
                Update-ListId $Group $Pair.Key
            }
        }
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -notmatch '^(HostGroup|FirewallRule)$' -and
        $_.Value.Import })) {
            @($Pair.Value.Import).foreach{
                if ($_.groups -or $_.host_groups) {
                    # Update host group 'id' values
                    foreach ($OldId in $Config.Ids.HostGroup.old_id) {
                        $NewId = ($Config.Ids.HostGroup | Where-Object { $_.old_id -eq $OldId }).new_id
                        if ($NewId -and $_.groups) {
                            [string[]]$_.groups = $_.groups -replace $OldId,$NewId
                        } elseif ($NewId -and $_.host_groups) {
                            [string[]]$_.host_groups = $_.host_groups -replace $OldId,$NewId
                        }
                    }
                }
            }
            $Pair.Value['Created'] = if ($Pair.Key -eq 'FirewallGroup') {
                foreach ($i in $Pair.Value.Import) {
                    # Create FirewallGroup using properties from 'Import'
                    $FwGroup = $i | Select-Object name,enabled,description,comment,rule_ids
                    if ($FwGroup.rule_ids) {
                        # Select rule from Import using 'family' as 'id' value
                        [object[]]$Rules = foreach ($i in $FwGroup.rule_ids) {
                            $Config.FirewallRule.Import | Where-Object { $_.family -eq $i -and
                                $_.deleted -eq $false }
                        }
                        # Trim rule names to 64 characters
                        @($Rules).foreach{ if ($_.name.length -gt 64) { $_.name = ($_.name).SubString(0,63) }}
                        if ($Rules) {
                            # Add 'rules' and remove 'rule_ids' from object
                            Set-Property $FwGroup rules $Rules
                            $FwGroup.PSObject.Properties.Remove('rule_ids')
                        }
                    }
                    @($FwGroup | & "New-Falcon$($Pair.Key)").foreach{
                        # Append new 'id' to object, capture new 'id' and return result
                        Set-Property $FwGroup id $_
                        $FwGroup
                    }
                }
            } elseif ($Pair.Key -eq 'IoaGroup') {
                foreach ($i in $Pair.Value.Import) {
                    # Create IoaGroup
                    $IoaGroup = $i | & "New-Falcon$($Pair.Key)"
                    if ($IoaGroup) {
                        if ($i.rules) {
                            # Create IoaRules
                            $IoaGroup.rules = @($i.rules).foreach{
                                $_.rulegroup_id = $IoaGroup.id
                                $IoaRule = $_ | New-FalconIoaRule
                                if ($IoaRule.enabled -eq $false -and $_.enabled -eq $true) {
                                    # Enable created rule using status from 'Import'
                                    $IoaRule.enabled = $true
                                }
                                $IoaRule
                            }
                            if ($IoaGroup.rules.enabled -eq $true) {
                                @($IoaGroup | Edit-FalconIoaRule).foreach{
                                    @($_.rules).Where({ $_.enabled -eq $true }).foreach{
                                        # Notify of enabled rules
                                        Write-Host "[Import-FalconConfig] Enabled IoaRule '$($_.name)'."
                                    }
                                }
                            }
                        }
                        if ($i.enabled -eq $true -and $IoaGroup.enabled -eq $false) {
                            $IoaGroup.enabled = $true
                            $IoaGroup = @($IoaGroup | & "Edit-Falcon$($Pair.Key)").foreach{
                                # Enable IoaGroup and return result
                                Write-Host "[Import-FalconConfig] Enabled $($Pair.Key) '$($_.name)'."
                                $_
                            }
                        }
                        $IoaGroup
                    }
                }
            } elseif ($Pair.Key -eq 'Script') {
                foreach ($i in $Pair.Value.Import) {
                    # Create scripts
                    $Script = $i | & "Send-Falcon$($Pair.Key)"
                    if ($Script) { $i | Select-Object name,platform }
                }
            } else {
                # Create exclusions, IOCs and policies
                $Pair.Value.Import | & "New-Falcon$($Pair.Key)"
            }
            foreach ($i in $Pair.Value.Created) {
                # Output notification of created item(s)
                $Name = if ($i.type -and $i.value) {
                    @($i.type,$i.value) -join ':'
                } elseif ($i.value) {
                    $i.value
                } else {
                    $i.name
                }
                if ($i.platform_name -or $i.platform) {
                    @('platform','platform_name').foreach{
                        if ($i.$_) {
                            Write-Host "[Import-FalconConfig] Created $($i.$_) $($Pair.Key) '$Name'."
                        }
                    }
                } else {
                    Write-Host "[Import-FalconConfig] Created $($Pair.Key) '$Name'."
                }
                # Capture new 'id' for assignment
                Update-ListId $i $Pair.Key
            }
        }
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Created })) {
            foreach ($Policy in $Pair.Value.Created) {
                # Copy 'old' policy for replicating settings and assignment
                $Clone = $Config.($Pair.Key).Import | Where-Object { $_.name -eq $Policy.name -and
                    $_.platform_name -eq $Policy.platform_name }
                $Clone.id = $Policy.id
                if ($Pair.Key -eq 'FirewallPolicy') {
                    # Add new policy 'id' to FirewallPolicy settings
                    if ($Clone.settings.policy_id) { $Clone.settings.policy_id = $Policy.id }
                    foreach ($OldId in $Config.Ids.FirewallGroup.old_id) {
                        # Update 'rule_group_ids' with new 'id' values
                        $NewId = ($Config.Ids.FirewallGroup | Where-Object { $_.old_id -eq $OldId }).new_id
                        if ($NewId -and $Clone.rule_group_ids) {
                            [string[]]$_.rule_group_ids = $_.rule_group_ids -replace $OldId,$NewId
                        }
                    }
                    if ($Clone.settings) {
                        # Apply FirewallPolicy settings
                        $Settings = $Clone.settings | Edit-FalconFirewallSetting
                        if ($Settings) {
                            Set-Property $Policy settings $Clone.settings
                            Write-Host "[Import-FalconConfig] Applied settings to $($Policy.platform_name) $(
                                $Pair.Key) '$($Policy.name)'."

                        }
                    }
                } elseif ($Clone.settings -or $Clone.prevention_settings) {
                    # Apply settings
                    $Policy = $Clone | & "Edit-Falcon$($Pair.Key)"
                    if ($Policy) {
                        Write-Host "[Import-FalconConfig] Applied settings to $($Policy.platform_name) $(
                            $Pair.Key) '$($Policy.name)'."

                    }
                }
                if ($Pair.Key -eq 'PreventionPolicy' -and $Clone.ioa_rule_groups) {
                    # Assign IoaGroup to PreventionPolicy
                    foreach ($OldId in $Config.Ids.IoaGroup.old_id) {
                        $NewId = ($Config.Ids.IoaGroup | Where-Object { $_.old_id -eq $OldId }).new_id
                        if ($NewId) {
                            $Name = ($Config.Ids.IoaGroup | Where-Object { $_.old_id -eq $OldId }).name
                            $Policy = $Clone.id | & "Invoke-Falcon$($Pair.Key)Action" 'add-rule-group' $NewId
                            if ($Policy) {
                                Write-Host "[Import-FalconConfig] Assigned IoaGroup '$Name' to $(
                                    $Policy.platform_name) $($Pair.Key) '$($Policy.name)'."

                            }
                        }
                    }
                }
                foreach ($NewId in $Clone.groups) {
                    # Assign HostGroup to policy
                    $Name = ($Config.Ids.HostGroup | Where-Object { $_.new_id -eq $NewId }).name
                    if ($Name) {
                        $Policy = $Clone.id | & "Invoke-Falcon$($Pair.Key)Action" 'add-host-group' $NewId
                        if ($Policy) {
                            Write-Host "[Import-FalconConfig] Assigned HostGroup '$Name' to $(
                                $Policy.platform_name) $($Pair.Key) '$($Policy.name)'."

                        }
                    }
                }
                if ($Clone.enabled -eq $true) {
                    # Enable policy
                    $Policy = $Clone.id | & "Invoke-Falcon$($Pair.Key)Action" enable
                    if ($Policy) {
                        Write-Host "[Import-FalconConfig] Enabled $($Policy.platform_name) $($Pair.Key) '$(
                            $Policy.name)'."

                    }
                }
            }
        }
    }
    end {
        foreach ($Pair in $Config.GetEnumerator().Where({ $_.Value.Created })) {
            foreach ($i in $Pair.Value.Created) {
                # Output created items to CSV
                $Obj = New-ResultObject $i $Pair.Key
                try { $Obj | Export-Csv $OutputFile -NoTypeInformation -Append } catch { $Obj }
            }
        }
        foreach ($Pair in $Config.Excluded.GetEnumerator().Where({ $_.Value })) {
            foreach ($i in $Pair.Value) {
                # Output excluded items to CSV
                $Obj = New-ResultObject $i $Pair.Key
                try { $Obj | Export-Csv $OutputFile -NoTypeInformation -Append } catch { $Obj }
            }
        }
        if ($Config.Values.Created) {
            foreach ($Pair in $Config.GetEnumerator().Where({ $_.Key -match 'Policy$' -and $_.Value.Cid -and
            $_.Value.Created })) {
                foreach ($i in $Pair.Value.Created.platform_name) {
                    if ($Pair.Value.Cid | Where-Object { $_.platform_name -eq $i }) {
                        # Output precedence warning (per platform) if existing policies were found in CID
                        Write-Warning "Existing $i $($Pair.Key) items were found. Verify policy precedence!"
                    }
                }
            }
        }
        if (Test-Path $OutputFile) { Get-ChildItem $OutputFile | Select-Object FullName,Length,LastWriteTime }
    }
}