Public/Import-IntuneBaseline.ps1

function Import-IntuneBaseline {
    <#
    .SYNOPSIS
        Imports OpenIntuneBaseline policies using IntuneManagement module
    .DESCRIPTION
        Downloads OpenIntuneBaseline from GitHub and imports all policies using the IntuneManagement module.
        Uses IntuneManagement's silent batch mode for automated imports.
    .PARAMETER BaselinePath
        Path to the OpenIntuneBaseline directory (will download if not specified)
    .PARAMETER IntuneManagementPath
        Path to IntuneManagement module (will download if not specified)
    .PARAMETER TenantId
        Target tenant ID (uses connected tenant if not specified)
    .PARAMETER ImportMode
        Import mode: SkipIfExists (default - skip policies that already exist)
    .PARAMETER IncludeAssignments
        Include policy assignments during import
    .PARAMETER Platform
        Filter baseline imports by platform. Valid values: Windows, macOS, iOS, Android, All.
        Defaults to 'All' which imports all baseline policies regardless of platform.
        - Windows: Imports from WINDOWS/ and WINDOWS365/ folders
        - macOS: Imports from MACOS/ folder
        - iOS/Android: Imports from BYOD/ folder (app protection policies)
    .EXAMPLE
        Import-IntuneBaseline
    .EXAMPLE
        Import-IntuneBaseline -BaselinePath ./OpenIntuneBaseline -ImportMode SkipIfExists
    .EXAMPLE
        Import-IntuneBaseline -Platform Windows,macOS
    #>

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

        [Parameter()]
        [string]$TenantId,

        [Parameter()]
        [ValidateSet('Windows', 'macOS', 'iOS', 'Android', 'All')]
        [string[]]$Platform = @('All'),

        [Parameter()]
        [ValidateSet('SkipIfExists')]
        [string]$ImportMode = 'SkipIfExists',

        [Parameter()]
        [switch]$RemoveExisting
    )

    # Use connected tenant if not specified
    if (-not $TenantId -and $script:HydrationState.TenantId) {
        $TenantId = $script:HydrationState.TenantId
    }

    if (-not $TenantId) {
        throw "TenantId is required. Either connect using Connect-IntuneHydration or specify -TenantId parameter."
    }

    # Download OpenIntuneBaseline if not provided
    if (-not $BaselinePath -or -not (Test-Path -Path $BaselinePath)) {
        $BaselinePath = Get-OpenIntuneBaseline
    }

    # OpenIntuneBaseline uses OS-based folder structure:
    # - OS/IntuneManagement/ - Exported by IntuneManagement tool (requires Windows GUI to import)
    # - OS/NativeImport/ - Settings Catalog policies that can be imported via Graph API
    # - BYOD/AppProtection/ - App protection policies

    # Map folder names to Graph API endpoints (normalized names only, no duplicates)
    $endpointMap = @{
        'NativeImport'                     = 'deviceManagement/configurationPolicies'
        'AppProtection'                    = 'deviceAppManagement/managedAppPolicies'
        'Administrative Templates'         = 'deviceManagement/groupPolicyConfigurations'
        'Compliance'                       = 'deviceManagement/deviceCompliancePolicies'
        'Compliance Policies'              = 'deviceManagement/deviceCompliancePolicies'
        'Configuration Profiles'           = 'deviceManagement/deviceConfigurations'
        'Device Configuration'             = 'deviceManagement/deviceConfigurations'
        'Device Enrollment Configurations' = 'deviceManagement/deviceEnrollmentConfigurations'
        'Endpoint Security'                = 'deviceManagement/intents'
        'Settings Catalog'                 = 'deviceManagement/configurationPolicies'
        'Scripts'                          = 'deviceManagement/deviceManagementScripts'
        'Proactive Remediations'           = 'deviceManagement/deviceHealthScripts'
        'Windows Autopilot'                = 'deviceManagement/windowsAutopilotDeploymentProfiles'
        'App Configuration'                = 'deviceAppManagement/mobileAppConfigurations'
        'App Protection Policies'          = 'deviceAppManagement/managedAppPolicies'
    }

    # Map @odata.type to Graph API endpoints for IntuneManagement exports
    $odataTypeToEndpoint = @{
        # Device Configurations
        '#microsoft.graph.windowsHealthMonitoringConfiguration'         = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windows10GeneralConfiguration'                = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windows10EndpointProtectionConfiguration'     = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windows10CustomConfiguration'                 = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsDeliveryOptimizationConfiguration'     = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsUpdateForBusinessConfiguration'        = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsIdentityProtectionConfiguration'       = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsKioskConfiguration'                    = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.editionUpgradeConfiguration'                  = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.sharedPCConfiguration'                        = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsWifiConfiguration'                     = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.windowsWiredNetworkConfiguration'             = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.macOSGeneralDeviceConfiguration'              = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.macOSCustomConfiguration'                     = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.macOSEndpointProtectionConfiguration'         = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.iosGeneralDeviceConfiguration'                = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.iosCustomConfiguration'                       = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.androidGeneralDeviceConfiguration'            = 'deviceManagement/deviceConfigurations'
        '#microsoft.graph.androidWorkProfileGeneralDeviceConfiguration' = 'deviceManagement/deviceConfigurations'
        # Compliance Policies
        '#microsoft.graph.windows10CompliancePolicy'                    = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.windows81CompliancePolicy'                    = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.macOSCompliancePolicy'                        = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.iosCompliancePolicy'                          = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.androidCompliancePolicy'                      = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.androidWorkProfileCompliancePolicy'           = 'deviceManagement/deviceCompliancePolicies'
        '#microsoft.graph.androidDeviceOwnerCompliancePolicy'           = 'deviceManagement/deviceCompliancePolicies'
        # Settings Catalog / Configuration Policies
        '#microsoft.graph.deviceManagementConfigurationPolicy'          = 'deviceManagement/configurationPolicies'
        # Windows Update for Business - Driver Updates
        '#microsoft.graph.windowsDriverUpdateProfile'                   = 'deviceManagement/windowsDriverUpdateProfiles'
    }

    # Folders that previously required IntuneManagement tool - now we try to import via Graph API
    $intuneManagementFolders = @('IntuneManagement')

    $results = @()

    # Check Windows Driver Update license upfront (cached for all driver update profiles)
    $hasDriverUpdateLicense = $null  # Lazy-loaded when needed

    # Remove existing baseline policies if requested
    # SAFETY: Only delete policies that have "Imported by Intune Hydration Kit" in description
    if ($RemoveExisting) {
        # Delete from main endpoints used by baselines
        $deleteEndpoints = @(
            'beta/deviceManagement/configurationPolicies',
            'beta/deviceManagement/deviceConfigurations',
            'beta/deviceManagement/deviceCompliancePolicies',
            'beta/deviceAppManagement/androidManagedAppProtections',
            'beta/deviceAppManagement/iosManagedAppProtections'
        )

        foreach ($endpoint in $deleteEndpoints) {
            try {
                $listUri = $endpoint
                do {
                    $existing = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop
                    foreach ($policy in $existing.value) {
                        $policyName = if ($policy.displayName) { $policy.displayName } elseif ($policy.name) { $policy.name } else { "Unknown" }
                        $policyId = $policy.id

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

                        if ($PSCmdlet.ShouldProcess($policyName, "Delete baseline policy")) {
                            try {
                                Invoke-MgGraphRequest -Method DELETE -Uri "$endpoint/$policyId" -ErrorAction Stop
                                Write-HydrationLog -Message " Deleted: $policyName" -Level Info
                                $results += New-HydrationResult -Name $policyName -Type 'BaselinePolicy' -Action 'Deleted' -Status 'Success'
                            } catch {
                                $errMessage = Get-GraphErrorMessage -ErrorRecord $_
                                Write-HydrationLog -Message " Failed: $policyName - $errMessage" -Level Warning
                                $results += New-HydrationResult -Name $policyName -Type 'BaselinePolicy' -Action 'Failed' -Status "Delete failed: $errMessage"
                            }
                        } else {
                            Write-HydrationLog -Message " WouldDelete: $policyName" -Level Info
                            $results += New-HydrationResult -Name $policyName -Type 'BaselinePolicy' -Action 'WouldDelete' -Status 'DryRun'
                        }
                    }
                    $listUri = $existing.'@odata.nextLink'
                } while ($listUri)
            } catch {
                Write-Warning "Failed to process endpoint $endpoint : $_"
            }
        }

        return $results
    }

    # Find all policy type subfolders within OS folders (WINDOWS, MACOS, BYOD, WINDOWS365)
    # OpenIntuneBaseline structure: OS/PolicyType/policy.json

    # Platform to folder mapping for filtering
    $platformFolderMapping = @{
        'Windows' = @('WINDOWS', 'WINDOWS365', 'Windows', 'Windows365')
        'macOS'   = @('MACOS', 'macOS', 'MacOS')
        'iOS'     = @('BYOD', 'byod')
        'Android' = @('BYOD', 'byod')
    }

    $osFolders = Get-ChildItem -Path $BaselinePath -Directory | Where-Object {
        $_.Name -notmatch '^\.'
    }

    # Filter OS folders by platform if specified
    if ($Platform -and $Platform -notcontains 'All') {
        $allowedFolders = @()
        foreach ($plat in $Platform) {
            if ($platformFolderMapping.ContainsKey($plat)) {
                $allowedFolders += $platformFolderMapping[$plat]
            }
        }
        $allowedFolders = $allowedFolders | Select-Object -Unique

        $osFolders = $osFolders | Where-Object {
            $folderName = $_.Name
            $allowedFolders -contains $folderName
        }

        Write-Verbose "Platform filter active: Processing folders: $($osFolders.Name -join ', ')"
    }

    $totalPolicies = 0
    $policyTypefolders = @()

    foreach ($osFolder in $osFolders) {
        # Get policy type subfolders within each OS folder
        $subFolders = Get-ChildItem -Path $osFolder.FullName -Directory | Where-Object {
            $_.Name -notmatch '^\.' -and (Get-ChildItem -Path $_.FullName -Filter "*.json" -File -Recurse).Count -gt 0
        }

        foreach ($subFolder in $subFolders) {
            $jsonFiles = Get-ChildItem -Path $subFolder.FullName -Filter "*.json" -File -Recurse
            $totalPolicies += $jsonFiles.Count
            $policyTypefolders += @{
                Folder     = $subFolder
                OsFolder   = $osFolder.Name
                PolicyType = $subFolder.Name
            }
        }
    }

    if ($PSCmdlet.ShouldProcess("$totalPolicies policies from OpenIntuneBaseline", "Import to Intune")) {

        # Pre-fetch existing policies from all unique endpoints to avoid repeated API calls
        $endpointPolicyCache = @{}
        $uniqueEndpoints = $odataTypeToEndpoint.Values | Sort-Object -Unique
        foreach ($cacheEndpoint in $uniqueEndpoints) {
            $endpointPolicyCache[$cacheEndpoint] = @{}
            try {
                $listUri = "beta/$cacheEndpoint"
                do {
                    $cacheResponse = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop
                    foreach ($policy in $cacheResponse.value) {
                        # Use 'name' for configurationPolicies, 'displayName' for others
                        $policyDisplayName = if ($cacheEndpoint -eq 'deviceManagement/configurationPolicies') {
                            $policy.name
                        } else {
                            if ($policy.displayName) { $policy.displayName } elseif ($policy.name) { $policy.name } else { $null }
                        }
                        if ($policyDisplayName -and -not $endpointPolicyCache[$cacheEndpoint].ContainsKey($policyDisplayName)) {
                            $endpointPolicyCache[$cacheEndpoint][$policyDisplayName] = $policy.id
                        }
                    }
                    $listUri = $cacheResponse.'@odata.nextLink'
                } while ($listUri)
            } catch {
                # Endpoint might not support listing, continue without cache for this endpoint
                Write-Verbose "Could not cache policies from $cacheEndpoint - will check individually"
            }
        }

        foreach ($policyFolder in $policyTypefolders) {
            $folder = $policyFolder.Folder
            $folderName = $policyFolder.PolicyType
            $osName = $policyFolder.OsFolder
            $jsonFiles = Get-ChildItem -Path $folder.FullName -Filter "*.json" -File -Recurse

            # For IntuneManagement folders, try to import using @odata.type routing
            if ($folderName -in $intuneManagementFolders) {
                foreach ($jsonFile in $jsonFiles) {
                    $policyName = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)

                    try {
                        # Read JSON content and replace %OrganizationId% placeholder with actual tenant ID
                        $jsonContent = Get-Content -Path $jsonFile.FullName -Raw
                        if ($jsonContent -match '%OrganizationId%') {
                            Write-Verbose "Replacing %OrganizationId% with tenant ID in $policyName"
                            $jsonContent = $jsonContent -replace '%OrganizationId%', $TenantId
                        }
                        $policyContent = $jsonContent | ConvertFrom-Json
                        $odataType = $policyContent.'@odata.type'

                        # Determine endpoint from @odata.type
                        $typeEndpoint = $odataTypeToEndpoint[$odataType]
                        if (-not $typeEndpoint) {
                            Write-Warning " Skipping $policyName - unsupported @odata.type: $odataType"
                            $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Skipped' -Status "Unsupported @odata.type: $odataType"
                            continue
                        }

                        # Check for Windows Driver Update license requirement
                        if ($typeEndpoint -eq 'deviceManagement/windowsDriverUpdateProfiles') {
                            # Lazy-load the license check (only check once)
                            if ($null -eq $hasDriverUpdateLicense) {
                                Write-Verbose "Checking Windows Driver Update license..."
                                $hasDriverUpdateLicense = Test-WindowsDriverUpdateLicense
                                if (-not $hasDriverUpdateLicense) {
                                    Write-HydrationLog -Message "Windows Driver Update profiles require additional licensing (Windows E3/E5, M365 Business Premium, etc.)" -Level Warning
                                }
                            }

                            if (-not $hasDriverUpdateLicense) {
                                Write-HydrationLog -Message " Skipped: $policyName - Missing Windows Driver Update license" -Level Warning
                                $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Skipped' -Status 'Missing Windows Driver Update license (requires Windows E3/E5, M365 Business Premium, or equivalent)'
                                continue
                            }
                        }

                        # Get display name
                        $displayName = $policyContent.displayName
                        if (-not $displayName) {
                            $displayName = $policyName
                        }

                        # Check if policy exists using pre-fetched cache
                        $existingPolicy = $endpointPolicyCache[$typeEndpoint].ContainsKey($displayName)

                        if ($existingPolicy -and $ImportMode -eq 'SkipIfExists') {
                            Write-HydrationLog -Message " Skipped: $displayName" -Level Info
                            $results += New-HydrationResult -Name $displayName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Skipped' -Status 'Already exists'
                            continue
                        }

                        # Prepare import body - remove read-only and assignment properties
                        $importBody = Copy-DeepObject -InputObject $policyContent
                        Remove-ReadOnlyGraphProperties -InputObject $importBody -AdditionalProperties @(
                            'supportsScopeTags', 'deviceManagementApplicabilityRuleOsEdition',
                            'deviceManagementApplicabilityRuleOsVersion',
                            'deviceManagementApplicabilityRuleDeviceMode',
                            '@odata.id', '@odata.editLink',
                            'creationSource', 'settingCount', 'priorityMetaData',
                            'assignments', 'settingDefinitions', 'isAssigned'
                        )

                        # Add hydration kit tag to description
                        $existingDesc = if ($importBody.description) { $importBody.description } else { "" }
                        $importBody.description = if ($existingDesc) { "$existingDesc - Imported by Intune Hydration Kit" } else { "Imported by Intune Hydration Kit" }

                        # Remove properties with @odata annotations (metadata) except @odata.type
                        # Also remove #microsoft.graph.* action properties
                        $metadataProps = @($importBody.PSObject.Properties | Where-Object {
                                ($_.Name -match '^@odata\.' -and $_.Name -ne '@odata.type') -or
                                ($_.Name -match '@odata\.') -or
                                ($_.Name -match '^#microsoft\.graph\.')
                            })
                        foreach ($prop in $metadataProps) {
                            if ($prop.Name -ne '@odata.type') {
                                $importBody.PSObject.Properties.Remove($prop.Name)
                            }
                        }

                        # Special handling for Settings Catalog (configurationPolicies)
                        if ($typeEndpoint -eq 'deviceManagement/configurationPolicies') {
                            Write-Verbose " Processing Settings Catalog policy: $displayName"

                            # Build a clean body with only the required properties
                            $cleanBody = @{
                                name         = $importBody.name
                                description  = $importBody.description
                                platforms    = $importBody.platforms
                                technologies = $importBody.technologies
                                settings     = @()
                            }

                            # Add optional properties if present
                            if ($importBody.roleScopeTagIds) {
                                $cleanBody.roleScopeTagIds = $importBody.roleScopeTagIds
                            }
                            if ($importBody.templateReference -and $importBody.templateReference.templateId) {
                                $cleanBody.templateReference = @{
                                    templateId = $importBody.templateReference.templateId
                                }
                            }

                            # Clean settings - remove id and odata navigation properties from each setting
                            if ($importBody.settings) {
                                foreach ($setting in $importBody.settings) {
                                    $cleanSetting = $setting | ConvertTo-Json -Depth 100 -Compress | ConvertFrom-Json

                                    # Remove read-only properties from the setting
                                    $propsToRemove = @($cleanSetting.PSObject.Properties | Where-Object {
                                            $_.Name -eq 'id' -or $_.Name -match '@odata\.' -or $_.Name -eq 'settingDefinitions'
                                        })
                                    foreach ($prop in $propsToRemove) {
                                        $cleanSetting.PSObject.Properties.Remove($prop.Name)
                                    }

                                    $cleanBody.settings += $cleanSetting
                                }
                            }

                            $importBody = [PSCustomObject]$cleanBody
                        }

                        # Clean up scheduledActionsForRule - remove nested @odata.context and IDs
                        if ($importBody.scheduledActionsForRule) {
                            $cleanedActions = @()
                            foreach ($action in $importBody.scheduledActionsForRule) {
                                $cleanAction = @{
                                    ruleName = $action.ruleName
                                }
                                if ($action.scheduledActionConfigurations) {
                                    $cleanConfigs = @()
                                    foreach ($config in $action.scheduledActionConfigurations) {
                                        # Ensure notificationMessageCCList is always an array, never null
                                        $ccList = @()
                                        if ($null -ne $config.notificationMessageCCList -and $config.notificationMessageCCList.Count -gt 0) {
                                            $ccList = @($config.notificationMessageCCList)
                                        }
                                        $cleanConfig = @{
                                            actionType                = $config.actionType
                                            gracePeriodHours          = [int]$config.gracePeriodHours
                                            notificationTemplateId    = if ($config.notificationTemplateId) { $config.notificationTemplateId } else { "" }
                                            notificationMessageCCList = $ccList
                                        }
                                        $cleanConfigs += $cleanConfig
                                    }
                                    $cleanAction.scheduledActionConfigurations = $cleanConfigs
                                }
                                $cleanedActions += $cleanAction
                            }
                            $importBody.scheduledActionsForRule = $cleanedActions
                        }

                        # Create the policy
                        $null = Invoke-MgGraphRequest -Method POST -Uri "beta/$typeEndpoint" -Body ($importBody | ConvertTo-Json -Depth 100) -ContentType 'application/json' -ErrorAction Stop

                        Write-HydrationLog -Message " Created: $displayName" -Level Info
                        $results += New-HydrationResult -Name $displayName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Created' -Status 'Success'
                    } catch {
                        $errorMsg = Get-GraphErrorMessage -ErrorRecord $_
                        Write-HydrationLog -Message " Failed: $policyName - $errorMsg" -Level Warning
                        $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Failed' -Status $errorMsg
                    }

                    Start-Sleep -Milliseconds 100
                }
                continue
            }

            # Determine API endpoint based on policy type folder name
            $endpoint = $endpointMap[$folderName]
            if (-not $endpoint) {
                Write-Warning "No endpoint mapping for folder: $osName/$folderName - skipping"
                foreach ($jsonFile in $jsonFiles) {
                    $policyName = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)
                    $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Skipped' -Status "No endpoint mapping for $folderName"
                }
                continue
            }

            # Progress tracking for this folder
            $folderTotal = $jsonFiles.Count
            $folderCurrent = 0

            # Pre-fetch existing policies for this endpoint to avoid repeated API calls (page through all results)
            $existingPolicies = @{}
            try {
                $listUri = "beta/$endpoint"
                do {
                    $existingResponse = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop
                    foreach ($policy in $existingResponse.value) {
                        $policyDisplayName = if ($policy.displayName) { $policy.displayName } elseif ($policy.name) { $policy.name } else { $null }
                        if ($policyDisplayName -and -not $existingPolicies.ContainsKey($policyDisplayName)) {
                            $existingPolicies[$policyDisplayName] = $policy.id
                        }
                    }
                    $listUri = $existingResponse.'@odata.nextLink'
                } while ($listUri)
            } catch {
                # Endpoint might not support listing, continue without cache
                Write-Verbose "Could not cache policies from $endpoint - will check individually"
            }

            foreach ($jsonFile in $jsonFiles) {
                $folderCurrent++
                Write-Progress -Activity "Importing $osName/$folderName" -Status "$folderCurrent of $folderTotal" -PercentComplete (($folderCurrent / $folderTotal) * 100)

                $policyName = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)

                try {
                    # Read JSON content and replace %OrganizationId% placeholder with actual tenant ID
                    $jsonContent = Get-Content -Path $jsonFile.FullName -Raw
                    if ($jsonContent -match '%OrganizationId%') {
                        Write-Verbose "Replacing %OrganizationId% with tenant ID in $policyName"
                        $jsonContent = $jsonContent -replace '%OrganizationId%', $TenantId
                    }
                    $policyContent = $jsonContent | ConvertFrom-Json

                    # Get display name from policy
                    $displayName = $policyContent.displayName
                    if (-not $displayName) {
                        $displayName = $policyContent.name
                    }
                    if (-not $displayName) {
                        $displayName = $policyName
                    }

                    # Check if policy exists using cached list
                    $existingPolicy = $existingPolicies.ContainsKey($displayName)

                    if ($existingPolicy -and $ImportMode -eq 'SkipIfExists') {
                        Write-HydrationLog -Message " Skipped: $displayName" -Level Info
                        $results += New-HydrationResult -Name $displayName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Skipped' -Status 'Already exists'
                        continue
                    }

                    # Clean up import properties that shouldn't be sent
                    $importBody = Copy-DeepObject -InputObject $policyContent

                    # Remove read-only and system properties
                    Remove-ReadOnlyGraphProperties -InputObject $importBody -AdditionalProperties @(
                        'supportsScopeTags', 'deviceManagementApplicabilityRuleOsEdition',
                        'deviceManagementApplicabilityRuleOsVersion',
                        'deviceManagementApplicabilityRuleDeviceMode',
                        'creationSource', 'settingCount', 'priorityMetaData'
                    )

                    # Add hydration kit tag to description
                    $existingDesc = if ($importBody.description) { $importBody.description } else { "" }
                    $importBody.description = if ($existingDesc) { "$existingDesc - Imported by Intune Hydration Kit" } else { "Imported by Intune Hydration Kit" }

                    # Create the policy
                    $null = Invoke-MgGraphRequest -Method POST -Uri "beta/$endpoint" -Body ($importBody | ConvertTo-Json -Depth 100) -ContentType 'application/json' -ErrorAction Stop

                    Write-HydrationLog -Message " Created: $displayName" -Level Info

                    $results += New-HydrationResult -Name $displayName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Created' -Status 'Success'
                } catch {
                    $errorMsg = Get-GraphErrorMessage -ErrorRecord $_
                    Write-HydrationLog -Message " Failed: $policyName - $errorMsg" -Level Warning

                    $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'Failed' -Status $errorMsg
                }

                # Small delay to avoid rate limiting
                Start-Sleep -Milliseconds 100
            }
            Write-Progress -Activity "Importing $osName/$folderName" -Completed
        }

    } else {
        # WhatIf mode - just report what would be imported
        foreach ($policyFolder in $policyTypefolders) {
            $folder = $policyFolder.Folder
            $osName = $policyFolder.OsFolder
            $folderName = $policyFolder.PolicyType
            $jsonFiles = Get-ChildItem -Path $folder.FullName -Filter "*.json" -File -Recurse

            foreach ($jsonFile in $jsonFiles) {
                $policyName = [System.IO.Path]::GetFileNameWithoutExtension($jsonFile.Name)

                $results += New-HydrationResult -Name $policyName -Path $jsonFile.FullName -Type "$osName/$folderName" -Action 'WouldCreate' -Status 'DryRun'
            }
        }
    }

    return $results
}