Public/Get-TntIntuneAppleCertificateReport.ps1
|
function Get-TntIntuneAppleCertificateReport { <# .SYNOPSIS Monitors Apple Device Enrollment Program (DEP) tokens and Apple Push Notification Service (APNS) certificates expiration in Microsoft Intune. .DESCRIPTION This function connects to Microsoft Graph using an app registration and generates detailed reports about Apple DEP tokens, APNS certificates, and VPP (Volume Purchase Program) tokens. It identifies certificates and tokens nearing expiration to prevent service disruptions for iOS/iPadOS and macOS device management. .PARAMETER TenantId The Azure AD Tenant ID (GUID) to connect to. .PARAMETER ClientId The Application (Client) ID of the app registration created for security reporting. .PARAMETER ClientSecret The client secret for the app registration. Use this for automated scenarios. .PARAMETER CertificateThumbprint The thumbprint of the certificate to use for authentication instead of client secret. .PARAMETER ThresholdInDays Number of days before expiration to flag certificates/tokens as expiring. Defaults to 30 days. .EXAMPLE Get-TntIntuneAppleCertificateReport -TenantId $tenantId -ClientId $clientId -ClientSecret $secret Monitors all Apple certificates and tokens, showing those expiring within 30 days. .EXAMPLE Get-TntIntuneAppleCertificateReport -TenantId $tenantId -ClientId $clientId -ClientSecret $secret -ThresholdInDays 60 Monitors certificates with 60-day threshold. .OUTPUTS System.Management.Automation.PSCustomObject Returns a report object containing: - Summary: Counts of items by status and type - AllItems: Detailed list of all certificates and tokens with status and risk level .NOTES Author: Tom de Leeuw Website: https://systom.dev Module: TenantReports Required Permissions: - DeviceManagementConfiguration.Read.All (Application) - DeviceManagementManagedDevices.Read.All (Application) - DeviceManagementApps.Read.All (Application) .LINK https://systom.dev #> [CmdletBinding(DefaultParameterSetName = 'ClientSecret')] [OutputType([System.Management.Automation.PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidateNotNullOrEmpty()] [Alias('Tenant')] [string]$TenantId, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidatePattern('^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$')] [Alias('ApplicationId')] [string]$ClientId, [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Secret', 'ApplicationSecret')] [SecureString]$ClientSecret, [Parameter(Mandatory = $true, ParameterSetName = 'Certificate', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Thumbprint')] [string]$CertificateThumbprint, [Parameter(Mandatory = $true, ParameterSetName = 'Interactive')] [switch]$Interactive, [Parameter()] [ValidateRange(1, 365)] [int]$ThresholdInDays = 30 ) begin { # Define certificate/token types for risk assessment $CertificateTypeRisk = @{ 'APNS' = @{ RiskLevel = 'Critical' Impact = 'All iOS/iPadOS/macOS devices will lose management capabilities' Renewability = 'Must be renewed with same Apple ID' } 'DEP' = @{ RiskLevel = 'High' Impact = 'New device enrollment via DEP/ABM/ASM will fail' Renewability = 'Can be renewed with different Apple ID' } 'VPP' = @{ RiskLevel = 'Medium' Impact = 'App deployment and license management will be affected' Renewability = 'Can be renewed with same Apple ID' } } Write-Information 'STARTED : Apple certificate and token expirations...' -InformationAction Continue } process { try { # Establish connection $ConnectionParams = Get-ConnectionParameters -BoundParameters $PSBoundParameters $ConnectionInfo = Connect-TntGraphSession @ConnectionParams # Initialize collections for certificate/token data $AllItems = [System.Collections.Generic.List[PSObject]]::new() $Errors = [System.Collections.Generic.List[PSObject]]::new() $Now = [DateTime]::Now # Get APNS Certificate Write-Verbose 'Retrieving Apple Push Notification Certificate...' try { $ApnsCertUri = 'https://graph.microsoft.com/beta/deviceManagement/applePushNotificationCertificate' $ApnsCert = Invoke-MgGraphRequest -Uri $ApnsCertUri -Method GET -ErrorAction Stop if ($ApnsCert) { $DaysUntilExpiry = ([DateTime]$ApnsCert.ExpirationDateTime - $Now).Days $IsExpired = $DaysUntilExpiry -lt 0 $IsExpiring = $DaysUntilExpiry -le $ThresholdInDays -and $DaysUntilExpiry -ge 0 $ApnsItem = [PSCustomObject]@{ Type = 'APNS' Name = 'Apple Push Notification Certificate' AppleIdentifier = $ApnsCert.AppleIdentifier ?? 'Not available' ExpirationDateTime = $ApnsCert.ExpirationDateTime DaysUntilExpiry = $DaysUntilExpiry IsExpired = $IsExpired IsExpiring = $IsExpiring RiskLevel = $CertificateTypeRisk['APNS'].RiskLevel Impact = $CertificateTypeRisk['APNS'].Impact RenewalGuidance = $CertificateTypeRisk['APNS'].Renewability LastModifiedDateTime = $ApnsCert.LastModifiedDateTime CertificateSerialNumber = $ApnsCert.CertificateSerialNumber ?? 'Not available' Status = if ($IsExpired) { 'Expired' } elseif ($IsExpiring) { 'ExpiringSoon' } else { 'Valid' } } $AllItems.Add($ApnsItem) Write-Verbose "APNS Certificate: $(if ($ApnsCert.ExpirationDateTime) { "Expires $($ApnsCert.ExpirationDateTime) ($($DaysUntilExpiry) days)" } else { 'No expiration data available' })" } else { Write-Verbose 'No APNS Certificates found in the tenant' } } catch { # This endpoint throws a terminating error if no cert is found which messes up Invoke-SecurityReport; suppress error if 'NotFound' in error msg. if ($_.Exception.Message -match 'NotFound') { Write-Verbose 'No APNS Certificates found in the tenant' } else { $ErrorMsg = "Failed to retrieve APNS certificate: $($_.Exception.Message)" Write-Warning $ErrorMsg $Errors.Add([PSCustomObject]@{ Type = 'APNS' Error = $ErrorMsg }) } } # Get DEP Tokens Write-Verbose 'Retrieving Apple DEP (Device Enrollment Program) tokens...' try { # DEP tokens are in beta endpoint $DepTokensUri = 'https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings' $DepTokensResponse = Invoke-MgGraphRequest -Uri $DepTokensUri -Method GET -ErrorAction Stop if ($DepTokensResponse.value -and $DepTokensResponse.value.Count -gt 0) { foreach ($DepToken in $DepTokensResponse.value) { if ($DepToken.tokenExpirationDateTime) { $DaysUntilExpiry = ([DateTime]$DepToken.tokenExpirationDateTime - $Now).Days $IsExpired = $DaysUntilExpiry -lt 0 $IsExpiring = $DaysUntilExpiry -le $ThresholdInDays -and $DaysUntilExpiry -ge 0 $DepItem = [PSCustomObject]@{ Type = 'DEP' Name = "$($DepToken.tokenName ?? 'DEP Token') ($($DepToken.tokenType ?? 'Unknown Type'))" AppleIdentifier = $DepToken.appleIdentifier ?? 'Not available' ExpirationDateTime = $DepToken.tokenExpirationDateTime DaysUntilExpiry = $DaysUntilExpiry IsExpired = $IsExpired IsExpiring = $IsExpiring RiskLevel = $CertificateTypeRisk['DEP'].RiskLevel Impact = $CertificateTypeRisk['DEP'].Impact RenewalGuidance = $CertificateTypeRisk['DEP'].Renewability LastModifiedDateTime = $DepToken.lastModifiedDateTime LastSuccessfulSync = $DepToken.lastSuccessfulSyncDateTime SyncedDeviceCount = $DepToken.syncedDeviceCount ?? 0 TokenId = $DepToken.id Status = if ($IsExpired) { 'Expired' } elseif ($IsExpiring) { 'ExpiringSoon' } else { 'Valid' } } $AllItems.Add($DepItem) } } Write-Verbose "Found $($DepTokensResponse.value.Count) DEP token(s)" } else { Write-Verbose 'No DEP tokens found in the tenant' } } catch { $ErrorMsg = "Failed to retrieve DEP tokens: $($_.Exception.Message)" Write-Warning $ErrorMsg $Errors.Add([PSCustomObject]@{ Type = 'DEP' Error = $ErrorMsg }) } # Get VPP Tokens Write-Verbose 'Retrieving Apple VPP (Volume Purchase Program) tokens...' try { # VPP tokens are in beta endpoint $VppTokensUri = 'https://graph.microsoft.com/beta/deviceAppManagement/vppTokens' $VppTokensResponse = Invoke-MgGraphRequest -Uri $VppTokensUri -Method GET -ErrorAction Stop if ($VppTokensResponse.value -and $VppTokensResponse.value.Count -gt 0) { foreach ($VppToken in $VppTokensResponse.value) { if ($VppToken.expirationDateTime) { $DaysUntilExpiry = ([DateTime]$VppToken.expirationDateTime - $Now).Days $IsExpired = $DaysUntilExpiry -lt 0 $IsExpiring = $DaysUntilExpiry -le $ThresholdInDays -and $DaysUntilExpiry -ge 0 $VppItem = [PSCustomObject]@{ Type = 'VPP' Name = "$($VppToken.organizationName ?? 'VPP Token') ($($VppToken.vppTokenAccountType ?? 'Unknown Account Type'))" AppleIdentifier = $VppToken.appleId ?? 'Not available' ExpirationDateTime = $VppToken.expirationDateTime DaysUntilExpiry = $DaysUntilExpiry IsExpired = $IsExpired IsExpiring = $IsExpiring RiskLevel = $CertificateTypeRisk['VPP'].RiskLevel Impact = $CertificateTypeRisk['VPP'].Impact RenewalGuidance = $CertificateTypeRisk['VPP'].Renewability LastModifiedDateTime = $VppToken.lastModifiedDateTime LastSyncDateTime = $VppToken.lastSyncDateTime TokenId = $VppToken.id CountryOrRegion = $VppToken.countryOrRegion ?? 'Not specified' Status = if ($IsExpired) { 'Expired' } elseif ($IsExpiring) { 'ExpiringSoon' } else { 'Valid' } } $AllItems.Add($VppItem) } } Write-Verbose "Found $($VppTokensResponse.value.Count) VPP token(s)" } else { Write-Verbose 'No VPP tokens found in the tenant' } } catch { $ErrorMsg = "Failed to retrieve VPP tokens: $($_.Exception.Message)" Write-Warning $ErrorMsg $Errors.Add([PSCustomObject]@{ Type = 'VPP' Error = $ErrorMsg }) } # Throw terminating error if no cert/token is found at all if ($AllItems.Count -eq 0) { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new('Get-TntIntuneAppleCertificateReport failed: No Apple certificates/tokens found.'), 'GetTntIntuneAppleCertificateReportError', [System.Management.Automation.ErrorCategory]::OperationStopped, $TenantId ) $PSCmdlet.ThrowTerminatingError($errorRecord) } # Generate summary statistics $ExpiringCount = $AllItems.Where({ $_.IsExpiring }).Count $Summary = [PSCustomObject]@{ TenantId = $TenantId ReportGeneratedDate = $Now TotalItems = $AllItems.Count ExpiringItems = $ExpiringCount ExpiredItems = $AllItems.Where({ $_.IsExpired }).Count ValidItems = $AllItems.Where({ -not $_.IsExpired -and -not $_.IsExpiring }).Count APNSCertificates = $AllItems.Where({ $_.Type -eq 'APNS' }).Count DEPTokens = $AllItems.Where({ $_.Type -eq 'DEP' }).Count VPPTokens = $AllItems.Where({ $_.Type -eq 'VPP' }).Count } Write-Information "FINISHED : Apple certificate expirations - $($AllItems.Count) items checked ($ExpiringCount expiring soon)" -InformationAction Continue [PSCustomObject]@{ Summary = $Summary AllItems = $AllItems | Sort-Object Type, DaysUntilExpiry } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Get-TntIntuneAppleCertificateReport failed: $($_.Exception.Message)", $_.Exception), 'GetTntIntuneAppleCertificateReportError', [System.Management.Automation.ErrorCategory]::OperationStopped, $TenantId ) $PSCmdlet.ThrowTerminatingError($errorRecord) } finally { if ($ConnectionInfo.ShouldDisconnect) { Disconnect-TntGraphSession -ConnectionState $ConnectionInfo } } } } |