Public/Imports/Import-IntuneEnrollmentProfile.ps1

function Import-IntuneEnrollmentProfile {
    <#
    .SYNOPSIS
        Imports enrollment profiles
    .DESCRIPTION
        Creates Windows Autopilot deployment profiles and Enrollment Status Page configurations.
        Optionally creates Apple enrollment profiles if ABM is enabled.
    .PARAMETER TemplatePath
        Path to the enrollment template directory
    .PARAMETER DeviceNameTemplate
        Custom device naming template (default: %SERIAL%)
    .PARAMETER Platform
        Filter templates by platform. Valid values: Windows, macOS, All.
        Defaults to 'All' which imports all enrollment profile templates regardless of platform.
        Note: Enrollment profiles are available for Windows (Autopilot, ESP) and macOS (DEP).
    .EXAMPLE
        Import-IntuneEnrollmentProfile
    .EXAMPLE
        Import-IntuneEnrollmentProfile -Platform Windows
    #>

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

        [Parameter()]
        [string]$DeviceNameTemplate,

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

        [Parameter()]
        [switch]$RemoveExisting
    )

    # Use default template path if not specified
    if (-not $TemplatePath) {
        $TemplatePath = Join-Path -Path $script:TemplatesPath -ChildPath "Enrollment"
    }

    if (-not (Test-Path -Path $TemplatePath)) {
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.Exception]::new("Enrollment template directory not found: $TemplatePath"),
            'EnrollmentTemplateNotFound',
            [System.Management.Automation.ErrorCategory]::ObjectNotFound,
            $TemplatePath
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    $results = @()
    $templateFiles = Get-FilteredTemplates -Path $TemplatePath -Platform $Platform -FilterMode 'Prefix' -ResourceType "enrollment template"

    if ((-not $templateFiles -or $templateFiles.Count -eq 0) -and -not $RemoveExisting) {
        Write-Warning "No $Platform enrollment templates found."
        return @()
    }

    # Remove existing enrollment profiles if requested
    # SAFETY: Only delete profiles that have "Imported by Intune Hydration Kit" in description
    if ($RemoveExisting) {
        # Load template names to scope deletes to only profiles this kit would create
        $knownTemplateNames = if ($templateFiles -and $templateFiles.Count -gt 0) {
            Get-TemplateDisplayNames -TemplateFiles $templateFiles
        } else {
            [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        }
        $includeWindowsEnrollment = -not $Platform -or $Platform -contains 'All' -or $Platform -contains 'Windows'
        $includeMacOSEnrollment = -not $Platform -or $Platform -contains 'All' -or $Platform -contains 'macOS'
        $macOSKnownTemplateNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)

        if ($includeWindowsEnrollment -and $knownTemplateNames.Count -eq 0) {
            $statusMessage = 'No Windows enrollment templates found; enrollment profile delete skipped to avoid unscoped cleanup'
            Write-HydrationLog -Message " Skipped: Windows enrollment profiles - $statusMessage" -Level Warning
            $results += New-HydrationResult -Name 'Windows enrollment profiles' -Type 'EnrollmentProfile' -Action 'Skipped' -Status $statusMessage
            $includeWindowsEnrollment = $false
        }

        if ($includeMacOSEnrollment) {
            $macOSTemplateFiles = @(Get-FilteredTemplates -Path $TemplatePath -Platform @('macOS') -FilterMode 'Prefix' -ResourceType "enrollment template")
            if ($macOSTemplateFiles.Count -gt 0) {
                $macOSKnownTemplateNames = Get-TemplateDisplayNames -TemplateFiles $macOSTemplateFiles
            }

            if ($macOSKnownTemplateNames.Count -eq 0) {
                $statusMessage = 'No macOS enrollment templates found; DEP enrollment profile delete skipped to avoid unscoped cleanup'
                Write-HydrationLog -Message " Skipped: macOS DEP enrollment profiles - $statusMessage" -Level Warning
                $results += New-HydrationResult -Name 'macOS DEP enrollment profiles' -Type 'MacOSDEPEnrollmentProfile' -Action 'Skipped' -Status $statusMessage
                $includeMacOSEnrollment = $false
            }
        }

        if ($includeWindowsEnrollment) {
            # Canonical delete pipeline: enumerate -> marker/template-scoped decision -> batched DELETE
            $windowsDeleteTargets = @(
                @{ Endpoint = 'beta/deviceManagement/windowsAutopilotDeploymentProfiles'; Type = 'AutopilotDeploymentProfile'; Label = 'Autopilot profiles' },
                @{
                    Endpoint        = 'beta/deviceManagement/deviceEnrollmentConfigurations'
                    Type            = 'EnrollmentStatusPage'
                    Label           = 'ESP profiles'
                    CandidateFilter = {
                        param($candidate)
                        [string]$candidate.'@odata.type' -eq '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration'
                    }
                },
                @{ Endpoint = "beta/deviceManagement/configurationPolicies?`$filter=technologies eq 'enrollment'"; Type = 'AutopilotDevicePreparation'; Label = 'Autopilot Device Preparation policies' }
            )

            foreach ($target in $windowsDeleteTargets) {
                $candidateParams = @{
                    Endpoint           = $target.Endpoint
                    KnownTemplateNames = $knownTemplateNames
                }
                if ($target.CandidateFilter) {
                    $candidateParams.CandidateFilter = $target.CandidateFilter
                }

                $deleteCandidates = @(Get-HydrationDeleteCandidates @candidateParams)
                if ($deleteCandidates.Count -eq 0) {
                    Write-Verbose "No $($target.Label) found to delete"
                    continue
                }

                $results += Invoke-HydrationDeleteCandidates -Candidates $deleteCandidates -ResultType $target.Type -Label $target.Label
            }
        }

        if ($includeMacOSEnrollment) {
            # macOS DEP profiles are nested under depOnboardingSettings/{token}/enrollmentProfiles.
            # Enumerate the token-scoped profiles, then use the canonical candidate/delete path.
            try {
                $depTokens = Invoke-MgGraphRequest -Method GET -Uri "beta/deviceManagement/depOnboardingSettings?`$select=id,displayName" -ErrorAction Stop
                $depProfileObjects = @()
                foreach ($depToken in $depTokens.value) {
                    try {
                        $depProfiles = Invoke-MgGraphRequest -Method GET -Uri "beta/deviceManagement/depOnboardingSettings/$($depToken.id)/enrollmentProfiles?`$select=id,displayName,description" -ErrorAction Stop
                    } catch {
                        Write-HydrationLog -Message "Failed to list macOS DEP enrollment profiles for DEP token '$($depToken.displayName)': $_" -Level Warning
                        continue
                    }

                    foreach ($depProfile in $depProfiles.value) {
                        $depProfile | Add-Member -NotePropertyName FullObjectUri -NotePropertyValue "beta/deviceManagement/depOnboardingSettings/$($depToken.id)/enrollmentProfiles/$($depProfile.id)" -Force
                        $depProfile | Add-Member -NotePropertyName DeleteUrl -NotePropertyValue "/deviceManagement/depOnboardingSettings/$($depToken.id)/enrollmentProfiles/$($depProfile.id)" -Force
                        $depProfileObjects += $depProfile
                    }
                }

                $depDeleteCandidates = @(Get-HydrationDeleteCandidates -InputObject $depProfileObjects -KnownTemplateNames $macOSKnownTemplateNames)
                $results += Invoke-HydrationDeleteCandidates -Candidates $depDeleteCandidates -ResultType 'MacOSDEPEnrollmentProfile' -Label 'macOS DEP enrollment profiles'
            } catch {
                Write-HydrationLog -Message "Failed to list DEP onboarding settings: $_" -Level Warning
            }
        }

        return $results
    }

    #region Discover and process all enrollment templates
    foreach ($templateFile in $templateFiles) {
        try {
            $template = Get-Content -Path $templateFile.FullName -Raw -ErrorAction Stop |
                ConvertFrom-Json -ErrorAction Stop
        } catch {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                [System.Exception]::new("Failed to load or parse enrollment template file '$($templateFile.FullName)': $($_.Exception.Message)", $_.Exception),
                'TemplateParseError',
                [System.Management.Automation.ErrorCategory]::ParserError,
                $templateFile.FullName
            )
            $PSCmdlet.WriteError($errorRecord)
            Write-HydrationLog -Message " Failed: $($templateFile.Name) - $($_.Exception.Message)" -Level Error
            $results += New-HydrationResult -Name $templateFile.Name -Type 'EnrollmentTemplate' -Action 'Failed' -Status $_.Exception.Message
            continue
        }
        # Some templates use displayName, others use name (configurationPolicies)
        $templateBaseName = if ($template.displayName) { $template.displayName } else { $template.name }
        $importPrefix = if ([string]::IsNullOrEmpty($script:ImportPrefix)) { '[IHD] ' } else { [string]$script:ImportPrefix }
        $templateBaseName = [string]$templateBaseName
        $isPrefixedTemplateName = $templateBaseName.StartsWith($importPrefix, [System.StringComparison]::OrdinalIgnoreCase)
        $profileName = if ($isPrefixedTemplateName) {
            $templateBaseName
        } else {
            "$importPrefix$templateBaseName"
        }
        if ($isPrefixedTemplateName) {
            $templateBaseName = $templateBaseName.Substring($importPrefix.Length)
        }
        $odataType = $template.'@odata.type'
        if ($template.technologies -eq 'enrollment') { $odataType = '#microsoft.graph.deviceManagementConfigurationPolicy' }

        # Skip templates with empty or missing @odata.type
        if ([string]::IsNullOrWhiteSpace($odataType)) {
            Write-HydrationLog -Message " Skipped: $($templateFile.Name) - Missing @odata.type" -Level Warning
            $results += New-HydrationResult -Name $templateFile.Name -Type 'EnrollmentTemplate' -Action 'Skipped' -Status 'Missing @odata.type'
            continue
        }

        switch ($odataType) {
            '#microsoft.graph.azureADWindowsAutopilotDeploymentProfile' {
                $results += Import-HydrationAutopilotProfile -Template $template -ProfileName $profileName -TemplateBaseName $templateBaseName -DeviceNameTemplate $DeviceNameTemplate
            }

            '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration' {
                $results += Import-HydrationEnrollmentStatusPage -Template $template -ProfileName $profileName -TemplateBaseName $templateBaseName
            }

            '#microsoft.graph.depMacOSEnrollmentProfile' {
                $results += Import-HydrationDepEnrollmentProfile -Template $template -ProfileName $profileName -TemplateBaseName $templateBaseName
            }

            '#microsoft.graph.deviceManagementConfigurationPolicy' {
                $results += Import-HydrationDevicePreparationPolicy -Template $template -ProfileName $profileName -TemplateBaseName $templateBaseName
            }

            default {
                Write-HydrationLog -Message " Skipped: $profileName - Unknown profile type: $odataType" -Level Warning
                $results += New-HydrationResult -Name $profileName -Type 'Unknown' -Action 'Skipped' -Status "Unknown @odata.type: $odataType"
            }
        }
    }
    #endregion

    return $results
}