Invoke-IntuneHydration.ps1

#Requires -Version 7.0
#Requires -Modules Microsoft.Graph.Authentication

<#
.SYNOPSIS
    Main orchestrator script for Intune tenant hydration
.DESCRIPTION
    Executes the complete hydration workflow including authentication,
    pre-flight checks, and import of all baseline configurations.
.PARAMETER SettingsPath
    Path to the settings JSON file
.PARAMETER WhatIf
    Run in dry-run mode without making changes to Intune
.EXAMPLE
    ./Invoke-IntuneHydration.ps1 -SettingsPath ./settings.json
.EXAMPLE
    ./Invoke-IntuneHydration.ps1 -SettingsPath ./settings.json -WhatIf
#>

[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory = $true)]
    [ValidateScript({ Test-Path $_ })]
    [string]$SettingsPath
)


$ErrorActionPreference = 'Stop'
$InformationPreference = 'Continue'

# Resolve paths - script is in module root
$moduleRoot = $PSScriptRoot

# Import the module
$modulePath = Join-Path -Path $moduleRoot -ChildPath 'IntuneHydrationKit.psd1'
if (Test-Path -Path $modulePath) {
    Import-Module -Name $modulePath -Force
}
else {
    throw "Module not found at: $modulePath"
}

#region Main Execution

try {
    # Load settings first to apply options
    $settings = Import-HydrationSettings -Path $SettingsPath

    # Display current settings
    Write-Host "Loaded settings from: $SettingsPath" -InformationAction Continue
    Write-Host "Target Tenant: $($settings.tenant.tenantId)" -InformationAction Continue
    Write-Host "Authentication Mode: $($settings.authentication.mode)" -InformationAction Continue
    Write-Host "Options:" -InformationAction Continue
    Write-Host ($settings.options | Out-String) -InformationAction Continue
    Write-Host "Imports Enabled:" -InformationAction Continue
    Write-Host ($settings.imports | Out-String) -InformationAction Continue

    # Apply options from settings file (command-line switches take precedence)
    # Initialize variables with defaults (these will be set below if $settings.options exists)
    $createEnabled = $false
    $RemoveExisting = $false

    if ($settings.options) {
        # Validate options - create and delete are mutually exclusive
        $createEnabled = $settings.options.create -eq $true
        $deleteEnabled = $settings.options.delete -eq $true

        if ($createEnabled -and $deleteEnabled) {
            throw "Only one of 'create' or 'delete' options can be true. Current settings: create=$createEnabled, delete=$deleteEnabled"
        }

        if (-not $createEnabled -and -not $deleteEnabled) {
            throw "At least one of 'create' or 'delete' options must be true. Current settings: create=$createEnabled, delete=$deleteEnabled"
        }

        # dryRun from settings enables WhatIf if not already set via command line
        if ($settings.options.dryRun -eq $true -and -not $WhatIfPreference) {
            $script:WhatIfPreference = $true
        }

        # Set operation mode based on options
        $RemoveExisting = $settings.options.delete -eq $true

        # verbose from settings enables verbose output
        if ($settings.options.verbose -eq $true) {
            $script:VerbosePreference = 'Continue'
        }
    }

    # Initialize logging (after applying verbose setting)
    $logsPath = Join-Path -Path $moduleRoot -ChildPath 'Logs'
    Initialize-HydrationLogging -LogPath $logsPath -EnableVerbose:($VerbosePreference -eq 'Continue')

    Write-HydrationLog -Message "=== Intune Hydration Kit Started ===" -Level Info
    Write-HydrationLog -Message "Loaded settings for tenant: $($settings.tenant.tenantId)" -Level Info

    if ($WhatIfPreference) {
        Write-HydrationLog -Message "Running in DRY-RUN mode - no changes will be made" -Level Warning
    }

    if ($RemoveExisting) {
        if (-not $createEnabled) {
            Write-HydrationLog -Message "DELETE-ONLY mode - configurations will be deleted without recreation" -Level Warning
        }
        else {
            Write-HydrationLog -Message "Remove existing enabled - matching configurations will be deleted before import" -Level Warning
        }
    }

    # Initialize results tracking
    $allResults = @()

    # Step 1: Authenticate
    Write-HydrationLog -Message "Step 1: Authenticating to Microsoft Graph" -Level Info

    $authParams = @{
        TenantId = $settings.tenant.tenantId
    }

    # Add environment if specified
    if ($settings.authentication.environment) {
        $authParams['Environment'] = $settings.authentication.environment
    }

    if ($settings.authentication.mode -eq 'clientSecret') {
        $authParams['ClientId'] = $settings.authentication.clientId
        $authParams['ClientSecret'] = $settings.authentication.clientSecret | ConvertTo-SecureString -AsPlainText -Force
    }
    else {
        $authParams['Interactive'] = $true
    }

    # Always connect to Graph API (needed for dry-run to check existing policies)
    Connect-IntuneHydration @authParams

    # Step 2: Pre-flight checks
    Write-HydrationLog -Message "Step 2: Running pre-flight checks" -Level Info

    # Always run pre-flight checks (read-only operations)
    Test-IntunePrerequisites | Out-Null

    # Step 3: Dynamic Groups
    if ($settings.imports.dynamicGroups) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Creating" }
        Write-HydrationLog -Message "Step 3: $stepAction Dynamic Groups" -Level Info

        # Delete existing dynamic groups if RemoveExisting is set
        # SAFETY: Only delete groups that have "Imported by Intune-Hydration-Kit" in description
        if ($RemoveExisting) {

            try {
                # Get all dynamic groups with descriptions
                $listUri = "beta/groups?`$filter=groupTypes/any(c:c eq 'DynamicMembership')&`$select=id,displayName,description"
                do {
                    $existingGroups = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop
                    foreach ($group in $existingGroups.value) {
                        # Safety check: Only delete if created by this kit (has hydration marker in description)
                        if (-not (Test-HydrationKitObject -Description $group.description -ObjectName $group.displayName)) {
                            Write-Verbose "Skipping '$($group.displayName)' - not created by Intune-Hydration-Kit"
                            continue
                        }

                        if ($PSCmdlet.ShouldProcess($group.displayName, "Delete dynamic group")) {
                            try {
                                Invoke-MgGraphRequest -Method DELETE -Uri "beta/groups/$($group.id)" -ErrorAction Stop
                                Write-HydrationLog -Message " Deleted: $($group.displayName)" -Level Info
                                $allResults += New-HydrationResult -Type 'DynamicGroup' -Name $group.displayName -Action 'Deleted' -Status 'Success'
                            }
                            catch {
                                Write-HydrationLog -Message "Failed to delete group '$($group.displayName)': $_" -Level Warning
                                $allResults += New-HydrationResult -Type 'DynamicGroup' -Name $group.displayName -Action 'Failed' -Status $_.Exception.Message
                            }
                        }
                        else {
                            $allResults += New-HydrationResult -Type 'DynamicGroup' -Name $group.displayName -Action 'WouldDelete' -Status 'DryRun'
                        }
                    }
                    $listUri = $existingGroups.'@odata.nextLink'
                } while ($listUri)
            }
            catch {
                Write-HydrationLog -Message "Failed to list dynamic groups: $_" -Level Warning
            }
        }
        else {
            # Normal create mode
            $groupsTemplatePath = Join-Path -Path $moduleRoot -ChildPath 'Templates/DynamicGroups'

            if (Test-Path -Path $groupsTemplatePath) {
                $groupTemplates = Get-ChildItem -Path $groupsTemplatePath -Filter "*.json" -File

                # Collect all groups from templates
                $allGroupDefs = @()
                foreach ($templateFile in $groupTemplates) {
                    $templateContent = Get-Content -Path $templateFile.FullName -Raw | ConvertFrom-Json

                    # Handle templates with multiple groups
                    $groups = if ($templateContent.groups) { $templateContent.groups } else { @($templateContent) }
                    $allGroupDefs += $groups
                }

                foreach ($groupDef in $allGroupDefs) {
                    if ($PSCmdlet.ShouldProcess($groupDef.displayName, "Create dynamic group")) {
                        $groupResult = New-IntuneDynamicGroup -DisplayName $groupDef.displayName -Description $groupDef.description -MembershipRule $groupDef.membershipRule

                        $allResults += New-HydrationResult -Type 'DynamicGroup' -Name $groupDef.displayName -Action $groupResult.Action -Id $groupResult.Id -Details $groupResult.Reason
                        Write-HydrationLog -Message " $($groupResult.Action): $($groupDef.displayName)" -Level Info
                    }
                }
            }
            else {
                Write-HydrationLog -Message "Dynamic Groups template directory not found" -Level Warning
            }
        }
    }

    # Step 4: Device Filters
    if ($settings.imports.deviceFilters) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Creating" }
        Write-HydrationLog -Message "Step 4: $stepAction Device Filters" -Level Info

        $filterResults = Import-IntuneDeviceFilter -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $filterResults
    }

    # Step 5: OpenIntuneBaseline
    if ($settings.imports.openIntuneBaseline) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 5: $stepAction OpenIntuneBaseline policies" -Level Info

        $baselineParams = @{}

        if ($settings.openIntuneBaseline.downloadPath) {
            $baselineParams['BaselinePath'] = $settings.openIntuneBaseline.downloadPath
        }

        # Import function handles ShouldProcess internally for each policy
        $baselineParams['RemoveExisting'] = $RemoveExisting
        $baselineParams['WhatIf'] = $WhatIfPreference
        $baselineResults = Import-IntuneBaseline @baselineParams
        $allResults += $baselineResults
    }

    # Step 6: Compliance Templates
    if ($settings.imports.complianceTemplates) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 6: $stepAction Compliance templates" -Level Info

        $complianceResults = Import-IntuneCompliancePolicy -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $complianceResults
    }

    # Step 7: Notification Templates
    if ($settings.imports.notificationTemplates) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 7: $stepAction Notification Templates" -Level Info

        $notificationResults = Import-IntuneNotificationTemplate -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $notificationResults
    }

    # Step 8: App Protection Policies (MAM)
    if ($settings.imports.appProtection) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 8: $stepAction App Protection policies" -Level Info

        $mamResults = Import-IntuneAppProtectionPolicy -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $mamResults
    }

    # Step 9: Enrollment Profiles
    if ($settings.imports.enrollmentProfiles) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 9: $stepAction Enrollment Profiles" -Level Info

        $enrollmentResults = Import-IntuneEnrollmentProfile -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $enrollmentResults
    }

    # Step 10: Conditional Access Starter Pack
    if ($settings.imports.conditionalAccess) {
        $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" }
        Write-HydrationLog -Message "Step 10: $stepAction Conditional Access Starter Pack" -Level Info

        $caResults = Import-IntuneConditionalAccessPolicy -RemoveExisting:$RemoveExisting -WhatIf:$WhatIfPreference
        $allResults += $caResults
    }

    # Step 11: Generate Summary Report
    Write-HydrationLog -Message "Step 11: Generating Summary Report" -Level Info

    $reportsPath = Join-Path -Path $moduleRoot -ChildPath $settings.reporting.outputPath
    if (-not (Test-Path -Path $reportsPath)) {
        New-Item -Path $reportsPath -ItemType Directory -Force | Out-Null
    }

    $summary = Get-ResultSummary -Results $allResults

    # Generate markdown report
    $reportPath = Join-Path -Path $reportsPath -ChildPath "Hydration-Summary.md"
    $jsonReportPath = $null
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    $reportContent = @"
# Intune Hydration Summary
 
**Generated:** $timestamp
**Tenant:** $($settings.tenant.tenantId)
**Environment:** $($settings.authentication.environment)
**Mode:** $(if ($WhatIfPreference) { 'Dry-Run' } else { 'Live' })
 
## Summary
 
| Metric | Count |
|--------|-------|
| Total Operations | $($allResults.Count) |
| Created | $($summary.Created) |
| Updated | $($summary.Updated) |
| Skipped | $($summary.Skipped) |
| Would Create | $($summary.WouldCreate) |
| Would Update | $($summary.WouldUpdate) |
| Failed | $($summary.Failed) |
 
## Details by Type
 
"@


    # Group results by type
    $byType = $allResults | Group-Object -Property Type
    foreach ($typeGroup in $byType) {
        $typeResults = $typeGroup.Group
        $created = ($typeResults | Where-Object { $_.Action -eq 'Created' }).Count
        $updated = ($typeResults | Where-Object { $_.Action -eq 'Updated' }).Count
        $skipped = ($typeResults | Where-Object { $_.Action -eq 'Skipped' }).Count
        $wouldCreate = ($typeResults | Where-Object { $_.Action -eq 'WouldCreate' }).Count
        $failed = ($typeResults | Where-Object { $_.Action -eq 'Failed' }).Count

        $wouldUpdate = ($typeResults | Where-Object { $_.Action -eq 'WouldUpdate' }).Count

        $reportContent += @"
 
### $($typeGroup.Name)
- Created: $created
- Updated: $updated
- Skipped: $skipped
- Would Create: $wouldCreate
- Would Update: $wouldUpdate
- Failed: $failed
 
"@

    }

    if ($allResults.Count -gt 0) {
        $reportContent += @"
 
## All Operations
 
| Timestamp | Type | Name | Action | ID | Details |
|-----------|------|------|--------|-----|---------|
"@


        foreach ($result in $allResults) {
            $reportContent += "| $($result.Timestamp) | $($result.Type) | $($result.Name) | $($result.Action) | $($result.Id) | $($result.Details) |`n"
        }
    }

    $reportContent += @"
 
## Important Notes
 
- **Conditional Access policies** were created in **DISABLED** state. Review and enable as needed.
- **OpenIntuneBaseline policies** were imported using IntuneManagement module.
- Review all configurations before enabling in production.
 
"@


    $reportContent | Out-File -FilePath $reportPath -Encoding utf8
    Write-HydrationLog -Message "Summary report written to: $reportPath" -Level Info

    # Also write JSON if requested
    if ('json' -in $settings.reporting.formats) {
        $jsonReportPath = Join-Path -Path $reportsPath -ChildPath "Hydration-Summary.json"
        @{
            Timestamp = $timestamp
            Tenant = $settings.tenant.tenantId
            Environment = $settings.authentication.environment
            Mode = if ($WhatIfPreference) { 'DryRun' } else { 'Live' }
            Summary = $summary
            Results = $allResults
        } | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonReportPath -Encoding utf8
        Write-HydrationLog -Message "JSON report written to: $jsonReportPath" -Level Info
    }

    Write-HydrationLog -Message "=== Intune Hydration Kit Completed ===" -Level Info

    # Friendly console summary
    Write-Host "" -InformationAction Continue
    Write-Host "---------------- Summary ----------------" -InformationAction Continue
    if ($WhatIfPreference) {
        Write-Host ("Would Create: {0} | Would Update: {1} | Would Delete: {2} | Skipped: {3} | Failed: {4}" -f $summary.WouldCreate, $summary.WouldUpdate, $summary.WouldDelete, $summary.Skipped, $summary.Failed) -InformationAction Continue
    }
    else {
        Write-Host ("Created: {0} | Updated: {1} | Deleted: {2} | Skipped: {3} | Failed: {4}" -f $summary.Created, $summary.Updated, $summary.Deleted, $summary.Skipped, $summary.Failed) -InformationAction Continue
    }
    Write-Host "Reports: $reportPath" -InformationAction Continue
    if ($jsonReportPath) {
        Write-Host "JSON: $jsonReportPath" -InformationAction Continue
    }
    Write-Host "----------------------------------------" -InformationAction Continue

    # Exit with appropriate code
    if ($summary.Failed -gt 0) {
        Write-HydrationLog -Message "Completed with $($summary.Failed) failures" -Level Warning
        exit 1
    }
    else {
        if ($WhatIfPreference) {
            Write-HydrationLog -Message "Dry-run completed: $($summary.WouldCreate) would create, $($summary.WouldUpdate) would update, $($summary.WouldDelete) would delete, $($summary.Skipped) skipped" -Level Info
        }
        else {
            Write-HydrationLog -Message "Completed successfully: $($summary.Created) created, $($summary.Updated) updated, $($summary.Skipped) skipped" -Level Info
        }
        exit 0
    }
}
catch {
    Write-HydrationLog -Message "Fatal error: $_" -Level Error
    Write-Error $_
    exit 1
}

#endregion