M365CertAuth.psm1
|
# Certificate Authentication Module for Microsoft 365 #Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.DirectoryManagement function Get-CertificateForAuth { <# .SYNOPSIS Gets or creates a certificate for Azure authentication .DESCRIPTION Handles certificate creation, app registration, permission assignment, and returns certificate for connections .NOTES Automatically connects to Microsoft Graph with required scopes if not already connected .PARAMETER AppName Name of the Azure AD app registration .PARAMETER CertSubject Certificate subject name .EXAMPLE $auth = Get-CertificateForAuth Connect-PnPOnline -Url $url -ClientId $auth.AppId -Tenant $auth.TenantId -CertificateBase64Encoded $auth.CertificateBase64 #> param( [string]$AppName = "M365 PowerShell App", [string]$CertSubject = "CN=M365 PowerShell" ) try { # Establish Graph connection with required scopes try { $context = Get-MgContext -ErrorAction SilentlyContinue $needsConnection = $false if (-not $context) { $needsConnection = $true } else { # Check for required scopes $requiredScopes = @("Application.ReadWrite.All", "AppRoleAssignment.ReadWrite.All", "Directory.Read.All", "RoleManagement.ReadWrite.Directory") $missingScopes = @() foreach ($scope in $requiredScopes) { if ($context.Scopes -notcontains $scope) { $missingScopes += $scope } } if ($missingScopes.Count -gt 0) { $needsConnection = $true } } if ($needsConnection) { Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Yellow Connect-MgGraph -Scopes @("Application.ReadWrite.All", "AppRoleAssignment.ReadWrite.All", "Directory.Read.All", "RoleManagement.ReadWrite.Directory") -NoWelcome -ErrorAction Stop | Out-Null Write-Host "Connected to Microsoft Graph" -ForegroundColor Green } } catch { throw "Failed to connect to Microsoft Graph: $($_.Exception.Message)" } # Validate and select best certificate $allMatchingCerts = Get-ChildItem -Path "Cert:\CurrentUser\My" | Where-Object { $_.Subject -eq $CertSubject } $validCerts = @() foreach ($cert in $allMatchingCerts) { $certStatus = @{ Certificate = $cert IsValid = $true Issues = @() DaysUntilExpiry = ($cert.NotAfter - (Get-Date)).Days HasPrivateKey = $false IsRegisteredInAzure = $false } # Check expiry if ($certStatus.DaysUntilExpiry -le 0) { $certStatus.Issues += "Certificate expired on $($cert.NotAfter.ToString('yyyy-MM-dd HH:mm'))" $certStatus.IsValid = $false } elseif ($certStatus.DaysUntilExpiry -le 30) { $certStatus.Issues += "Certificate expires soon ($($certStatus.DaysUntilExpiry) days)" } # Validate private key try { if ($cert.HasPrivateKey) { $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) if ($rsaCert) { $certStatus.HasPrivateKey = $true } else { $certStatus.Issues += "Private key not accessible" $certStatus.IsValid = $false } } else { $certStatus.Issues += "No private key available" $certStatus.IsValid = $false } } catch { $certStatus.Issues += "Private key validation failed: $($_.Exception.Message)" $certStatus.IsValid = $false } $validCerts += $certStatus } # Select best certificate $bestCert = $validCerts | Where-Object { $_.IsValid -and $_.HasPrivateKey -and $_.DaysUntilExpiry -gt 0 } | Sort-Object @{Expression = {$_.IsRegisteredInAzure}; Descending = $true}, @{Expression = {$_.DaysUntilExpiry}; Descending = $true} | Select-Object -First 1 if (-not $bestCert) { # Clean up expired certificates silently $expiredCerts = $validCerts | Where-Object { -not $_.IsValid } foreach ($expiredCert in $expiredCerts) { try { Remove-Item -Path "Cert:\CurrentUser\My\$($expiredCert.Certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue } catch {} } # Create new certificate $cert = New-SelfSignedCertificate -Subject $CertSubject -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256 -NotAfter (Get-Date).AddYears(2) Write-Host "✓ Certificate created: $($cert.Thumbprint)" -ForegroundColor Green } else { $cert = $bestCert.Certificate if ($bestCert.Issues.Count -gt 0) { Write-Host "⚠️ Certificate issues: $($bestCert.Issues -join '; ')" -ForegroundColor Yellow } Write-Host "✓ Certificate ready: $($cert.Thumbprint) (expires in $($bestCert.DaysUntilExpiry) days)" -ForegroundColor Green } # Get or create app registration $app = Get-MgApplication -Filter "displayName eq '$AppName'" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $app) { Write-Host "Creating app registration and permissions..." -ForegroundColor Yellow $app = New-MgApplication -DisplayName $AppName $servicePrincipal = New-MgServicePrincipal -AppId $app.AppId # Wait for service principal propagation Start-Sleep -Seconds 10 # Assign Microsoft Graph permissions try { $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -ErrorAction Stop $graphAppRoles = @( "62a82d76-70ea-41e2-9197-370581804d09", # Group.ReadWrite.All "741f803b-c850-494e-b5df-cde7c675a1ca", # User.ReadWrite.All "bf7b1a76-6e77-406b-b258-bf5c7720e98f", # Group.Create "810c84a8-4a9e-49e6-bf7d-12d183f40d01", # Mail.Read "75359482-378d-4052-8f01-80520e7db3cd", # Files.ReadWrite.All "9492366f-7969-46a4-8d15-ed1a20078fff", # Sites.ReadWrite.All "0c0bf378-bf22-4481-8f81-9e89a9b4960a", # Sites.FullControl.All "b633e1c5-b582-4048-a93e-9f11b44c7e96", # Mail.Send "e2a3a72e-5f79-4c64-b1b1-878b674786c9", # Mail.ReadWrite "40f97065-369a-49f4-947c-6a255697ae91", # MailboxSettings.ReadWrite "ef54d2bf-783f-4e0f-bca1-3210c0444d99", # Calendars.ReadWrite "19dbc75e-c2e2-444c-a770-ec69d8559fc7", # Directory.ReadWrite.All "230c1aed-a721-4c5d-9cb4-a90514e508ef", # Reports.Read.All "06b708a9-e830-4db3-a914-8e69da51d44f", # AppRoleAssignment.ReadWrite.All "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" # RoleManagement.ReadWrite.Directory ) foreach ($roleId in $graphAppRoles) { try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $graphServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $graphServicePrincipal.Id -AppRoleId $roleId -ErrorAction SilentlyContinue } catch { if ($_.Exception.Message -notlike "*Permission being assigned already exists*") { Write-Host " ⚠️ Failed to assign Graph permission $roleId" -ForegroundColor Yellow } } } Write-Host "✓ Microsoft Graph permissions assigned" -ForegroundColor Green } catch { Write-Host "⚠️ Failed to assign Graph permissions: $($_.Exception.Message)" -ForegroundColor Yellow } # Assign Exchange Online permissions try { $exchangeServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 Exchange Online'" -ErrorAction Stop if ($exchangeServicePrincipal) { $exchangeAppRole = "dc50a0fb-09a3-484d-be87-e023b12c6440" # Exchange.ManageAsApp try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $exchangeServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $exchangeServicePrincipal.Id -AppRoleId $exchangeAppRole -ErrorAction SilentlyContinue Write-Host "✓ Exchange permissions assigned" -ForegroundColor Green } catch { if ($_.Exception.Message -notlike "*Permission being assigned already exists*") { Write-Host "⚠️ Failed to assign Exchange permission" -ForegroundColor Yellow } } } } catch { Write-Host " ⚠️ Failed to assign Exchange permissions: $($_.Exception.Message)" -ForegroundColor Yellow } # Assign SharePoint permissions try { $sharePointServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 SharePoint Online'" -ErrorAction Stop if ($sharePointServicePrincipal) { $sharePointAppRoles = @( "678536fe-1083-478a-9c59-b99265e6b0d3", # Sites.FullControl.All "741f803b-c850-494e-b5df-cde7c675a1ca" # User.ReadWrite.All ) foreach ($roleId in $sharePointAppRoles) { try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sharePointServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $sharePointServicePrincipal.Id -AppRoleId $roleId -ErrorAction SilentlyContinue } catch { if ($_.Exception.Message -notlike "*Permission being assigned already exists*") { Write-Host " ⚠️ Failed to assign SharePoint permission $roleId" -ForegroundColor Yellow } } } Write-Host "✓ SharePoint permissions assigned" -ForegroundColor Green } } catch { Write-Host " ⚠️ Failed to assign SharePoint permissions: $($_.Exception.Message)" -ForegroundColor Yellow } # Assign Exchange Administrator role (required for Exchange Online App-Only Authentication) try { $exchangeAdminRoleId = "29232cdf-9323-42fd-ade2-1d097af3e4de" # Exchange Administrator $roleAssignmentBody = @{ principalId = $servicePrincipal.Id roleDefinitionId = $exchangeAdminRoleId directoryScopeId = "/" } Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" -Body ($roleAssignmentBody | ConvertTo-Json) -ContentType "application/json" Write-Host "✓ Exchange Administrator role assigned" -ForegroundColor Green } catch { if ($_.Exception.Message -like "*already exists*" -or $_.Exception.Message -like "*already assigned*") { Write-Host "✓ Exchange Administrator role already assigned" -ForegroundColor Green } else { Write-Host "⚠️ Failed to assign Exchange Administrator role - manual assignment may be required" -ForegroundColor Yellow } } # Try to assign Global Administrator role (optional but helpful) try { $globalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator $roleAssignmentBody = @{ principalId = $servicePrincipal.Id roleDefinitionId = $globalAdminRoleId directoryScopeId = "/" } Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" -Body ($roleAssignmentBody | ConvertTo-Json) -ContentType "application/json" Write-Host "✓ Global Administrator role assigned" -ForegroundColor Green } catch { Write-Host "⚠️ Failed to assign Global Admin role - manual assignment may be required" -ForegroundColor Yellow } # Wait for role propagation Write-Host "Waiting for role propagation..." -ForegroundColor Yellow Start-Sleep -Seconds 30 Write-Host "✓ App created: $($app.AppId)" -ForegroundColor Green } else { Write-Host "✓ Using existing app: $($app.AppId)" -ForegroundColor Green # Check and add missing permissions to existing app try { $servicePrincipal = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'" -ErrorAction Stop # Get service principals for permission checks $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -ErrorAction Stop $exchangeServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 Exchange Online'" -ErrorAction SilentlyContinue $sharePointServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 SharePoint Online'" -ErrorAction SilentlyContinue # Check existing directory role assignments $existingRoleAssignments = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?`$filter=principalId eq '$($servicePrincipal.Id)'" -ErrorAction SilentlyContinue $exchangeAdminRoleId = "29232cdf-9323-42fd-ade2-1d097af3e4de" # Exchange Administrator $hasExchangeAdminRole = $existingRoleAssignments.value | Where-Object { $_.roleDefinitionId -eq $exchangeAdminRoleId } # Assign Exchange Administrator role if missing if (-not $hasExchangeAdminRole) { try { $roleAssignmentBody = @{ principalId = $servicePrincipal.Id roleDefinitionId = $exchangeAdminRoleId directoryScopeId = "/" } Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" -Body ($roleAssignmentBody | ConvertTo-Json) -ContentType "application/json" -ErrorAction Stop Write-Host " ✓ Added Exchange Administrator role" -ForegroundColor Green } catch { if ($_.Exception.Message -like "*already exists*" -or $_.Exception.Message -like "*already assigned*") { Write-Host " ✓ Exchange Administrator role already assigned" -ForegroundColor Green } else { Write-Host " ⚠️ Failed to assign Exchange Administrator role - manual assignment required" -ForegroundColor Yellow } } } else { Write-Host " ✓ Exchange Administrator role already assigned" -ForegroundColor Green } # Try to assign Global Administrator role if missing (optional but helpful) $globalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10" # Global Administrator $hasGlobalAdminRole = $existingRoleAssignments.value | Where-Object { $_.roleDefinitionId -eq $globalAdminRoleId } if (-not $hasGlobalAdminRole) { try { $roleAssignmentBody = @{ principalId = $servicePrincipal.Id roleDefinitionId = $globalAdminRoleId directoryScopeId = "/" } Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" -Body ($roleAssignmentBody | ConvertTo-Json) -ContentType "application/json" Write-Host " ✓ Added Global Administrator role" -ForegroundColor Green } catch { Write-Host " ⚠️ Failed to assign Global Admin role - manual assignment may be required" -ForegroundColor Yellow } } else { Write-Host " ✓ Global Administrator role already assigned" -ForegroundColor Green } # Check Microsoft Graph permissions $existingGraphAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipal.Id | Where-Object { $_.ResourceId -eq $graphServicePrincipal.Id } $requiredGraphRoles = @( "62a82d76-70ea-41e2-9197-370581804d09", # Group.ReadWrite.All "741f803b-c850-494e-b5df-cde7c675a1ca", # User.ReadWrite.All "bf7b1a76-6e77-406b-b258-bf5c7720e98f", # Group.Create "810c84a8-4a9e-49e6-bf7d-12d183f40d01", # Mail.Read "75359482-378d-4052-8f01-80520e7db3cd", # Files.ReadWrite.All "9492366f-7969-46a4-8d15-ed1a20078fff", # Sites.ReadWrite.All "0c0bf378-bf22-4481-8f81-9e89a9b4960a", # Sites.FullControl.All (correct ID) "b633e1c5-b582-4048-a93e-9f11b44c7e96", # Mail.Send "e2a3a72e-5f79-4c64-b1b1-878b674786c9", # Mail.ReadWrite "40f97065-369a-49f4-947c-6a255697ae91", # MailboxSettings.ReadWrite "ef54d2bf-783f-4e0f-bca1-3210c0444d99" # Calendars.ReadWrite ) foreach ($roleId in $requiredGraphRoles) { $hasRole = $existingGraphAssignments | Where-Object { $_.AppRoleId -eq $roleId } if (-not $hasRole) { try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $graphServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $graphServicePrincipal.Id -AppRoleId $roleId -ErrorAction Stop Write-Host " ✓ Added Graph permission: $roleId" -ForegroundColor Green } catch { Write-Host " ⚠️ Failed to add Graph permission $roleId" -ForegroundColor Yellow } } } # Check Exchange permissions $exchangeServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 Exchange Online'" -ErrorAction SilentlyContinue if ($exchangeServicePrincipal) { $existingExchangeAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipal.Id | Where-Object { $_.ResourceId -eq $exchangeServicePrincipal.Id } $exchangeAppRole = "dc50a0fb-09a3-484d-be87-e023b12c6440" # Exchange.ManageAsApp $hasExchangeRole = $existingExchangeAssignments | Where-Object { $_.AppRoleId -eq $exchangeAppRole } if (-not $hasExchangeRole) { try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $exchangeServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $exchangeServicePrincipal.Id -AppRoleId $exchangeAppRole -ErrorAction Stop Write-Host " ✓ Added Exchange permission: $exchangeAppRole" -ForegroundColor Green } catch { Write-Host " ⚠️ Failed to add Exchange permission $exchangeAppRole" -ForegroundColor Yellow } } } # Check SharePoint permissions if ($sharePointServicePrincipal) { $existingSharePointAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipal.Id | Where-Object { $_.ResourceId -eq $sharePointServicePrincipal.Id } $requiredSharePointRoles = @( "678536fe-1083-478a-9c59-b99265e6b0d3", # Sites.FullControl.All "741f803b-c850-494e-b5df-cde7c675a1ca" # User.ReadWrite.All ) foreach ($roleId in $requiredSharePointRoles) { $hasRole = $existingSharePointAssignments | Where-Object { $_.AppRoleId -eq $roleId } if (-not $hasRole) { try { New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sharePointServicePrincipal.Id -PrincipalId $servicePrincipal.Id -ResourceId $sharePointServicePrincipal.Id -AppRoleId $roleId -ErrorAction Stop Write-Host " ✓ Added SharePoint permission: $roleId" -ForegroundColor Green } catch { Write-Host " ⚠️ Failed to add SharePoint permission $roleId" -ForegroundColor Yellow } } } } Write-Host "✓ Permissions configured" -ForegroundColor Green } catch { Write-Host " ⚠️ Could not check/update permissions: $($_.Exception.Message)" -ForegroundColor Yellow } } # Upload certificate to app with duplicate detection $fullApp = Get-MgApplication -ApplicationId $app.Id -Property "Id,KeyCredentials" -ErrorAction Stop $displayName = "PowerShell Certificate - $($cert.Thumbprint)" $existingKeys = @($fullApp.KeyCredentials) # Duplicate detection using multiple methods $certAlreadyRegistered = $false $certDerBase64 = [Convert]::ToBase64String($cert.RawData) # Compute thumbprint for comparison $hexThumb = ($cert.Thumbprint -replace ' ','') $thumbBytes = @() for ($i = 0; $i -lt $hexThumb.Length; $i += 2) { $thumbBytes += [Convert]::ToByte($hexThumb.Substring($i,2),16) } $thumbBase64 = [Convert]::ToBase64String($thumbBytes) foreach ($keyCred in $existingKeys) { $customIdBase64 = $null if ($keyCred.CustomKeyIdentifier) { $customIdBase64 = [Convert]::ToBase64String($keyCred.CustomKeyIdentifier) } $typeMatches = ($keyCred.Type -eq 'AsymmetricX509Cert') -or -not $keyCred.Type $keyMatches = $false if ($keyCred.Key) { try { $keyBase64 = [Convert]::ToBase64String($keyCred.Key) if ($keyBase64 -and ($keyBase64 -eq $certDerBase64)) { $keyMatches = $true } } catch {} } $displayMatches = ($keyCred.DisplayName -and ($keyCred.DisplayName -eq $displayName)) if ($typeMatches -and ( ($customIdBase64 -and ($customIdBase64 -eq $thumbBase64)) -or $keyMatches -or $displayMatches )) { if ($keyCred.EndDateTime -and $keyCred.EndDateTime -lt (Get-Date)) { Write-Host "⚠️ Certificate registration expired - will re-upload" -ForegroundColor Yellow } else { $certAlreadyRegistered = $true break } } } if (-not $certAlreadyRegistered) { Write-Host "Uploading certificate to app..." -ForegroundColor Yellow $newKey = @{ type = "AsymmetricX509Cert" usage = "Verify" key = $cert.RawData displayName = $displayName } $allKeys = @($existingKeys) + @($newKey) Update-MgApplication -ApplicationId $fullApp.Id -BodyParameter @{ keyCredentials = $allKeys } Write-Host "✓ Certificate uploaded, waiting for propagation..." -ForegroundColor Green Start-Sleep -Seconds 45 } else { Write-Host "✓ Certificate already registered" -ForegroundColor Green } # Step 4: Test certificate auth $context = Get-MgContext $tenantId = $context.TenantId Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null # Use thumbprint method like the original script - suppress all output Connect-MgGraph -ClientId $app.AppId -TenantId $tenantId -CertificateThumbprint $cert.Thumbprint -NoWelcome -ContextScope Process -ErrorAction Stop | Out-Null 2>&1 $newContext = Get-MgContext if ($newContext.AuthType -ne "AppOnly") { throw "Certificate authentication failed - AuthType: $($newContext.AuthType)" } # Quick API test $null = Get-MgUser -Top 1 -ErrorAction Stop Write-Host "✓ Certificate authentication ready" -ForegroundColor Green # Return certificate and connection info # Export certificate with private key as base64 for PnP PowerShell $tempPassword = ConvertTo-SecureString -String "TempPassword123!" -Force -AsPlainText $tempPfxPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "temp_cert_$($cert.Thumbprint).pfx") try { Export-PfxCertificate -Cert $cert -FilePath $tempPfxPath -Password $tempPassword -Force | Out-Null $pfxBytes = [System.IO.File]::ReadAllBytes($tempPfxPath) $certBase64 = [Convert]::ToBase64String($pfxBytes) Remove-Item $tempPfxPath -Force -ErrorAction SilentlyContinue } catch { Write-Host " ⚠️ Could not export certificate with private key, using public key only" -ForegroundColor Yellow $certBase64 = [Convert]::ToBase64String($cert.RawData) $tempPassword = $null } return @{ Certificate = $cert AppId = $app.AppId TenantId = $tenantId Thumbprint = $cert.Thumbprint CertificateBase64 = $certBase64 CertificatePassword = $tempPassword } } catch { throw "Certificate setup failed: $($_.Exception.Message)" } } function Show-CertificateAuthHelp { <# .SYNOPSIS Shows help and examples for using certificate authentication with Microsoft 365 services .DESCRIPTION Displays connection examples for all major Microsoft 365 PowerShell modules using certificate authentication #> Write-Host "`nCertificate Authentication Help" -ForegroundColor Cyan Write-Host "===============================" -ForegroundColor Cyan Write-Host "Microsoft 365 Certificate Authentication Module`n" -ForegroundColor Gray Write-Host "USAGE:" -ForegroundColor Yellow Write-Host "`$auth = Get-CertificateForAuth" -ForegroundColor White Write-Host "Returns: Certificate, AppId, TenantId, Thumbprint, CertificateBase64, CertificatePassword" -ForegroundColor Gray Write-Host "Note: Module automatically connects to Microsoft Graph with required permissions`n" -ForegroundColor Gray Write-Host "CONNECTION EXAMPLES:" -ForegroundColor Yellow Write-Host "`nExchange Online:" -ForegroundColor Cyan Write-Host "Connect-ExchangeOnline -AppId `$auth.AppId -CertificateThumbprint `$auth.Thumbprint -Organization 'tenant.onmicrosoft.com'" -ForegroundColor White Write-Host "`nMicrosoft Graph:" -ForegroundColor Cyan Write-Host "Connect-MgGraph -ClientId `$auth.AppId -TenantId `$auth.TenantId -CertificateThumbprint `$auth.Thumbprint" -ForegroundColor White Write-Host "`nSharePoint Online (PnP):" -ForegroundColor Cyan Write-Host "Connect-PnPOnline -Url 'https://tenant.sharepoint.com' -ClientId `$auth.AppId -Tenant 'tenant.onmicrosoft.com' -CertificateBase64Encoded `$auth.CertificateBase64 -CertificatePassword `$auth.CertificatePassword" -ForegroundColor White Write-Host "`nMicrosoft Teams:" -ForegroundColor Cyan Write-Host "Connect-MicrosoftTeams -ApplicationId `$auth.AppId -TenantId `$auth.TenantId -CertificateThumbprint `$auth.Thumbprint" -ForegroundColor White Write-Host "`nAzure PowerShell:" -ForegroundColor Cyan Write-Host "Connect-AzAccount -ApplicationId `$auth.AppId -TenantId `$auth.TenantId -CertificateThumbprint `$auth.Thumbprint -ServicePrincipal" -ForegroundColor White Write-Host "`nSecurity & Compliance:" -ForegroundColor Cyan Write-Host "Connect-IPPSSession -AppId `$auth.AppId -CertificateThumbprint `$auth.Thumbprint -Organization 'tenant.onmicrosoft.com'" -ForegroundColor White Write-Host "`nPERMISSIONS INCLUDED:" -ForegroundColor Yellow Write-Host "- Microsoft Graph: User, Group, Mail, Files, Sites, Directory, Reports" -ForegroundColor White Write-Host "- Exchange Online: ManageAsApp (full mailbox access)" -ForegroundColor White Write-Host "- SharePoint: FullControl and User permissions" -ForegroundColor White Write-Host "- Exchange Administrator role (required for Exchange Online App-Only auth)" -ForegroundColor White Write-Host "- Global Administrator role (optional, enhances compatibility)" -ForegroundColor White Write-Host "`nCERTIFICATE MANAGEMENT:" -ForegroundColor Yellow Write-Host "- Automatically creates certificates if none exist" -ForegroundColor White Write-Host "- Validates expiry dates and private key access" -ForegroundColor White Write-Host "- Cleans up expired certificates automatically" -ForegroundColor White Write-Host "- Uploads certificates to app registration" -ForegroundColor White Write-Host "- Handles duplicate detection and re-registration" -ForegroundColor White } Export-ModuleMember -Function Get-CertificateForAuth, Show-CertificateAuthHelp |