public/Test-CmHealth.ps1

#requires -RunAsAdministrator
<#
.SYNOPSIS
    Validate MECM/ConfigMgr site systems and configuration.
.DESCRIPTION
    Validate MECM/ConfigMgr site systems operational health status, and recommended configuration.
.PARAMETER SiteCode
    ConfigMgr 3-character alphanumeric site code.
.PARAMETER Database
    Name of site SQL database.
.PARAMETER SiteServer
    NetBIOS or FQDN of site server (primary, CAS, secondary). Default is localhost
.PARAMETER SqlInstance
    NetBIOS or FQDN of site database SQL instance. Default is localhost
.PARAMETER TestingScope
    Scope of tests to execute: All (default), Host, AD, SQL, CM, WSUS, Select
    The Select option displays a gridview to select the individual tests to perform
.PARAMETER ConfigFile
    Path to cmhealth.json (create or import). If not found, it will attempt to create a new
    one in the specified path. The default path is the user TEMP folder.
.PARAMETER Remediate
    Attempt remediation when possible
.PARAMETER Source
    Alternate source path for WinSXS referencing. Used only for Test-HostServerFeatures
    Default is C:\Windows\WinSxS
.PARAMETER DaysBack
    Number of days to go back for checking status messages, errors, warnings, etc. Default is 7
.PARAMETER Credential
    PS Credential object for authenticating under alternate context
.PARAMETER LogFile
    Path and name of log file. Default is $env:TEMP\cmhealth_yyyy-mm-dd.log
.PARAMETER NoVersionCheck
    Skip checking for newer module version (default is to attempt a version check)
.PARAMETER AllServers
    Run tests on all site systems within the current ConfigMgr site database
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01"
    Runs all tests on the local machine
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" -AllServers
    Runs all tests on all site systems
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" -SiteServer "CM01" -SqlInstance "CM01" -TestingScope "ALL"
    Runs all tests
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" -SiteServer "CM01" -SqlInstance "CM01" -TestingScope "Host"
    Runs only the site server host tests
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" -SiteServer "CM01" -SqlInstance "CM01" -TestingScope "Host" -Remediate -Credential $cred
    Runs only the site server host tests and attempts to remediate identified deficiences using alternate user credentials
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" -SiteServer "CM01" -SqlInstance "CM01" -TestingScope "Host" -Remediate -Source "\\server3\sources\ws2019\WinSxS"
    Runs only the site server host tests and attempts to remediate identified deficiences with WinSXS source path provided
.EXAMPLE
    $failed = Test-CmHealth -SiteCode "P01" -Database "CM_P01" | Where-Object Status -eq 'Fail'
    Runs all tests and only returns those which failed
.EXAMPLE
    Test-CmHealth -SiteCode "P01" -Database "CM_P01" | Select-Object TestName,Status,Message | Where-Object Status -eq 'Fail'
    Display summary of failed tests
.EXAMPLE
    $results = Test-CmHealth -SiteCode "P01" -Database "CM_P01" | Where-Object Status -eq 'Fail'; $results | Select TestData
    Display test output from failed tests
.EXAMPLE
    $results = Test-CmHealth -SiteCode "P01" -Database "CM_P01" -TestScope Previous
    Run the same set of tests as the previous session (each run saves list of test names)
.LINK
    https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Test-CmHealth.md
.NOTES
    Thank you!
#>


function Test-CmHealth {
    [CmdletBinding()]
    [OutputType()]
    param (
        [parameter(Mandatory=$True)][ValidateLength(3,3)][string] $SiteCode,
        [parameter(Mandatory=$True)][ValidateNotNullOrEmpty()][string] $Database,
        [parameter(Mandatory=$False)][ValidateNotNullOrEmpty()][string] $SiteServer = "$((Get-WmiObject win32_computersystem).DNSHostName+"."+$(Get-WmiObject win32_computersystem).Domain)",
        [parameter(Mandatory=$False)][ValidateNotNullOrEmpty()][string] $SqlInstance = "$((Get-WmiObject win32_computersystem).DNSHostName+"."+$(Get-WmiObject win32_computersystem).Domain)",
        [parameter(Mandatory=$False)][ValidateSet('All','AD','CM','Host','SQL','WSUS','Select','Previous')][string] $TestingScope = 'All',
        [parameter(Mandatory=$False)][string]$ConfigFile = "$($env:TEMP)\cmhealth.json",
        [parameter(Mandatory=$False)][boolean] $Remediate = $False,
        [parameter(Mandatory=$False)][string] $Source = "c:\windows\winsxs",
        [parameter(Mandatory=$False)][pscredential] $Credential,
        [parameter(Mandatory=$False)][string]$LogFile = "$($env:TEMP)\cmhealth_$(Get-Date -f 'yyyy-MM-dd').log",
        [parameter(Mandatory=$False)][switch]$NoVersionCheck,
        [parameter(Mandatory=$False)][switch]$AllServers
    )
    if (-not($NoVersionCheck)) { Test-CmHealthModuleVersion }
    if (-not(Test-Path $ConfigFile)) { New-CmHealthConfig -Path $ConfigFile }
    $startTime1 = (Get-Date)
    Write-Host "Thank you for using CMHealth! (and your ConfigMgr site thanks you too)" -ForegroundColor Cyan
    Write-Host "Thanks to the authors of PowerShell modules: DbaTools, Carbon, AdsiPS and psWindowsUpdate" -ForegroundColor Cyan
    #Write-Warning "If you haven't refreshed the cmhealth.json file since 0.2.24 or earlier, rename or delete the file and run this command again."
    Write-Host "log file = $LogFile"
    Write-Log -Message "------------------ begin processing --------------------"
    if (!(Test-Path "$ConfigFile")) {
        Write-Log -Message "Default configuration has not been defined." -Category Error -Show
        break
    }
    $Script:CmHealthConfig = Import-CmHealthSettings -Primary $ConfigFile
    if ($null -eq $CmHealthConfig) {
        Write-Log -Message "configuration data could not be imported" -Category Error -Show
        break
    }
    $GLOBAL:CmhParams = [ordered]@{
        ComputerName = $SiteServer
        SqlInstance  = $SqlInstance
        SiteCode     = $SiteCode
        Database     = $Database
        Source       = $Source
        Remediate    = $Remediate
        Credential   = $Credential
        LogFile      = $LogFile
        Verbose      = $VerbosePreference
    }
    #$GLOBAL:CmhParams = $params
    $mpath = $(Split-Path (Get-Module cmhealth).Path)
    $tpath = "$($mpath)\tests"
    $tests = Get-ChildItem -Path $tpath -Filter "*.ps1"
    Write-Log -Message "$($tests.Count) tests found in library"
    Write-Log -Message "testing scope = $TestingScope"
    if ($TestingScope -in ('All','AD')) {
        Write-Log -Message "AD tests may require RSAT to be installed" -Category Warning -Show
    }
    switch ($TestingScope) {
        'All' {
            $testset = @($tests.BaseName)
        }
        'Select' {
            $testset = @($tests.BaseName | Out-GridView -Title "Select Test to Execute" -OutputMode Multiple)
        }
        'Previous' {
            $testset = @(Get-CmHealthLastTestSet)
        }
        Default {
            $testset = @($tests.BaseName | Where-Object {$_ -match "Test-$($TestingScope)"})
        }
    }
    Write-Log -Message "$($testset.Count) tests were selected"
    if ($testset.Count -gt 0) {
        Write-Log -Message "saving test selection to history file"
        Set-CmHealthLastTestSet -TestNames $testset | Out-Null
    } else {
        Write-Log -Message "no tests were selected"
    }
    $testcount = $testset.Count
    $counter = 1
    if ($AllServers) {
        [array]$siteSystems = Get-CmSiteServersList -ComputerName $cmhParams.ComputerName -SiteCode $cmhParams.SiteCode
        if ($siteSystems.Count -gt 0) {
            foreach ($server in $siteSystems) {
                $cmhParams.ComputerName = $server.ServerName
                foreach ($test in $testset) {
                    Write-Log -Message "$($server.ServerName): TEST $counter of $testcount`: $test" -Show
                    $testname = $test += ' -ScriptParams $CmhParams'
                    Invoke-Expression -Command $testname
                    $counter++
                }
            }
        } else {
            Write-Log -Message "failed to get list of site systems" -Category Error -Show
        }
    } else {
        foreach ($test in $testset) {
            Write-Log -Message "TEST $counter of $testcount`: $test" -Show
            $testname = $test += ' -ScriptParams $CmhParams'
            Invoke-Expression -Command $testname
            $counter++
        }
    }
    $runTime = New-TimeSpan -Start $startTime1 -End (Get-Date)
    Write-Log -Message "completed $($testset.Count) tests in: $($runTime.Hours) hrs $($runTime.Minutes) min $($runTime.Seconds) sec" -Show
}