Public/Authentication/Test-IntunePrerequisites.ps1
|
function Test-IntunePrerequisites { <# .SYNOPSIS Validates Intune tenant prerequisites .DESCRIPTION Checks for Intune license availability, Azure AD Premium P2 license (for risk-based Conditional Access), and required Microsoft Graph permission scopes. Non-blocking notes are emitted when Premium P2 is not found, as certain Conditional Access policies that use sign-in risk or user risk conditions require this license level. .PARAMETER RequiredScopes Delegated Graph scopes requested during interactive authentication. When omitted, scopes are resolved from the selected imports for direct prerequisite checks. .EXAMPLE Test-IntunePrerequisites #> [CmdletBinding()] param( [Parameter()] [hashtable]$Imports = @{}, [Parameter()] [hashtable]$MobileAppConfiguration = @{}, [Parameter()] [string[]]$MobileAppPlatforms = @('All'), [Parameter()] [string[]]$AppProtectionPlatforms = @('All'), [Parameter()] [string[]]$BaselinePlatforms = @('All'), [Parameter()] [string[]]$RequiredScopes ) Write-Information (Format-HydrationDisplayMessage -Message 'Validating Intune prerequisites...' -Style 'Section' -Emoji '🔎') -InformationAction Continue $issues = @() $notes = [System.Collections.Generic.List[object]]::new() $riskBasedPolicyNames = @() $conditionalAccessSelected = $Imports.Count -eq 0 -or ($Imports.ContainsKey('conditionalAccess') -and $Imports.conditionalAccess) $requiredScopes = if ($RequiredScopes) { $RequiredScopes } else { Get-HydrationGraphScopes ` -Imports $Imports ` -MobileAppConfiguration $MobileAppConfiguration ` -MobileAppPlatforms $MobileAppPlatforms } try { # Check organization info and licenses $org = Invoke-MgGraphRequest -Method GET -Uri "beta/organization?`$select=id,displayName" -ErrorAction Stop $orgDetails = $org.value[0] Write-Information (Format-HydrationDisplayMessage -Message "Connected to: $($orgDetails.displayName)" -Style 'Info' -Emoji '🏢') -InformationAction Continue # Check for Intune service plan $subscribedSkus = Invoke-MgGraphRequest -Method GET -Uri "beta/subscribedSkus?`$select=id,skuPartNumber,capabilityStatus,servicePlans" -ErrorAction Stop $intuneServicePlans = @( 'INTUNE_A', # Intune Plan 1 'INTUNE_EDU', # Intune for Education 'INTUNE_SMBIZ', # Intune Small Business 'AAD_PREMIUM', # Azure AD Premium (includes some Intune features) 'EMSPREMIUM' # Enterprise Mobility + Security ) # Premium P2 service plans (required for risk-based Conditional Access) $premiumP2ServicePlans = Get-PremiumP2ServicePlans $hasIntune = $false $hasPremiumP2 = $false foreach ($sku in $subscribedSkus.value) { # Skip disabled SKUs if ($sku.capabilityStatus -ne 'Enabled') { continue } foreach ($plan in $sku.servicePlans) { if ($plan.servicePlanName -in $intuneServicePlans -and $plan.provisioningStatus -eq 'Success') { $hasIntune = $true Write-Verbose "Found Intune license: $($plan.servicePlanName)" } if ($plan.servicePlanName -in $premiumP2ServicePlans -and $plan.provisioningStatus -eq 'Success') { $hasPremiumP2 = $true Write-Verbose "Found Premium P2 compatible license: $($plan.servicePlanName) in SKU $($sku.skuPartNumber)" } } } if (-not $hasIntune) { $issues += "No active Intune license found. Please ensure Intune is licensed for this tenant." } if ($conditionalAccessSelected -and -not $hasPremiumP2) { $riskBasedPolicyNames = @( 'Require multifactor authentication for risky sign-ins' 'Require password change for high-risk users' 'Block high risk agent identities' 'Block access to Office365 apps for users with insider risk' ) $notes.Add([PSCustomObject]@{ Message = 'Azure AD Premium P2 not detected. Risk-based Conditional Access templates will be skipped:' DetailLines = $riskBasedPolicyNames }) } if ($conditionalAccessSelected) { $notes.Add([PSCustomObject]@{ Message = 'Some Conditional Access templates use private preview features and will be skipped unless the tenant is explicitly authorized.' DetailLines = @() }) } # Check for required permission scopes $context = Get-MgContext if ($null -eq $context) { $issues += "Not connected to Microsoft Graph. Please run Connect-IntuneHydration first." } else { $isAppOnly = $context.AuthType -eq 'AppOnly' -or ($context.ClientId -and -not $context.Account) if ($isAppOnly) { # App-only auth uses app roles, so delegated scope validation does not apply $notes.Add([PSCustomObject]@{ Message = 'App-only authentication detected; delegated scope validation was skipped.' DetailLines = @() }) } else { $currentScopes = $context.Scopes $missingScopes = @($requiredScopes | Where-Object { $currentScopes -notcontains $_ }) if ($missingScopes.Count -gt 0) { $issues += "Missing required permission scopes: $($missingScopes -join ', ')" } else { Write-Verbose 'All required permission scopes are present' } } } if ($issues.Count -eq 0) { $workloadAccessIssues = @(Test-HydrationGraphWorkloadAccess ` -Imports $Imports ` -MobileAppConfiguration $MobileAppConfiguration ` -MobileAppPlatforms $MobileAppPlatforms ` -AppProtectionPlatforms $AppProtectionPlatforms ` -BaselinePlatforms $BaselinePlatforms) if ($workloadAccessIssues.Count -gt 0) { $issues += $workloadAccessIssues } } if ($notes.Count -gt 0) { Write-Information (Format-HydrationDisplayMessage -Message 'Notes:' -Style 'Section' -Emoji '📝') -InformationAction Continue foreach ($note in $notes) { Write-Information (Format-HydrationDisplayMessage -Message $note.Message -Style 'Info' -Emoji '•' -Indent 2) -InformationAction Continue foreach ($policyName in $note.DetailLines) { Write-Information (Format-HydrationDisplayMessage -Message "'$policyName'" -Style 'Muted' -Emoji '↳' -Indent 4) -InformationAction Continue } } } # Report results if ($issues.Count -gt 0) { foreach ($issue in $issues) { Write-Warning $issue } # Surface specific issues in the exception message so callers/tests can pattern match $issueMessage = $issues -join ' | ' $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Prerequisite checks failed: $issueMessage"), 'PrerequisiteCheckFailed', [System.Management.Automation.ErrorCategory]::NotEnabled, $null ) $PSCmdlet.ThrowTerminatingError($errorRecord) } Write-Information (Format-HydrationDisplayMessage -Message 'Pre-flight checks passed' -Style 'Success' -Emoji '✅') -InformationAction Continue return $true } catch { if ($_.Exception.Message -match "Prerequisite checks failed") { throw } $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Failed to validate prerequisites: $($_.Exception.Message)", $_.Exception), 'PrerequisiteValidationFailed', [System.Management.Automation.ErrorCategory]::NotSpecified, $null ) $PSCmdlet.ThrowTerminatingError($errorRecord) } } |