Public/Connect-IntuneAssignmentChecker.ps1

function Connect-IntuneAssignmentChecker {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, HelpMessage = "App ID for authentication")]
        [string]$AppId,

        [Parameter(Mandatory = $false, HelpMessage = "Tenant ID for authentication")]
        [string]$TenantId,

        [Parameter(Mandatory = $false, HelpMessage = "Certificate Thumbprint for authentication")]
        [string]$CertificateThumbprint,

        [Parameter(Mandatory = $false, HelpMessage = "Client Secret for authentication")]
        [string]$ClientSecret,

        [Parameter(Mandatory = $false, HelpMessage = "Environment (Global, USGov, USGovDoD)")]
        [ValidateSet("Global", "USGov", "USGovDoD")]
        [string]$Environment = "Global"
    )

    # ── Banner ────────────────────────────────────────────────────────────
    $localVersion = "4.0.0"

    Write-Host "INTUNE ASSIGNMENT CHECKER" -ForegroundColor Cyan
    Write-Host "Made by Ugur Koc" -NoNewline
    Write-Host " | Version" -NoNewline; Write-Host " $localVersion" -ForegroundColor Yellow -NoNewline
    Write-Host " | Last updated: " -NoNewline; Write-Host "2026-04-14" -ForegroundColor Magenta
    Write-Host ""
    Write-Host "Feedback & Issues: " -NoNewline -ForegroundColor Cyan
    Write-Host "https://github.com/ugurkocde/IntuneAssignmentChecker/issues" -ForegroundColor White
    Write-Host "Changelog: " -NoNewline -ForegroundColor Cyan
    Write-Host "https://github.com/ugurkocde/IntuneAssignmentChecker/releases" -ForegroundColor White
    Write-Host ""
    Write-Host "Support this Project: " -NoNewline -ForegroundColor Cyan
    Write-Host "https://github.com/sponsors/ugurkocde" -ForegroundColor White
    Write-Host ""
    Write-Host "DISCLAIMER: This script is provided AS IS without warranty of any kind." -ForegroundColor Yellow
    Write-Host ""

    # ── Version check via PSGallery ───────────────────────────────────────
    try {
        $galleryModule = Find-Module -Name 'IntuneAssignmentChecker' -Repository PSGallery -ErrorAction Stop
        $local  = [System.Version]::new($localVersion)
        $latest = [System.Version]::new($galleryModule.Version)

        if ($local -lt $latest) {
            Write-Host "A newer version is available on PSGallery: $($galleryModule.Version) (you are running $localVersion)" -ForegroundColor Yellow
            Write-Host "Run 'Update-Module IntuneAssignmentChecker' to upgrade." -ForegroundColor Yellow
            Write-Host ""
        }
        elseif ($local -gt $latest) {
            Write-Host "Note: You are running a pre-release version ($localVersion)" -ForegroundColor Magenta
            Write-Host ""
        }
    }
    catch {
        Write-Host "Unable to check for updates. Continue with current version..." -ForegroundColor Gray
    }

    # ── Determine if parameters provide app-based auth credentials ────────
    $hasAppId          = -not [string]::IsNullOrWhiteSpace($AppId)
    $hasTenantId       = -not [string]::IsNullOrWhiteSpace($TenantId)
    $hasClientSecret   = -not [string]::IsNullOrWhiteSpace($ClientSecret)
    $hasCertThumbprint = -not [string]::IsNullOrWhiteSpace($CertificateThumbprint)
    $parameterMode     = $hasAppId -or $hasTenantId -or $hasClientSecret -or $hasCertThumbprint

    # ── Required permissions ──────────────────────────────────────────────
    $requiredPermissions = @(
        @{ Permission = "User.Read.All";                         Reason = "Required to read user profile information and check group memberships" }
        @{ Permission = "Group.Read.All";                        Reason = "Needed to read group information and memberships" }
        @{ Permission = "DeviceManagementConfiguration.Read.All"; Reason = "Allows reading Intune device configuration policies and their assignments" }
        @{ Permission = "DeviceManagementApps.Read.All";         Reason = "Necessary to read mobile app management policies and app configurations" }
        @{ Permission = "DeviceManagementManagedDevices.Read.All"; Reason = "Required to read managed device information and compliance policies" }
        @{ Permission = "Device.Read.All";                       Reason = "Needed to read device information from Entra ID" }
        @{ Permission = "DeviceManagementScripts.Read.All";      Reason = "Needed to read device management and health scripts" }
        @{ Permission = "CloudPC.Read.All";                      Reason = "Required to read Windows 365 Cloud PC provisioning policies and settings (optional if W365 not licensed)" }
        @{ Permission = "DeviceManagementRBAC.Read.All";         Reason = "Required to read role scope tags for scope tag display and filtering" }
    )

    # ── Connect to Microsoft Graph ────────────────────────────────────────
    try {
        $graphContext = Get-MgContext -ErrorAction SilentlyContinue

        if ($null -ne $graphContext) {
            Write-Host "Microsoft Graph is already connected, continuing to check permissions." -ForegroundColor Green
            # Set GraphEndpoint from existing connection environment
            $connectedEnv = $graphContext.Environment
            switch ($connectedEnv) {
                'USGov'    { $script:GraphEndpoint = "https://graph.microsoft.us"; $script:GraphEnvironment = "USGov" }
                'USGovDoD' { $script:GraphEndpoint = "https://dod-graph.microsoft.us"; $script:GraphEnvironment = "USGovDoD" }
                default    { $script:GraphEndpoint = "https://graph.microsoft.com"; $script:GraphEnvironment = "Global" }
            }
        }
        else {
            Write-Host "No existing Microsoft Graph connection found. Attempting connection..." -ForegroundColor Yellow

            if ($hasAppId -and $hasTenantId -and $hasClientSecret) {
                # Client Secret authentication
                Write-Host "Connecting using Client Secret authentication..." -ForegroundColor Yellow
                Set-Environment -EnvironmentName $Environment
                $secureSecret = ConvertTo-SecureString $ClientSecret -AsPlainText -Force
                $credential = New-Object System.Management.Automation.PSCredential($AppId, $secureSecret)
                $null = Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $credential -Environment $script:GraphEnvironment -NoWelcome -ErrorAction Stop
            }
            elseif ($hasAppId -and $hasTenantId -and $hasCertThumbprint) {
                # Certificate-based authentication
                Write-Host "Connecting using Certificate authentication..." -ForegroundColor Yellow
                Set-Environment -EnvironmentName $Environment
                $null = Connect-MgGraph -ClientId $AppId -TenantId $TenantId -Environment $script:GraphEnvironment -CertificateThumbprint $CertificateThumbprint -NoWelcome -ErrorAction Stop
            }
            else {
                # Interactive authentication fallback
                Write-Host "App ID, Tenant ID, or authentication credential (Certificate/Client Secret) is missing or not set correctly." -ForegroundColor Red
                $manualConnection = Read-Host "Would you like to attempt a manual interactive connection? (y/n)"
                if ($manualConnection -match '^[Yy]') {
                    Write-Host "Attempting manual interactive connection (you need privileges to consent permissions)..." -ForegroundColor Yellow
                    $permissionsList = ($requiredPermissions | ForEach-Object { $_.Permission }) -join ', '
                    if ($parameterMode) {
                        Set-Environment -EnvironmentName $Environment
                    }
                    else {
                        Set-Environment
                    }
                    $null = Connect-MgGraph -Scopes $permissionsList -Environment $script:GraphEnvironment -NoWelcome -ErrorAction Stop
                }
                else {
                    Write-Host "Connection cancelled by user." -ForegroundColor Red
                    return
                }
            }
            Write-Host "Successfully connected to Microsoft Graph" -ForegroundColor Green
        }

        # ── Verify permissions ────────────────────────────────────────────
        $context = Get-MgContext
        $currentPermissions = $context.Scopes

        # Store tenant information
        if ($context) {
            $script:CurrentTenantId = $context.TenantId
            $script:CurrentUserUPN  = $context.Account

            try {
                $org = Invoke-MgGraphRequest -Method GET -Uri "$script:GraphEndpoint/v1.0/organization" -ErrorAction SilentlyContinue
                if ($org.value -and $org.value.Count -gt 0) {
                    $script:CurrentTenantName = $org.value[0].displayName
                }
            }
            catch {
                $script:CurrentTenantName = $context.TenantId
            }
        }

        # For app-only auth, Scopes is null -- permissions come from the app registration
        if ($null -eq $currentPermissions -or $currentPermissions.Count -eq 0) {
            Write-Host "App-only authentication detected. Permissions are managed via the app registration." -ForegroundColor Yellow
            Write-Host "Ensure the required permissions are granted in the Azure Portal." -ForegroundColor Yellow
            Write-Host ""
        }
        else {
            Write-Host "Checking required permissions..." -ForegroundColor Cyan
            $missingPermissions = @()
            foreach ($permissionInfo in $requiredPermissions) {
                $permission = $permissionInfo.Permission
                $hasPermission = $currentPermissions -contains $permission -or $currentPermissions -contains $permission.Replace(".Read", ".ReadWrite")
                if (-not $hasPermission) {
                    $missingPermissions += $permission
                }
            }

            if ($missingPermissions.Count -eq 0) {
                Write-Host "All $($requiredPermissions.Count) required permissions verified." -ForegroundColor Green
                Write-Host ""
            }
            else {
                Write-Host "WARNING: The following permissions are missing:" -ForegroundColor Red
                $missingPermissions | ForEach-Object {
                    $missingPermission = $_
                    $reason = ($requiredPermissions | Where-Object { $_.Permission -eq $missingPermission }).Reason
                    Write-Host " - $missingPermission" -ForegroundColor Yellow
                    Write-Host " Reason: $reason" -ForegroundColor Gray
                }
                Write-Host "The script will continue, but it may not function correctly without these permissions." -ForegroundColor Red
                Write-Host "Please ensure these permissions are granted to the app registration for full functionality." -ForegroundColor Yellow

                $continueChoice = Read-Host "Do you want to continue anyway? (y/n)"
                if ($continueChoice -notmatch '^[Yy]') {
                    Write-Host "Connection cancelled by user." -ForegroundColor Red
                    return
                }
            }
        }
    }
    catch {
        Write-Host "Failed to connect to Microsoft Graph. Error: $_" -ForegroundColor Red

        if ($_.Exception.Message -like "*Certificate with thumbprint*was not found*") {
            Write-Host "The specified certificate was not found or has expired. Please check your certificate configuration." -ForegroundColor Yellow
        }

        if ($_.Exception.Message -like "*AADSTS7000215*" -or $_.Exception.Message -like "*Invalid client secret*") {
            Write-Host "The provided client secret is invalid or has expired. Please check your client secret configuration." -ForegroundColor Yellow
        }

        return
    }

    # ── Initialize scope tag lookup ───────────────────────────────────────
    $script:ScopeTagLookup = Get-ScopeTagLookup
}