Src/Private/Connect-IntuneSession.ps1
|
function Connect-IntuneSession { <# .SYNOPSIS Establishes authenticated connections to Microsoft Graph for Intune reporting. Supports both interactive delegated auth and app-only (cert/secret) auth. Validates that all required scopes are present in the token, reconnecting if needed. Supports Global, USGov, and USGovDoD sovereign cloud environments. .NOTES Version: 0.1.2 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$UserPrincipalName ) #region Validate UPN if (-not (Test-UserPrincipalName -UserPrincipalName $UserPrincipalName)) { $errorMsg = "Invalid User Principal Name format: '$UserPrincipalName'. Expected format: user@domain.com" Write-TranscriptLog $errorMsg 'ERROR' 'AUTH' throw $errorMsg } #endregion Write-TranscriptLog "Starting connection to Microsoft Graph for Intune report: $UserPrincipalName" 'INFO' 'AUTH' #region Graph endpoint -- sovereign cloud support $EnvSetting = if ($script:Options.GraphEnvironment) { $script:Options.GraphEnvironment } else { 'Global' } $script:GraphEndpoint = switch ($EnvSetting) { 'USGov' { 'https://graph.microsoft.us' } 'USGovDoD' { 'https://dod-graph.microsoft.us' } default { 'https://graph.microsoft.com' } } if ($EnvSetting -ne 'Global') { Write-Host " - Graph endpoint: $($script:GraphEndpoint) ($EnvSetting)" -ForegroundColor Cyan } Write-AbrDebugLog "Graph endpoint: $($script:GraphEndpoint) (env: $EnvSetting)" 'INFO' 'AUTH' #endregion $GraphScopes = @( 'Organization.Read.All' 'Directory.Read.All' 'DeviceManagementConfiguration.Read.All' # Config profiles, compliance, scripts, baselines 'DeviceManagementApps.Read.All' # MAM, apps, proactive remediations 'DeviceManagementManagedDevices.Read.All' # Managed devices 'DeviceManagementServiceConfig.Read.All' # Enrollment, Autopilot 'DeviceManagementRBAC.Read.All' # RBAC / scope tags 'Group.Read.All' # Assignment group resolution + member counts 'User.Read.All' # User lookups 'Reports.Read.All' # Reporting data 'CloudPC.Read.All' # Windows 365 / Cloud PC ) #region App-only authentication (cert or client secret) $AppAuthConfig = $script:Options.AppOnlyAuth $useAppOnly = ($AppAuthConfig -and $AppAuthConfig.AppId -and $AppAuthConfig.TenantId -and ($AppAuthConfig.CertificateThumbprint -or $AppAuthConfig.ClientSecret)) if ($useAppOnly) { Write-Host " - Authentication: App-only (AppId: $($AppAuthConfig.AppId))" -ForegroundColor Cyan Write-TranscriptLog "App-only auth configured -- AppId: $($AppAuthConfig.AppId)" 'INFO' 'AUTH' $connectParams = @{ ClientId = $AppAuthConfig.AppId TenantId = $AppAuthConfig.TenantId NoWelcome = $true ErrorAction = 'Stop' } if ($AppAuthConfig.CertificateThumbprint) { $connectParams['CertificateThumbprint'] = $AppAuthConfig.CertificateThumbprint Write-Host " - Auth method: Certificate (thumbprint: $($AppAuthConfig.CertificateThumbprint.Substring(0,[Math]::Min(8,$AppAuthConfig.CertificateThumbprint.Length)))...)" -ForegroundColor Cyan } elseif ($AppAuthConfig.ClientSecret) { # ClientSecret auth requires converting to credential $secSecret = ConvertTo-SecureString $AppAuthConfig.ClientSecret -AsPlainText -Force $clientCred = [System.Management.Automation.PSCredential]::new($AppAuthConfig.AppId, $secSecret) $connectParams['ClientSecretCredential'] = $clientCred $connectParams.Remove('ClientId') Write-Host " - Auth method: Client Secret" -ForegroundColor Cyan } if ($EnvSetting -ne 'Global') { $connectParams['Environment'] = $EnvSetting } try { Invoke-WithRetry -ScriptBlock { Connect-MgGraph @connectParams } -OperationName 'Connect to Microsoft Graph (app-only)' Write-Host " - App-only authentication successful." -ForegroundColor Green $script:GraphScopesFullyConfirmed = $true Write-TranscriptLog "App-only auth successful" 'SUCCESS' 'AUTH' } catch { throw "App-only authentication failed: $($_.Exception.Message)" } return } #endregion #region Interactive / delegated auth with scope validation $ExistingGraph = $null try { $ExistingGraph = Get-MgContext -ErrorAction SilentlyContinue } catch { } if ($ExistingGraph -and $ExistingGraph.TenantId) { $GrantedScopes = @($ExistingGraph.Scopes) $MissingScopes = $GraphScopes | Where-Object { $GrantedScopes -notcontains $_ } if ($MissingScopes -and $MissingScopes.Count -gt 0) { Write-Host " - Existing session missing scope(s) -- reconnecting:" -ForegroundColor Yellow foreach ($s in $MissingScopes) { Write-Host " Missing: $s" -ForegroundColor Yellow } Write-TranscriptLog "Session scope mismatch -- missing: $($MissingScopes -join ', '). Reconnecting." 'WARNING' 'AUTH' try { $null = Disconnect-MgGraph -ErrorAction SilentlyContinue } catch { } $ExistingGraph = $null } else { Write-Host " - Reusing existing Graph session (all required scopes confirmed)." -ForegroundColor Green Write-TranscriptLog "Reusing existing Graph session -- all scopes verified (TenantId: $($ExistingGraph.TenantId))" 'SUCCESS' 'AUTH' $null = ($script:GraphScopesFullyConfirmed = $true) } } if (-not $ExistingGraph -or -not $ExistingGraph.TenantId) { Write-Host " - Connecting to Microsoft Graph..." Write-TranscriptLog "Connecting to Microsoft Graph with required Intune scopes" 'INFO' 'AUTH' $connectParams = @{ Scopes = $GraphScopes NoWelcome = $true ErrorAction = 'Stop' } if ($EnvSetting -ne 'Global') { $connectParams['Environment'] = $EnvSetting } Invoke-WithRetry -ScriptBlock { Connect-MgGraph @connectParams } -OperationName 'Connect to Microsoft Graph (Intune)' $MgCtx = Get-MgContext -ErrorAction SilentlyContinue if ($MgCtx -and $MgCtx.TenantId) { Write-TranscriptLog "Microsoft Graph connection verified (TenantId: $($MgCtx.TenantId))" 'SUCCESS' 'AUTH' $GrantedScopes = @($MgCtx.Scopes) $StillMissing = $GraphScopes | Where-Object { $GrantedScopes -notcontains $_ } if ($StillMissing -and $StillMissing.Count -gt 0) { Write-Host " - WARNING: The following scopes were not granted:" -ForegroundColor Yellow foreach ($s in $StillMissing) { Write-Host " Not granted: $s" -ForegroundColor Yellow } Write-TranscriptLog "Post-connect: still missing -- $($StillMissing -join ', ')" 'WARNING' 'AUTH' $null = ($script:GraphScopesFullyConfirmed = $false) } else { Write-Host " - All required scopes confirmed in token." -ForegroundColor Green Write-TranscriptLog "All required scopes confirmed in token" 'SUCCESS' 'AUTH' $null = ($script:GraphScopesFullyConfirmed = $true) } } else { throw "Connect-MgGraph succeeded but Get-MgContext returned no context." } } #endregion Write-Host " - Microsoft Graph connected successfully." -ForegroundColor Green Write-TranscriptLog "Microsoft Graph connection established for Intune report: $UserPrincipalName" 'SUCCESS' 'AUTH' } function Disconnect-IntuneSession { [CmdletBinding()] param() Write-TranscriptLog "Disconnecting Microsoft Graph session" 'INFO' 'AUTH' try { $GraphCtx = Get-MgContext -ErrorAction SilentlyContinue if ($GraphCtx) { Write-Host " - Disconnecting Microsoft Graph..." $null = Disconnect-MgGraph -ErrorAction SilentlyContinue Write-TranscriptLog "Microsoft Graph session disconnected" 'SUCCESS' 'AUTH' } } catch { Write-TranscriptLog "Microsoft Graph disconnect warning: $($_.Exception.Message)" 'WARNING' 'AUTH' } Write-Host " - Session disconnected." -ForegroundColor Green Write-TranscriptLog "Intune session disconnected" 'SUCCESS' 'AUTH' } |