public/Add-MtTestResultDetail.ps1
| 
                                <# .SYNOPSIS Add detailed information about a test so that it can be displayed in the test results report. .DESCRIPTION This function is used to add detailed information about a test so that it can be displayed in the test results report. The description and result support markdown format. If the calling script/cmdlet has a markdown file with the same name as the script/cmdlet, it will be used to populate the description and result fields. A good example is the markdown for the Test-MtCaEmergencyAccessExists cmdlet: - https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCaEmergencyAccessExists.md - https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCaEmergencyAccessExists.ps1 The markdown file can include a separator `<!--- Results --->` to split the description and result sections. This allows for the overview and detailed information to be displayed separately in the Test results. .EXAMPLE Add-MtTestResultDetail -Description 'Test description' -Result 'Test result' This example adds detailed information about a test with a brief description and the result of the test. ```powershell $policiesWithoutEmergency = $policies | Where-Object { $CheckId -notin $_.conditions.users.excludeUsers -and $CheckId -notin $_.conditions.users.excludeGroups } Add-MtTestResultDetail -GraphObjects $policiesWithoutEmergency -GraphObjectType ConditionalAccess ``` This example shows how to use the Add-MtTestResultDetail function to add rich markdown content to the test results with deep links to the admin portal. .LINK https://maester.dev/docs/commands/Add-MtTestResultDetail #> function Add-MtTestResultDetail { [CmdletBinding()] param( # Brief description of what this test is checking. # Markdown is supported. [Parameter(Mandatory = $false)] [string] $Description, # Detailed information of the test result to provide additional context to the user. # This can be a summary of the items that caused the test to fail (e.g. list of user names, conditional access policies, etc.). # Markdown is supported. # If the test result contains a placeholder %TestResult%, it will be replaced with the values from the $GraphResult [Parameter(Mandatory = $false)] [string] $Result, # Collection of Graph objects to display in the test results report. # This will be inserted into the contents of Result parameter if the result contains a placeholder %TestResult%. [Object[]] $GraphObjects, # The type of graph object, this will be used to show the right deep-link to the test results report. [ValidateSet('AuthenticationMethod', 'AuthorizationPolicy', 'ConditionalAccess', 'ConsentPolicy', 'Devices', 'Domains', 'Groups', 'IdentityProtection', 'Users', 'UserRole' )] [string] $GraphObjectType, # Pester test name # Use the test name from the Pester context by default [Parameter(Mandatory = $false)] [string] $TestName = $____Pester.CurrentTest.ExpandedName, # A custom title for the test in the format of "MT.XXXX: <TestName>. Used in data driven tests like Entra recommendations 1024" [Parameter(Mandatory = $false)] [string] $TestTitle, # Common reasons for why the test was skipped. [Parameter(Mandatory = $false)] [ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotConnectedGraph', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance', 'NotConnectedTeams', 'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', 'NotLicensedExoDlp', "LicensedEntraIDPremium", 'NotSupported', 'Custom', 'NotLicensedMdo', 'NotLicensedMdoP2', 'NotLicensedMdoP1', 'NotLicensedAdvAudit', 'NotLicensedEop', 'Error', 'NotSupportedAppPermission', 'LimitedPermissions', 'NotLicensedDefenderXDR' )] [string] $SkippedBecause, # A custom reason for why the test was skipped. Requires `-SkippedBecause Custom`. [Parameter(Mandatory = $false)] [string] $SkippedCustomReason, # The error object that caused the test to be skipped. [Parameter(Mandatory = $false)] $SkippedError, # Severity level of the test result. Leave empty if no Severity is defined yet. [Parameter(Mandatory = $false)] [ValidateSet('Critical', 'High', 'Medium', 'Low', 'Info', '')] [string] $Severity ) $hasGraphResults = $GraphObjects -and $GraphObjectType if ($SkippedBecause) { if ($SkippedBecause -eq 'Custom') { if ([string]::IsNullOrEmpty($SkippedCustomReason)) { throw "SkippedBecause is set to 'Custom' but no SkippedCustomReason was provided." } $SkippedReason = $SkippedCustomReason } elseif ($SkippedBecause -eq 'Error') { $SkippedReason = "An error occurred while running the test. ⚠️" if ($SkippedError) { $SkippedReason += "`n`n" + '```' + "`n`n" + ($SkippedError | Out-String) + "`n`n" + '```' + "`n`n" } if ([string]::IsNullOrEmpty($Result)) { $Result = "Error. $SkippedReason" } } else { $SkippedReason = Get-MtSkippedReason $SkippedBecause } } if ([string]::IsNullOrEmpty($Result)) { $Result = "Skipped. $SkippedReason" } if ([string]::IsNullOrEmpty($Description)) { # Check if a markdown file exists for the cmdlet and parse the content try { $cmdletPath = $MyInvocation.PSCommandPath $markdownPath = $cmdletPath -replace '.ps1', '.md' if (Test-Path $markdownPath) { # Read the content and split it into description and result with "<!--- Results --->" as the separator $content = Get-Content $markdownPath -Raw -ErrorAction Stop $splitContent = $content -split "<!--- Results --->" $mdDescription = $splitContent[0] $mdResult = $splitContent[1] if (![string]::IsNullOrEmpty($Result)) { # If a result was provided in the parameter insert it into the markdown content try { if ($mdResult -match "%TestResult%") { $mdResult = $mdResult -replace "%TestResult%", $Result } else { $mdResult = $Result } } catch { Write-Warning "Failed to process markdown result template: $($_.Exception.Message)" $mdResult = $Result } # End of try-catch for result replacement in the markdown template. } $Description = $mdDescription $Result = $mdResult } } catch { Write-Warning "Failed to read markdown file '$markdownPath': $($_.Exception.Message)" # Continue without markdown content } # End of try-catch for markdown file reading } if ($hasGraphResults) { try { $graphResultMarkdown = Get-GraphObjectMarkdown -GraphObjects $GraphObjects -GraphObjectType $GraphObjectType $Result = $Result -replace "%TestResult%", $graphResultMarkdown } catch { Write-Warning "Failed to generate graph object markdown: $($_.Exception.Message)" # Continue with original result without graph object markdown } } if ([string]::IsNullOrEmpty($TestTitle)) { # If no test title is provided, use the test name $TestTitle = $____Pester.CurrentTest.ExpandedName } if ([string]::IsNullOrEmpty($Severity)) { # Check if the test has a severity tag using the internal helper function try { $Severity = Get-MtPesterTagValue -TagName 'Severity' } catch { Write-Warning "Failed to get severity tag: $($_.Exception.Message)" $Severity = '' } } try { $Service = Get-MtPesterTagValue -TagName 'Service' } catch { Write-Warning "Failed to get service tag: $($_.Exception.Message)" $Service = '' } $testInfo = @{ TestTitle = $TestTitle TestDescription = $Description TestResult = $Result TestSkipped = $SkippedBecause SkippedReason = $SkippedReason Severity = $Severity Service = $Service } Write-MtProgress -Activity "Running tests" -Status $testName Write-Verbose "Adding test result detail for $testName" # Write-Verbose "Description: $Description" # Makes it VERY verbose Write-Verbose "Result: $Result" if ($__MtSession -and $__MtSession.TestResultDetail) { if (![string]::IsNullOrEmpty($testName)) { # Only set if we are running in the context of Maester # Check if the test name is already in the session and display a warning $__MtSession.TestResultDetail[$testName] = $testInfo } } if ($SkippedBecause) { #This needs to be set at the end. Set-ItResult -Skipped -Because $SkippedReason } }  |