Public/Import-IntuneDeviceFilter.ps1

function Import-IntuneDeviceFilter {
    <#
    .SYNOPSIS
        Creates device filters for Intune from templates
    .DESCRIPTION
        Reads JSON templates from Templates/Filters and creates device filters via Graph API.
        Filters can be used to target or exclude devices from policy assignments.
    .PARAMETER TemplatePath
        Path to the filter template directory (defaults to Templates/Filters)
    .PARAMETER RemoveExisting
        If specified, removes existing filters created by this kit instead of creating new ones
    .EXAMPLE
        Import-IntuneDeviceFilter
    .EXAMPLE
        Import-IntuneDeviceFilter -TemplatePath ./MyFilters
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter()]
        [string]$TemplatePath,

        [Parameter()]
        [switch]$RemoveExisting
    )

    if (-not $TemplatePath) {
        $TemplatePath = Join-Path -Path $script:TemplatesPath -ChildPath "Filters"
    }

    if (-not (Test-Path -Path $TemplatePath)) {
        Write-Warning "Filter template directory not found: $TemplatePath"
        return @()
    }

    $templateFiles = Get-HydrationTemplates -Path $TemplatePath -Recurse -ResourceType "filter template"

    if (-not $templateFiles -or $templateFiles.Count -eq 0) {
        Write-Warning "No filter templates found in: $TemplatePath"
        return @()
    }

    $results = @()

    # Prefetch existing filters with pagination (OData filter on displayName not supported for this endpoint)
    # Store full filter objects so we can check descriptions later
    $existingFilters = @{}
    try {
        $listUri = "beta/deviceManagement/assignmentFilters?`$select=id,displayName,description"
        do {
            $existingFiltersResponse = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop
            foreach ($existingFilter in $existingFiltersResponse.value) {
                if (-not $existingFilters.ContainsKey($existingFilter.displayName)) {
                    $existingFilters[$existingFilter.displayName] = @{
                        Id = $existingFilter.id
                        Description = $existingFilter.description
                    }
                }
            }
            $listUri = $existingFiltersResponse.'@odata.nextLink'
        } while ($listUri)
    }
    catch {
        Write-Warning "Could not retrieve existing filters: $_"
        $existingFilters = @{}
    }

    # Build a simple name->id lookup for backwards compatibility in the import section
    $existingFilterNames = @{}
    foreach ($key in $existingFilters.Keys) {
        $existingFilterNames[$key] = $existingFilters[$key].Id
    }

    # Remove existing filters if requested
    # SAFETY: Only delete filters that have "Imported by Intune-Hydration-Kit" in description
    if ($RemoveExisting) {
        foreach ($filterName in $existingFilters.Keys) {
            $filterInfo = $existingFilters[$filterName]

            # Safety check: Only delete if created by this kit (has hydration marker in description)
            if (-not (Test-HydrationKitObject -Description $filterInfo.Description -ObjectName $filterName)) {
                Write-Verbose "Skipping '$filterName' - not created by Intune-Hydration-Kit"
                continue
            }

            if ($PSCmdlet.ShouldProcess($filterName, "Delete device filter")) {
                try {
                    Invoke-MgGraphRequest -Method DELETE -Uri "beta/deviceManagement/assignmentFilters/$($filterInfo.Id)" -ErrorAction Stop
                    Write-HydrationLog -Message " Deleted: $filterName" -Level Info
                    $results += New-HydrationResult -Name $filterName -Type 'DeviceFilter' -Action 'Deleted' -Status 'Success'
                }
                catch {
                    $errMessage = Get-GraphErrorMessage -ErrorRecord $_
                    Write-HydrationLog -Message " Failed: $filterName - $errMessage" -Level Warning
                    $results += New-HydrationResult -Name $filterName -Type 'DeviceFilter' -Action 'Failed' -Status "Delete failed: $errMessage"
                }
            }
            else {
                Write-HydrationLog -Message " WouldDelete: $filterName" -Level Info
                $results += New-HydrationResult -Name $filterName -Type 'DeviceFilter' -Action 'WouldDelete' -Status 'DryRun'
            }
        }

        return $results
    }

    # Process each template file
    foreach ($templateFile in $templateFiles) {
        try {
            $template = Get-Content -Path $templateFile.FullName -Raw -Encoding utf8 | ConvertFrom-Json

            # Each template file contains a "filters" array
            if (-not $template.filters) {
                Write-Warning "Template missing 'filters' array: $($templateFile.FullName)"
                $results += New-HydrationResult -Name $templateFile.Name -Path $templateFile.FullName -Type 'DeviceFilter' -Action 'Failed' -Status "Missing 'filters' array"
                continue
            }

            foreach ($filter in $template.filters) {
                # Validate required properties
                if (-not $filter.displayName) {
                    Write-Warning "Filter missing displayName in: $($templateFile.FullName)"
                    continue
                }
                if (-not $filter.platform) {
                    Write-Warning "Filter '$($filter.displayName)' missing platform in: $($templateFile.FullName)"
                    continue
                }
                if (-not $filter.rule) {
                    Write-Warning "Filter '$($filter.displayName)' missing rule in: $($templateFile.FullName)"
                    continue
                }

                try {
                    # Check if filter already exists using pre-fetched list
                    if ($existingFilterNames.ContainsKey($filter.displayName)) {
                        Write-HydrationLog -Message " Skipped: $($filter.displayName)" -Level Info
                        $results += New-HydrationResult -Name $filter.displayName -Id $existingFilterNames[$filter.displayName] -Platform $filter.platform -Type 'DeviceFilter' -Action 'Skipped' -Status 'Already exists'
                        continue
                    }

                    if ($PSCmdlet.ShouldProcess($filter.displayName, "Create device filter")) {
                        # Build description with hydration kit marker
                        $description = if ($filter.description) {
                            "$($filter.description) - Imported by Intune-Hydration-Kit"
                        } else {
                            "Imported by Intune-Hydration-Kit"
                        }

                        $filterBody = @{
                            displayName = $filter.displayName
                            description = $description
                            platform = $filter.platform
                            rule = $filter.rule
                            roleScopeTags = @("0")
                        }

                        $newFilter = Invoke-MgGraphRequest -Method POST -Uri "beta/deviceManagement/assignmentFilters" -Body $filterBody -ErrorAction Stop

                        Write-HydrationLog -Message " Created: $($filter.displayName)" -Level Info

                        $results += New-HydrationResult -Name $filter.displayName -Id $newFilter.id -Platform $filter.platform -Type 'DeviceFilter' -Action 'Created' -Status 'Success'
                    }
                    else {
                        Write-HydrationLog -Message " WouldCreate: $($filter.displayName)" -Level Info
                        $results += New-HydrationResult -Name $filter.displayName -Platform $filter.platform -Type 'DeviceFilter' -Action 'WouldCreate' -Status 'DryRun'
                    }
                }
                catch {
                    $errMessage = Get-GraphErrorMessage -ErrorRecord $_
                    Write-HydrationLog -Message " Failed: $($filter.displayName) - $errMessage" -Level Warning
                    $results += New-HydrationResult -Name $filter.displayName -Platform $filter.platform -Type 'DeviceFilter' -Action 'Failed' -Status $errMessage
                }
            }
        }
        catch {
            $errMessage = Get-GraphErrorMessage -ErrorRecord $_
            Write-HydrationLog -Message " Failed to parse: $($templateFile.Name) - $errMessage" -Level Warning
            $results += New-HydrationResult -Name $templateFile.Name -Path $templateFile.FullName -Type 'DeviceFilter' -Action 'Failed' -Status "Parse error: $errMessage"
        }
    }

    return $results
}