Public/Invoke-IntuneHydration.ps1
|
#Requires -Version 7.0 function Invoke-IntuneHydration { <# .SYNOPSIS Main orchestrator function for Intune tenant hydration .DESCRIPTION Executes the complete hydration workflow including authentication, pre-flight checks, and import of all baseline configurations. Two mutually exclusive invocation modes: 1. Settings File Mode: Use -SettingsPath to load all configuration from a JSON file 2. Parameter Mode: Use -Interactive or -ClientId/-ClientSecret with other parameters These modes cannot be mixed - choose one or the other. .PARAMETER SettingsPath Path to the settings JSON file. Use this for settings file-based invocation. Cannot be combined with -Interactive, -ClientId, or -ClientSecret. .PARAMETER TenantId Azure AD tenant ID (GUID format). Required for parameter-based invocation. .PARAMETER TenantName Tenant name for display purposes (e.g., contoso.onmicrosoft.com) .PARAMETER Interactive Use interactive authentication (browser-based login). Cannot be combined with -SettingsPath. .PARAMETER ClientId Application (client) ID for service principal authentication. Cannot be combined with -SettingsPath. .PARAMETER ClientSecret Client secret for service principal authentication (SecureString). Cannot be combined with -SettingsPath. .PARAMETER Environment Azure cloud environment. Valid values: Global, USGov, USGovDoD, Germany, China .PARAMETER Create Enable creation of configurations .PARAMETER Delete Enable deletion of kit-created configurations .PARAMETER Force Skip confirmation prompt when running in delete mode (available for both settings-file and parameter modes) .PARAMETER VerboseOutput Enable verbose logging output .PARAMETER OpenIntuneBaseline Process OpenIntuneBaseline policies .PARAMETER ComplianceTemplates Process compliance policy templates .PARAMETER AppProtection Process app protection policies .PARAMETER NotificationTemplates Process notification templates .PARAMETER EnrollmentProfiles Process enrollment profiles (Autopilot, ESP) .PARAMETER DynamicGroups Process dynamic groups .PARAMETER StaticGroups Process static (assigned) groups .PARAMETER DeviceFilters Process device filters .PARAMETER ConditionalAccess Process Conditional Access starter pack policies .PARAMETER MobileApps Process mobile app templates .PARAMETER All Enable all targets .PARAMETER Platform Filter imports by platform. Valid values: Windows, macOS, iOS, Android, Linux, All. Defaults to 'All' which imports resources for all platforms. This affects: ComplianceTemplates, DeviceFilters, AppProtection, MobileApps, EnrollmentProfiles, OpenIntuneBaseline. Cross-platform resources (DynamicGroups, StaticGroups, ConditionalAccess, NotificationTemplates) are not filtered. .PARAMETER ReportOutputPath Output directory for reports .PARAMETER ReportFormats Report formats to generate (markdown, json) .EXAMPLE Invoke-IntuneHydration -SettingsPath ./settings.json Run using settings from a JSON file. .EXAMPLE Invoke-IntuneHydration -SettingsPath ./settings.json -WhatIf Dry-run using settings file. .EXAMPLE Invoke-IntuneHydration -TenantId "00000000-0000-0000-0000-000000000000" -Interactive -Create -All Run with all imports enabled using interactive authentication. .EXAMPLE Invoke-IntuneHydration -TenantId "00000000-0000-0000-0000-000000000000" -ClientId "client-id" -ClientSecret $secret -Create -ComplianceTemplates -DynamicGroups Run with service principal authentication and specific imports enabled. .EXAMPLE Invoke-IntuneHydration -TenantId "00000000-0000-0000-0000-000000000000" -Interactive -Delete -All -WhatIf Dry-run delete mode with interactive authentication. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'SettingsFile')] param( # Settings file parameter - exclusive mode [Parameter(ParameterSetName = 'SettingsFile', Mandatory = $true, Position = 0)] [ValidateScript({ Test-Path $_ })] [string]$SettingsPath, # Tenant parameters - required for parameter-based modes [Parameter(ParameterSetName = 'Interactive', Mandatory = $true)] [Parameter(ParameterSetName = 'ServicePrincipal', Mandatory = $true)] [ValidatePattern('^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] [string]$TenantId, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [string]$TenantName, # Authentication parameters - Interactive mode [Parameter(ParameterSetName = 'Interactive', Mandatory = $true)] [switch]$Interactive, # Authentication parameters - Service Principal mode [Parameter(ParameterSetName = 'ServicePrincipal', Mandatory = $true)] [string]$ClientId, [Parameter(ParameterSetName = 'ServicePrincipal', Mandatory = $true)] [SecureString]$ClientSecret, # Environment - available for parameter-based modes only [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [ValidateSet('Global', 'USGov', 'USGovDoD', 'Germany', 'China')] [string]$Environment = 'Global', # Options parameters - available for parameter-based modes only [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$Create, [Parameter(ParameterSetName = 'SettingsFile')] [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$Delete, [Parameter(ParameterSetName = 'SettingsFile')] [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$Force, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$VerboseOutput, # Target enable switches - available for parameter-based modes only [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$OpenIntuneBaseline, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$ComplianceTemplates, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$AppProtection, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$NotificationTemplates, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$EnrollmentProfiles, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$DynamicGroups, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$StaticGroups, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$DeviceFilters, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$ConditionalAccess, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$MobileApps, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$CISBaselines, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [switch]$All, # Platform filter - available for all parameter sets [Parameter(ParameterSetName = 'SettingsFile')] [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [ValidateSet('Windows', 'macOS', 'iOS', 'Android', 'Linux', 'All')] [string[]]$Platform = @('All'), # Reporting parameters - available for parameter-based modes only [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [string]$ReportOutputPath, [Parameter(ParameterSetName = 'Interactive')] [Parameter(ParameterSetName = 'ServicePrincipal')] [ValidateSet('markdown', 'json')] [string[]]$ReportFormats ) $ErrorActionPreference = 'Stop' $InformationPreference = 'Continue' # Resolve module root - use $script:ModuleRoot if set by psm1, otherwise use PSScriptRoot parent $moduleRoot = if ($script:ModuleRoot) { $script:ModuleRoot } else { $splitPathParams = @{ Path = $PSScriptRoot Parent = $true } Split-Path @splitPathParams } #region Main Execution $executionStartTime = Get-Date try { $resolveSettingsParams = @{ ParameterSetName = $PSCmdlet.ParameterSetName SettingsPath = $SettingsPath Force = $Force Platform = $Platform TenantId = $TenantId TenantName = $TenantName Interactive = $Interactive ClientId = $ClientId ClientSecret = $ClientSecret Environment = $Environment Create = $Create Delete = $Delete VerboseOutput = $VerboseOutput OpenIntuneBaseline = $OpenIntuneBaseline ComplianceTemplates = $ComplianceTemplates AppProtection = $AppProtection NotificationTemplates = $NotificationTemplates EnrollmentProfiles = $EnrollmentProfiles DynamicGroups = $DynamicGroups StaticGroups = $StaticGroups DeviceFilters = $DeviceFilters ConditionalAccess = $ConditionalAccess MobileApps = $MobileApps CISBaselines = $CISBaselines All = $All ReportOutputPath = $ReportOutputPath ReportFormats = $ReportFormats WhatIfEnabled = [bool]$WhatIfPreference CommandRuntime = $PSCmdlet } $settings = Resolve-HydrationExecutionSettings @resolveSettingsParams Write-HydrationExecutionSettingsSummary -Settings $settings $platformFilters = Get-HydrationPlatformFilters -Platforms $settings.platforms $effectiveWhatIfEnabled = [bool]$WhatIfPreference -or ($settings.options.dryRun -eq $true) $effectiveVerboseEnabled = ($VerbosePreference -eq 'Continue') -or ($settings.options.verbose -eq $true) if ($settings.options.verbose -eq $true -and $VerbosePreference -ne 'Continue') { $VerbosePreference = 'Continue' Write-Verbose 'Verbose output enabled for this hydration run' } # Apply options from settings $createEnabled = $settings.options.create -eq $true $deleteEnabled = $settings.options.delete -eq $true $forceDelete = $settings.options.force -eq $true $RemoveExisting = $deleteEnabled $testOperationSettingsParams = @{ CreateEnabled = $createEnabled DeleteEnabled = $deleteEnabled ForceDelete = $forceDelete WhatIfEnabled = $effectiveWhatIfEnabled PSCmdlet = $PSCmdlet } if (-not (Test-HydrationOperationSettings @testOperationSettingsParams)) { return } # Initialize logging (after applying verbose setting) # Uses OS temp directory by default (e.g., $env:TEMP/IntuneHydrationKit/Logs on Windows, /tmp/IntuneHydrationKit/Logs on macOS/Linux) Initialize-HydrationLogging -EnableVerbose:$effectiveVerboseEnabled $logParams = @{ Message = '=== Intune Hydration Kit Started ===' Level = 'Info' } Write-HydrationLog @logParams $logParams = @{ Message = "Loaded settings for tenant: $(Get-ObfuscatedTenantId -TenantId $settings.tenant.tenantId)" Level = 'Info' } Write-HydrationLog @logParams if ($effectiveWhatIfEnabled) { $logParams = @{ Message = 'Running in DRY-RUN mode - no changes will be made' Level = 'Warning' } Write-HydrationLog @logParams } if ($RemoveExisting) { if (-not $createEnabled) { $logParams = @{ Message = 'DELETE-ONLY mode - configurations will be deleted without recreation' Level = 'Warning' } Write-HydrationLog @logParams } else { $logParams = @{ Message = 'Remove existing enabled - matching configurations will be deleted before import' Level = 'Warning' } Write-HydrationLog @logParams } } # Initialize results tracking $allResults = @() # Step 1: Authenticate $logParams = @{ Message = 'Step 1: Authenticating to Microsoft Graph' Level = 'Info' } Write-HydrationLog @logParams $getAuthParams = @{ AuthenticationSettings = $settings.authentication TenantId = $settings.tenant.tenantId } $authParams = Get-HydrationAuthParameters @getAuthParams $authParams['Verbose'] = $effectiveVerboseEnabled # Always connect to Graph API (needed for dry-run to check existing policies) Connect-IntuneHydration @authParams # Step 2: Pre-flight checks $logParams = @{ Message = 'Step 2: Running pre-flight checks' Level = 'Info' } Write-HydrationLog @logParams # Always run pre-flight checks (read-only operations) Test-IntunePrerequisites -Verbose:$effectiveVerboseEnabled | Out-Null # Step 3: Dynamic Groups if ($settings.imports.dynamicGroups) { $joinPathParams = @{ Path = $moduleRoot ChildPath = 'Templates/DynamicGroups' } $dynamicGroupTemplatePath = Join-Path @joinPathParams $dynamicGroupStepParams = @{ StepLabel = 'Step 3' GroupType = 'Dynamic' TemplatePath = $dynamicGroupTemplatePath Platforms = $platformFilters.Groups RemoveExisting = $RemoveExisting WhatIfEnabled = $effectiveWhatIfEnabled } $dynamicGroupResults = @((Invoke-HydrationGroupStep @dynamicGroupStepParams) | Where-Object { $null -ne $_ }) $allResults += $dynamicGroupResults } # Step 3b: Static Groups if ($settings.imports.staticGroups) { $joinPathParams = @{ Path = $moduleRoot ChildPath = 'Templates/StaticGroups' } $staticGroupTemplatePath = Join-Path @joinPathParams $staticGroupStepParams = @{ StepLabel = 'Step 3b' GroupType = 'Static' TemplatePath = $staticGroupTemplatePath Platforms = $platformFilters.Groups RemoveExisting = $RemoveExisting WhatIfEnabled = $effectiveWhatIfEnabled } $staticGroupResults = @((Invoke-HydrationGroupStep @staticGroupStepParams) | Where-Object { $null -ne $_ }) $allResults += $staticGroupResults } # Step 4: Device Filters if ($settings.imports.deviceFilters) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Creating" } $logParams = @{ Message = "Step 4: $stepAction Device Filters" Level = 'Info' } Write-HydrationLog @logParams $filterParams = @{ Platform = $platformFilters.DeviceFilters RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $filterResults = @((Import-IntuneDeviceFilter @filterParams) | Where-Object { $null -ne $_ }) $allResults += $filterResults } # Step 5: OpenIntuneBaseline if ($settings.imports.openIntuneBaseline) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 5: $stepAction OpenIntuneBaseline policies" Level = 'Info' } Write-HydrationLog @logParams $baselineParams = @{} # Import function handles ShouldProcess internally for each policy $baselineParams['RemoveExisting'] = $RemoveExisting $baselineParams['WhatIf'] = $effectiveWhatIfEnabled $baselineParams['Platform'] = $platformFilters.Baseline $baselineParams['Verbose'] = $effectiveVerboseEnabled $baselineResults = @((Import-IntuneBaseline @baselineParams) | Where-Object { $null -ne $_ }) $allResults += $baselineResults } # Step 5b: CIS Baselines if ($settings.imports.cisBaselines) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 5b: $stepAction CIS Baseline policies" Level = 'Info' } Write-HydrationLog @logParams $cisParams = @{ RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Platform = $platformFilters.CISBaseline Verbose = $effectiveVerboseEnabled } $cisResults = @((Import-CISBaseline @cisParams) | Where-Object { $null -ne $_ }) $allResults += $cisResults } # Step 6: Compliance Templates if ($settings.imports.complianceTemplates) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 6: $stepAction Compliance templates" Level = 'Info' } Write-HydrationLog @logParams $complianceParams = @{ Platform = $platformFilters.Compliance RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $complianceResults = @((Import-IntuneCompliancePolicy @complianceParams) | Where-Object { $null -ne $_ }) $allResults += $complianceResults } # Step 7: Notification Templates if ($settings.imports.notificationTemplates) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 7: $stepAction Notification Templates" Level = 'Info' } Write-HydrationLog @logParams $notificationParams = @{ RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $notificationResults = @((Import-IntuneNotificationTemplate @notificationParams) | Where-Object { $null -ne $_ }) $allResults += $notificationResults } # Step 8: App Protection Policies (MAM) if ($settings.imports.appProtection) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 8: $stepAction App Protection policies" Level = 'Info' } Write-HydrationLog @logParams $mamParams = @{ Platform = $platformFilters.AppProtection RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $mamResults = @((Import-IntuneAppProtectionPolicy @mamParams) | Where-Object { $null -ne $_ }) $allResults += $mamResults } # Step 9: Enrollment Profiles if ($settings.imports.enrollmentProfiles) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 9: $stepAction Enrollment Profiles" Level = 'Info' } Write-HydrationLog @logParams $enrollmentParams = @{ Platform = $platformFilters.EnrollmentProfiles RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $enrollmentResults = @((Import-IntuneEnrollmentProfile @enrollmentParams) | Where-Object { $null -ne $_ }) $allResults += $enrollmentResults } # Step 10: Conditional Access Starter Pack if ($settings.imports.conditionalAccess) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 10: $stepAction Conditional Access Starter Pack" Level = 'Info' } Write-HydrationLog @logParams $caParams = @{ RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $caResults = @((Import-IntuneConditionalAccessPolicy @caParams) | Where-Object { $null -ne $_ }) $allResults += $caResults } # Step 11: Mobile Apps if ($settings.imports.mobileApps) { $stepAction = if ($RemoveExisting) { "Deleting" } else { "Importing" } $logParams = @{ Message = "Step 11: $stepAction Mobile Apps" Level = 'Info' } Write-HydrationLog @logParams $mobileAppParams = @{ Platform = $platformFilters.MobileApps RemoveExisting = $RemoveExisting WhatIf = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $mobileAppResults = @((Import-IntuneMobileApp @mobileAppParams) | Where-Object { $null -ne $_ }) $allResults += $mobileAppResults } $summaryParams = @{ Settings = $settings Results = $allResults StartTime = $executionStartTime WhatIfEnabled = $effectiveWhatIfEnabled Verbose = $effectiveVerboseEnabled } $summaryOutput = Write-HydrationExecutionSummary @summaryParams $summary = $summaryOutput.Summary $reportPath = $summaryOutput.ReportPath $jsonReportPath = $summaryOutput.JsonReportPath $elapsedTime = $summaryOutput.ElapsedTime $elapsedTimeDisplay = $summaryOutput.ElapsedTimeDisplay # Return summary object (functions shouldn't call exit) return @{ Success = $summary.Failed -eq 0 Summary = $summary Results = $allResults ReportPath = $reportPath JsonReportPath = $jsonReportPath ElapsedTime = $elapsedTime ElapsedTimeDisplay = $elapsedTimeDisplay } } catch { $logParams = @{ Message = "Fatal error: $_" Level = 'Error' } Write-HydrationLog @logParams throw } #endregion } |