Public/Restore-NLBaselineFromIntune.ps1

<#
.SYNOPSIS
    Restores NLBaseline policies from exported backup files to Intune.
.DESCRIPTION
    Imports previously exported NLBaseline policies from JSON backup files back to Intune.
    Allows selection of specific backup files or restores all available backups.
.PARAMETER BackupFile
    Optional. Specific backup file to restore. If omitted, shows list of available backups.
.EXAMPLE
    Restore-NLBaselineFromIntune
    Restore-NLBaselineFromIntune -BackupFile "export_MicrosoftDefender_20240123_120000.json"
#>

function Restore-NLBaselineFromIntune {
    [CmdletBinding()]
    param(
        [string]$BackupFile
    )

    $ErrorActionPreference = "Stop"
    $workspacePath = Get-WorkspacePath
    if (-not $workspacePath) {
        Write-Error "Workspace not configured. Run Initialize-NLBaseline first."
        return
    }

    $config = Get-Config -WorkspacePath $workspacePath
    if (-not $config -or [string]::IsNullOrEmpty($config.AppRegistration.ClientId) -or [string]::IsNullOrEmpty($config.AppRegistration.ClientSecret) -or [string]::IsNullOrEmpty($config.AppRegistration.TenantId)) {
        Write-Error "App Registration not configured in config.json."
        return
    }

    $exportsDir = Join-Path -Path $workspacePath -ChildPath "Intune\Exports"
    if (-not (Test-Path -Path $exportsDir)) {
        Write-Error "Exports directory not found: $exportsDir. No backups available to restore."
        return
    }

    # Get all export files
    $backupFiles = Get-ChildItem -Path $exportsDir -Filter "export_*.json" | Sort-Object LastWriteTime -Descending

    if ($backupFiles.Count -eq 0) {
        Write-Error "No backup files found in $exportsDir. Export policies first using Export-NLBaselineFromIntune."
        return
    }

    # If specific file provided, use it; otherwise show selection
    $selectedFiles = @()
    if ($BackupFile) {
        $fullPath = Join-Path -Path $exportsDir -ChildPath $BackupFile
        if (-not (Test-Path -Path $fullPath)) {
            Write-Error "Backup file not found: $fullPath"
            return
        }
        $selectedFiles = @($fullPath)
    }
    else {
        Write-Host "`nAvailable backup files:`n" -ForegroundColor Cyan
        $index = 1
        foreach ($file in $backupFiles) {
            $fileInfo = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            $settingsCount = 0
            if ($null -ne $fileInfo) {
                if ($fileInfo -is [Array]) { $settingsCount = $fileInfo.Count }
                elseif ($null -ne $fileInfo.omaSettings) { $settingsCount = @($fileInfo.omaSettings).Count }
            }
            Write-Host " $index. $($file.Name) ($settingsCount settings, $($file.LastWriteTime))" -ForegroundColor White
            $index++
        }

        Write-Host "`n 0. Restore All" -ForegroundColor Yellow
        Write-Host " -1. Cancel`n" -ForegroundColor Yellow

        $choice = Read-Host "Select backup file(s) to restore (comma-separated for multiple, or 0 for all)"

        if ($choice -eq '-1') {
            Write-Host "Restore cancelled." -ForegroundColor Yellow
            return
        }

        if ($choice -eq '0') {
            $selectedFiles = $backupFiles | Select-Object -ExpandProperty FullName
        }
        else {
            $indices = $choice -split ',' | ForEach-Object { [int]::Parse($_.Trim()) }
            foreach ($idx in $indices) {
                if ($idx -ge 1 -and $idx -le $backupFiles.Count) {
                    $selectedFiles += $backupFiles[$idx - 1].FullName
                }
            }
        }
    }

    if ($selectedFiles.Count -eq 0) {
        Write-Error "No backup files selected."
        return
    }

    Write-Host "`nRestoring $($selectedFiles.Count) backup file(s) to Intune...`n" -ForegroundColor Cyan

    # Connect to Intune
    $connected = Connect-Intune -Config $config
    if (-not $connected) {
        Write-Error "Failed to connect to Microsoft Graph."
        return
    }

    $restored = 0
    $failed = 0

    foreach ($backupPath in $selectedFiles) {
        try {
            Write-Host "Processing: $(Split-Path -Leaf $backupPath)" -ForegroundColor Yellow

            $raw = Get-Content -Path $backupPath -Raw | ConvertFrom-Json
            $isNewFormat = $null -ne $raw -and $null -ne $raw.omaSettings

            $omaSettings = @()
            $policyName = "NLBaseline - " + ((Split-Path -Leaf $backupPath) -replace '^export_', '' -replace '_\d{8}_\d{6}\.json$', '')
            $policyDesc = "Restored from backup: $(Split-Path -Leaf $backupPath)"

            if ($isNewFormat) {
                $policyName = $raw.displayName
                $policyDesc = if ($raw.description) { $raw.description } else { $policyDesc }
                foreach ($o in @($raw.omaSettings)) {
                    $omaSettings += @{
                        "@odata.type" = $o.'@odata.type'
                        omaUri        = $o.omaUri
                        value         = $o.value
                        displayName   = $o.displayName
                        description   = $o.description
                    }
                }
            }
            else {
                $legacy = @($raw)
                if (-not $legacy -or $legacy.Count -eq 0) {
                    Write-Warning " No data found in backup file."
                    $failed++
                    continue
                }
                foreach ($setting in $legacy) {
                    $pathPart = "Registry/" + (if ($setting.Hive -eq 1) { "HKCU/" } else { "HKLM/" })
                    $pathPart += $setting.KeyName.Replace('\', '/')
                    if ($setting.ValueName) { $pathPart += "/$($setting.ValueName)" }
                    $omaUri = "./Device/Vendor/MSFT/" + $pathPart
                    $omaSetting = @{
                        "@odata.type" = if ($setting.Type -eq 1) { "#microsoft.graph.omaSettingString" } else { "#microsoft.graph.omaSettingInteger" }
                        displayName  = if ($setting.FriendlyName) { $setting.FriendlyName } else { "$($setting.KeyName)\$($setting.ValueName)" }
                        description  = if ($setting.URL) { $setting.URL } else { "" }
                        omaUri       = $omaUri
                    }
                    if ($setting.Type -eq 1) {
                        $omaSetting["value"] = $setting.RegValue
                    }
                    else {
                        $intVal = 0
                        [int]::TryParse($setting.RegValue, [ref]$intVal) | Out-Null
                        $omaSetting["value"] = $intVal
                    }
                    $omaSettings += $omaSetting
                }
            }

            if ($omaSettings.Count -eq 0) {
                Write-Warning " No OMA settings in backup; skip."
                $failed++
                continue
            }

            $chunkSize = 900
            $chunks = @()
            for ($i = 0; $i -lt $omaSettings.Count; $i += $chunkSize) {
                $end = [Math]::Min($i + $chunkSize - 1, $omaSettings.Count - 1)
                $chunks += , @($omaSettings[$i..$end])
            }

            for ($ci = 0; $ci -lt $chunks.Count; $ci++) {
                $chunk = $chunks[$ci]
                $displayName = $policyName
                if ($chunks.Count -gt 1) { $displayName += " (Part $($ci + 1))" }
                $body = @{
                    "@odata.type" = "#microsoft.graph.windows10CustomConfiguration"
                    displayName  = $displayName
                    description  = $policyDesc
                    omaSettings  = $chunk
                }
                $res = Invoke-IntuneGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations" -Body ($body | ConvertTo-Json -Depth 20)
                Write-Host " Created policy: $($res.displayName) (id: $($res.id))" -ForegroundColor Green
                $restored++
            }
        }
        catch {
            Write-Error " Failed to restore $(Split-Path -Leaf $backupPath): $_"
            $failed++
        }
    }

    Write-Host "`nRestore complete!" -ForegroundColor Cyan
    Write-Host " Restored: $restored policy/policies" -ForegroundColor Green
    if ($failed -gt 0) {
        Write-Host " Failed: $failed backup file(s)" -ForegroundColor Red
    }
}