diff --git a/powershell/Maester.Format.ps1xml b/powershell/Maester.Format.ps1xml index 57eb7dd89..dfeeddb68 100644 --- a/powershell/Maester.Format.ps1xml +++ b/powershell/Maester.Format.ps1xml @@ -103,6 +103,28 @@ } + + + + $null -ne $_.GitHub + + + + + if ($_.GitHub) { + $connectedText = if ($_.GitHub.Connected) { 'Connected' } else { 'Not connected' } + "$connectedText`n" + + "Organization: $($_.GitHub.Organization)`n" + + "Token Login: $($_.GitHub.TokenLogin)`n" + + "API Base URI: $($_.GitHub.ApiBaseUri)`n" + + "Role: $($_.GitHub.Role)`n" + + "Role State: $($_.GitHub.RoleState)`n" + + "Administration Permission Verified: $($_.GitHub.AdministrationPermissionVerified)`n" + } else { + '' + } + + diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index a8142ff5c..2a9110241 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -57,9 +57,9 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Add-MtMaesterAppFederatedCredential', 'Add-MtTestResultDetail', 'Clear-MtDnsCache', 'Clear-MtExoCache', - 'Clear-MtGraphCache', 'Compare-MtJsonObject', 'Compare-MtTestResult', 'Connect-Maester', 'Convert-MtResultsToFlatObject', + 'Clear-MtGraphCache', 'Compare-MtJsonObject', 'Compare-MtTestResult', 'Connect-Maester', 'Connect-MtGitHub', 'Convert-MtResultsToFlatObject', 'ConvertFrom-MailAuthenticationRecordDkim', 'ConvertFrom-MailAuthenticationRecordDmarc', - 'ConvertFrom-MailAuthenticationRecordMx', 'ConvertFrom-MailAuthenticationRecordSpf', 'Disconnect-Maester', + 'ConvertFrom-MailAuthenticationRecordMx', 'ConvertFrom-MailAuthenticationRecordSpf', 'Disconnect-Maester', 'Disconnect-MtGitHub', 'Get-MailAuthenticationRecord', 'Get-MtAdminPortalUrl', 'Get-MtAuthenticationMethodPolicyConfig', 'Get-MtAzureManagementGroup', 'Get-MtConditionalAccessPolicy', 'Get-MtExo', 'Get-MtExoThreatPolicyMalware', 'Get-MtGraphScope', 'Get-MtGroupMember', 'Get-MtHtmlReport', 'Get-MtLicenseInformation', 'Get-MtMaesterApp', 'Get-MtRole', diff --git a/powershell/Maester.psm1 b/powershell/Maester.psm1 index 1cb989e9f..3dd22854f 100644 --- a/powershell/Maester.psm1 +++ b/powershell/Maester.psm1 @@ -25,6 +25,7 @@ $__MtSession = @{ DataverseApiBase = $null # Resolved Dataverse OData API base URL (e.g. https://org123.api.crm.dynamics.com/api/data/v9.2) DataverseResourceUrl = $null # Dataverse resource URL for token acquisition (e.g. https://org123.crm.dynamics.com) DataverseEnvironmentId = $null # Environment identifier for display (e.g. org123.crm.dynamics.com) + GitHubCache = @{} # Per-session REST response cache; cleared each Invoke-Maester run } New-Variable -Name __MtSession -Value $__MtSession -Scope Script -Force diff --git a/powershell/internal/Clear-ModuleVariable.ps1 b/powershell/internal/Clear-ModuleVariable.ps1 index 17879e660..bb456aabd 100644 --- a/powershell/internal/Clear-ModuleVariable.ps1 +++ b/powershell/internal/Clear-ModuleVariable.ps1 @@ -21,5 +21,8 @@ Clear-MtExoCache $__MtSession.AIAgentInfo = $null $__MtSession.AzureDevOpsConnection = $null + # Do not clear GitHubConnection or GitHubAuthHeader — the user calls Connect-MtGitHub before + # Invoke-Maester; the session must persist across runs. Only the per-run cache is reset. + $__MtSession.GitHubCache = @{} # $__MtSession.Connections = @() # Do not clear connections as they are used to track the connection state. This module variable should only be set by Connect-Maester and Disconnect-Maester. } diff --git a/powershell/internal/Get-MtGitHubErrorMessage.ps1 b/powershell/internal/Get-MtGitHubErrorMessage.ps1 new file mode 100644 index 000000000..6ce0b5659 --- /dev/null +++ b/powershell/internal/Get-MtGitHubErrorMessage.ps1 @@ -0,0 +1,19 @@ +function Get-MtGitHubErrorMessage { + param([Parameter(Mandatory)] $ErrorRecord) + if (-not [string]::IsNullOrEmpty($ErrorRecord.ErrorDetails.Message)) { + try { + $parsed = $ErrorRecord.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop + if ($parsed.PSObject.Properties.Name -contains 'message' -and + -not [string]::IsNullOrEmpty($parsed.message)) { + return $parsed.message + } + } catch { + Write-Debug "Get-MtGitHubErrorMessage: ErrorDetails.Message is not JSON, returning raw string." + } + return $ErrorRecord.ErrorDetails.Message + } + if (-not [string]::IsNullOrEmpty($ErrorRecord.Exception.Message)) { + return $ErrorRecord.Exception.Message + } + return ($ErrorRecord | Out-String) +} diff --git a/powershell/internal/Get-MtGitHubErrorStatusCode.ps1 b/powershell/internal/Get-MtGitHubErrorStatusCode.ps1 new file mode 100644 index 000000000..046d6c590 --- /dev/null +++ b/powershell/internal/Get-MtGitHubErrorStatusCode.ps1 @@ -0,0 +1,11 @@ +function Get-MtGitHubErrorStatusCode { + param([Parameter(Mandatory)] $ErrorRecord) + try { + if ($ErrorRecord.Exception.Response -and $ErrorRecord.Exception.Response.StatusCode) { + return [int]$ErrorRecord.Exception.Response.StatusCode + } + } catch { + Write-Debug "Get-MtGitHubErrorStatusCode: $($_.Exception.Message)" + } + return $null +} diff --git a/powershell/internal/Get-MtGitHubRateLimitMessage.ps1 b/powershell/internal/Get-MtGitHubRateLimitMessage.ps1 new file mode 100644 index 000000000..f55609689 --- /dev/null +++ b/powershell/internal/Get-MtGitHubRateLimitMessage.ps1 @@ -0,0 +1,75 @@ +function Get-MtGitHubRateLimitMessage { + <# + .SYNOPSIS + Internal: Returns a GitHub rate-limit message for an ErrorRecord, or $null when the + error is not a rate-limit response. + + .DESCRIPTION + Mirrors the rate-limit detection in Invoke-MtGitHubRequest so that bootstrap callers + (Connect-MtGitHub) can distinguish HTTP 403/429 caused by rate limiting from + permission, token, or org-access failures. + + Returns: + - "GitHub API rate limit encountered (HTTP ). Resets at: