├── LICENSE ├── README.md ├── backlog ├── Test-CmBootImages.ps1 ├── Test-CmCMGStatus.ps1 ├── Test-CmCmgAppSync.ps1 ├── Test-CmContentLibDataDedupe.ps1 ├── Test-CmDiscoveryExclusions.ps1 ├── Test-CmSqlDbReplicationStatus.ps1 ├── Test-HostADKVersion.ps1 ├── Test-ModuleVersion.ps1 ├── Test-SqlDbFileCount.ps1 ├── Test-SqlDbFileGrowth.ps1 ├── Test-WsusIISAppPoolSettings.ps1 ├── Test-WsusWebConfig.ps1 └── roadmap.md ├── cmhealth.png ├── cmhealth.psd1 ├── cmhealth.psm1 ├── docs ├── Get-CmHealthTests.md ├── Invoke-CmHealthTests.md ├── Out-CmHealthReport.md ├── Reset-CmHealthConfig.md ├── Test-CmHealth.md ├── Test-CmHealthDependencies.md └── tests.md ├── optional ├── Test-CmCMGCostEstimate.ps1 ├── Test-CmClientEPPInfections.ps1 ├── Test-HostDriverAutomationTool.ps1 ├── Test-HostInstalledSoftware.ps1 └── Test-SqlDbDedicated.ps1 ├── private ├── buildnumbers_cm.csv ├── cmhealthutils.ps1 └── winclientbuildnumbers.csv ├── public ├── Get-CmHealthTests.ps1 ├── Invoke-CmHealthTests.ps1 ├── Out-CmHealthReport.ps1 ├── Reset-CmHealthConfig.ps1 ├── Show-CmHealthConfig.ps1 ├── Test-CmHealth.ps1 └── Test-CmHealthDependencies.ps1 ├── reserve ├── Test-Example.ps1 └── cmhealth.json ├── samples └── Install-DbMaintenanceSolution.ps1 └── tests ├── Test-AdSchemaExtension.ps1 ├── Test-AdSysMgtContainer.ps1 ├── Test-CmAppDeploymentExceptions.ps1 ├── Test-CmBoundariesOrphaned.ps1 ├── Test-CmBoundaryDuplicates.ps1 ├── Test-CmClientATPStatus.ps1 ├── Test-CmClientAssignmentFailures.ps1 ├── Test-CmClientBoundaryExceptions.ps1 ├── Test-CmClientCoverage.ps1 ├── Test-CmClientDeploymentFailures.ps1 ├── Test-CmClientErrors.ps1 ├── Test-CmClientHealth.ps1 ├── Test-CmClientInactive.ps1 ├── Test-CmClientInventoryMissing.ps1 ├── Test-CmClientInventoryStale.ps1 ├── Test-CmClientLowDiskSpace.ps1 ├── Test-CmClientMissing.ps1 ├── Test-CmClientOldVersion.ps1 ├── Test-CmClientUpdateAgentVersion.ps1 ├── Test-CmClientUpdateDeploymentErrors.ps1 ├── Test-CmClientUpdateScanErrors.ps1 ├── Test-CmCollectionRefresh.ps1 ├── Test-CmComponentErrors.ps1 ├── Test-CmComponentStatusMessages.ps1 ├── Test-CmComponentStatusSummary.ps1 ├── Test-CmContentDistErrors.ps1 ├── Test-CmContentMissing.ps1 ├── Test-CmContentNotDistributed.ps1 ├── Test-CmDPDiskSpace.ps1 ├── Test-CmDatabaseSize.ps1 ├── Test-CmDpNoGroups.ps1 ├── Test-CmInvMaxMIFSize.ps1 ├── Test-CmMpResponse.ps1 ├── Test-CmPackageDistErrors.ps1 ├── Test-CmQueryExpensiveRule.ps1 ├── Test-CmSiteAdminsCount.ps1 ├── Test-CmSiteCertificates.ps1 ├── Test-CmSiteInstallAccountRoles.ps1 ├── Test-CmSiteLastBackup.ps1 ├── Test-CmSiteMaintenanceTasks.ps1 ├── Test-CmSiteStatusMessages.ps1 ├── Test-CmUnknownDevices.ps1 ├── Test-CmUpdateADRErrors.ps1 ├── Test-CmUpdateCompliance.ps1 ├── Test-CmUpdateDeploymentErrors.ps1 ├── Test-CmUpdateErrorSolutions.ps1 ├── Test-CmUpdateScanErrors.ps1 ├── Test-CmWsusLastSync.ps1 ├── Test-HostAntiVirus.ps1 ├── Test-HostAppLogErrors.ps1 ├── Test-HostDiskSpace.ps1 ├── Test-HostDnsRegistration.ps1 ├── Test-HostDriveBlockSize.ps1 ├── Test-HostFirewallPorts.ps1 ├── Test-HostIESCDisabled.ps1 ├── Test-HostIISLogFiles.ps1 ├── Test-HostInstalledComponents.ps1 ├── Test-HostMemory.ps1 ├── Test-HostNoSmsOnDriveFile.ps1 ├── Test-HostOperatingSystem.ps1 ├── Test-HostRestarts.ps1 ├── Test-HostServerFeatures.ps1 ├── Test-HostServiceAccounts.ps1 ├── Test-HostServices.ps1 ├── Test-HostSystemLogErrors.ps1 ├── Test-HostWindowsUpdates.ps1 ├── Test-SqlAgentJobStatus.ps1 ├── Test-SqlDatabaseFileGrowth.ps1 ├── Test-SqlDatabaseFileInfo.ps1 ├── Test-SqlDatabaseNameDefault.ps1 ├── Test-SqlDbBackupHistory.ps1 ├── Test-SqlDbBackupTimes.ps1 ├── Test-SqlDbCollation.ps1 ├── Test-SqlDbDiskAlignment.ps1 ├── Test-SqlDbRecoveryModel.ps1 ├── Test-SqlDbResourceWaits.ps1 ├── Test-SqlDiskSpace.ps1 ├── Test-SqlHostOS.ps1 ├── Test-SqlIndexFragmentation.ps1 ├── Test-SqlLogSpace.ps1 ├── Test-SqlRoleMembers.ps1 ├── Test-SqlServerMemory.ps1 ├── Test-SqlServerVersion.ps1 ├── Test-SqlServiceSPN.ps1 └── Test-SqlUpdates.ps1 /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Skatterbrainz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backlog/Test-CmBootImages.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmBootImages { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Verify Boot Image OS Versions", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check if Boot Images are supported OS versions", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | bip.PackageID, bip.Name, bip.Version, 19 | CASE 20 | WHEN (LEFT(Version, 10) = '10.0.10240') THEN '1507' 21 | WHEN (LEFT(Version, 10) = '10.0.10586') THEN '1511' 22 | WHEN (LEFT(Version, 10) = '10.0.14393') THEN '1607' 23 | WHEN (LEFT(Version, 10) = '10.0.15063') THEN '1703' 24 | WHEN (LEFT(Version, 10) = '10.0.16299') THEN '1709' 25 | WHEN (LEFT(Version, 10) = '10.0.17134') THEN '1803' 26 | WHEN (LEFT(Version, 10) = '10.0.17763') THEN '1809' 27 | WHEN (LEFT(Version, 10) = '10.0.18362') THEN '1903' 28 | WHEN (LEFT(Version, 10) = '10.0.18363') THEN '1909' 29 | WHEN (LEFT(Version, 10) = '10.0.19041') THEN '2004' 30 | WHEN (LEFT(Version, 10) = '10.0.19042') THEN '20H2' 31 | WHEN (LEFT(Version, 10) = '10.0.19043') THEN '21H1' 32 | WHEN (LEFT(Version, 10) = '10.0.19044') THEN '21H2' 33 | WHEN (LEFT(Version, 10) = '10.0.22000') THEN '21H1' 34 | ELSE '' END AS BuildNumber, 35 | bip.Description, 36 | bip.PkgSourcePath, 37 | bip.SourceDate, 38 | bip.LastRefreshTime, 39 | CASE WHEN (DPNALPath LIKE '%cloudapp.net%') then 'Cloud DP' 40 | else 'OnPrem DP' end as DPType 41 | FROM 42 | dbo.v_BootImagePackage as bip LEFT OUTER JOIN 43 | dbo.v_DistributionStatus AS ds ON bip.PackageID = ds.PkgID" 44 | [array]$res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 45 | if ($res.Count -gt 0) { 46 | $msg = "$($res.Count) items found" 47 | [array]$cdp = $res | Where-Object {$_.DPType -eq 'Cloud DP'} 48 | if ($cdp.Count -gt 0) { 49 | $stat = $except 50 | $msg = "Content distributed to $($cdp.Count) cloud distribution points" 51 | } 52 | $res | Foreach-Object { 53 | $tempdata.Add( 54 | [pscustomobject]@{ 55 | Name = $_.Name 56 | PackageID = $_.PackageID 57 | Version = $_.Version 58 | BuildNumber = $_.BuildNumber 59 | Description = $_.Description 60 | SourcePath = $_.PkgSourcePath 61 | DPType = $_.DPType 62 | } 63 | ) 64 | } 65 | } else { 66 | $msg = "No boot images found" 67 | } 68 | } 69 | catch { 70 | $stat = 'ERROR' 71 | $msg = $_.Exception.Message -join ';' 72 | } 73 | finally { 74 | $([pscustomobject]@{ 75 | Computer = $ScriptParams.ComputerName 76 | TestName = $TestName 77 | TestGroup = $TestGroup 78 | TestData = $tempdata 79 | Description = $Description 80 | Status = $stat 81 | Message = $msg 82 | RunTime = $(Get-RunTime -BaseTime $startTime) 83 | Credential = $(if($ScriptParams.Credential){$($ScriptParams.Credential).UserName} else { $env:USERNAME }) 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /backlog/Test-CmCMGStatus.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmCMGStatus { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Cloud Management Gateway Status", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check status of CMG", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | <# 18 | ======================================================= 19 | COMMENT: EXAMPLE FOR SQL QUERY RELATED TESTS... DELETE THIS BLOCK IF NOT USED 20 | ======================================================= 21 | 22 | $query = "" 23 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 24 | if ($null -ne $res -and $res.Count -gt 0) { 25 | $stat = $except 26 | $msg = "$($res.Count) items found" 27 | #$res | Foreach-Object {$tempdata.Add( [pscustomobject]@{Name=$_.Name} )} 28 | } 29 | ======================================================= 30 | #> 31 | } 32 | catch { 33 | $stat = 'ERROR' 34 | $msg = $_.Exception.Message -join ';' 35 | } 36 | finally { 37 | Write-Output $([pscustomobject]@{ 38 | Computer = $ScriptParams.ComputerName 39 | TestName = $TestName 40 | TestGroup = $TestGroup 41 | TestData = $tempdata 42 | Description = $Description 43 | Status = $stat 44 | Message = $msg 45 | RunTime = $(Get-RunTime -BaseTime $startTime) 46 | Credential = $(if($ScriptParams.Credential){$($ScriptParams.Credential).UserName} else { $env:USERNAME }) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backlog/Test-CmCmgAppSync.ps1: -------------------------------------------------------------------------------- 1 | $query = "SELECT TOP (1000) [AppModelId] 2 | ,[InternalId] 3 | ,[ProgramId] 4 | ,[ProductName] 5 | ,[ProductVersion] 6 | ,[Publisher] 7 | ,[ProductLanguage] 8 | ,[ProductCode] 9 | ,[IsDeleted] 10 | ,[SequenceNumber] 11 | FROM [dbo].[vCMGS_AppProgramSyncData]" -------------------------------------------------------------------------------- /backlog/Test-CmContentLibDataDedupe.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # credit: Chad Simmons: 3 | 4 | # TASK: Get package source paths 5 | # TASK: Get shares > root paths (drive\folder) 6 | # TASK: Get Dedupe setting per volume 7 | 8 | # TASK: Get DP servers 9 | 10 | Get-DedupVolume | 11 | Select-Object Volume, Enabled, MinimumFileAgeDays, MinimumFileSize, NoCompress, OptimizeInUseFiles, SavedSpace, SavingsRate, UnoptimizedSize, UsedSpace #-Volume $Drive 12 | 13 | # TASK: Modify (below) to query dedupe settings 14 | 15 | ForEach ($DPServer in $DPServers) { 16 | Invoke-Command -ComputerName $DPServer -ScriptBlock { 17 | & { Import-Module Deduplication; $Drives = @((Get-WmiObject -Class Win32_LogicalDisk -Filter { DriveType = 3 }).DeviceID); ForEach ($Drive in $Drives) { If (Test-Path -Path "$Drive\SCCMContentLib") {Enable-DedupVolume $Drive; Set-DedupVolume –Volume $Drive -NoCompressionFileType @('7z','mp3','mp4','mkv','jpg','png','zpaq','bak','wim'); Start-DedupJob –Volume $Drive -Type Optimization -Preempt } } } 18 | } 19 | } 20 | #> -------------------------------------------------------------------------------- /backlog/Test-CmDiscoveryExclusions.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmDiscoveryExclusions { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Discovery Methods with Exclusions", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Warn about exclusions and possible missing resources", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | <# 18 | ======================================================= 19 | COMMENT: EXAMPLE FOR SQL QUERY RELATED TESTS... DELETE THIS BLOCK IF NOT USED 20 | ======================================================= 21 | $query = "" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | if ($null -ne $res -and $res.Count -gt 0) { 24 | $stat = $except 25 | $msg = "$($res.Count) items found" 26 | #$res | Foreach-Object {$tempdata.Add( [pscustomobject]@{Name=$_.Name} )} 27 | } 28 | ======================================================= 29 | #> 30 | } 31 | catch { 32 | $stat = 'ERROR' 33 | $msg = $_.Exception.Message -join ';' 34 | } 35 | finally { 36 | Write-Output $([pscustomobject]@{ 37 | Computer = $ScriptParams.ComputerName 38 | TestName = $TestName 39 | TestGroup = $TestGroup 40 | TestData = $tempdata 41 | Description = $Description 42 | Status = $stat 43 | Message = $msg 44 | RunTime = $(Get-RunTime -BaseTime $startTime) 45 | Credential = $(if($ScriptParams.Credential){$($ScriptParams.Credential).UserName} else { $env:USERNAME }) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backlog/Test-CmSqlDbReplicationStatus.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSqlDbReplicationStatus { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "CM Database Replication Status", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for SQL Replication Errors and Warnings", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | <# 18 | ======================================================= 19 | | COMMENT: DELETE THIS BLOCK WHEN FINISHED: 20 | | 21 | | perform test and return result as an object... 22 | | $stat = $except (no need to set "PASS" since it's the default) 23 | | $msg = "custom message that N issues were found" 24 | | add supporting data to $tempdata array if it helps output 25 | | loop output into $tempdata.Add() array to return as TestData param in output 26 | ======================================================= 27 | #> 28 | 29 | <# 30 | ======================================================= 31 | COMMENT: EXAMPLE FOR SQL QUERY RELATED TESTS... DELETE THIS BLOCK IF NOT USED 32 | ======================================================= 33 | 34 | $query = "" 35 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 36 | if ($res.Count -gt 0) { 37 | $stat = $except 38 | $msg = "$($res.Count) items found" 39 | #$res | Foreach-Object {$tempdata.Add( [pscustomobject]@{Name=$_.Name} )} 40 | } 41 | ======================================================= 42 | #> 43 | } 44 | catch { 45 | $stat = 'ERROR' 46 | $msg = $_.Exception.Message -join ';' 47 | } 48 | finally { 49 | Write-Output $([pscustomobject]@{ 50 | TestName = $TestName 51 | TestGroup = $TestGroup 52 | TestData = $tempdata 53 | Description = $Description 54 | Status = $stat 55 | Message = $msg 56 | RunTime = $(Get-RunTime -BaseTime $startTime) 57 | Credential = $(if($ScriptParams.Credential){$($ScriptParams.Credential).UserName} else { $env:USERNAME }) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /backlog/Test-HostADKVersion.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostADKVersion { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Validate ADK Version", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Verify ADK version is supported", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | # https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/configs/support-for-windows-10 13 | $adk_cm = [pscustomobject]@{ 14 | "10.1.17763" = ("1906","1910") 15 | "10.1.18362" = ("1906","1910","2002","2006","2010") 16 | "10.1.19041" = ("2002","2006","2010") 17 | } 18 | $w10_cm = [pscustomobject]@{ 19 | "10.0.17134" = ("1906","1910","2002","2006","2010") 20 | "10.0.17763" = ("1906","1910","2002","2006","2010") 21 | "10.0.18363" = ("1906","1910","2002","2006","2010") 22 | "10.0.19041" = ("2002","2006","2010") 23 | "10.0.19042" = ("2006","2010") 24 | } 25 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 26 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 27 | $stat = "PASS" # do not change this 28 | $except = "WARNING" # or "FAIL" 29 | $msg = "No issues found" # do not change this either 30 | $apps = Get-WmiQueryResult -ClassName "Win32_Product" -Query "Name = 'Windows PE x86 x64'" -Params $ScriptParams 31 | foreach ($app in $apps) { 32 | $name = $app.Name 33 | $version = $app.Version 34 | # to-do: get cm site version and compare against $versionTable 35 | [string]$cmsiteversion = $(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\SMS\Setup' -Name "Version")."Version" 36 | $versionName = Get-CmVersionName -Version $cmsiteversion 37 | $tempdata.Add( 38 | [pscustomobject]@{ 39 | ADKPE = $name 40 | Version = $version 41 | Build = $versionName 42 | } 43 | ) 44 | } # foreach 45 | } 46 | catch { 47 | $stat = 'ERROR' 48 | $msg = $_.Exception.Message -join ';' 49 | } 50 | finally { 51 | $([pscustomobject]@{ 52 | Computer = $ScriptParams.ComputerName 53 | TestName = $TestName 54 | TestGroup = $TestGroup 55 | TestData = $tempdata 56 | Description = $Description 57 | Status = $stat 58 | Message = $msg 59 | RunTime = $(Get-RunTime -BaseTime $startTime) 60 | Credential = $(if($ScriptParams.Credential){$($ScriptParams.Credential).UserName} else { $env:USERNAME }) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /backlog/Test-ModuleVersion.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmHealthModuleVersion { 2 | try { 3 | $mv = Get-Module 'cmhealth' -ListAvailable | Select-Object -First 1 -ExpandProperty Version 4 | if ($null -ne $mv) { 5 | $mv = $mv -join '.' 6 | $fv = Find-Module 'cmhealth' | Select-Object -ExpandProperty Version 7 | if ([version]$fv -gt [version]$mv) { 8 | Write-Warning "$mv is installed. $fv is available" 9 | } else { 10 | Write-Host "cmhealth version $mv is the latest available" -ForegroundColor Green 11 | } 12 | } else { 13 | Write-Warning "cmhealth version could not be determined" 14 | } 15 | } 16 | catch { 17 | Write-Error $_.Exception.Message 18 | } 19 | } -------------------------------------------------------------------------------- /backlog/Test-SqlDbFileCount.ps1: -------------------------------------------------------------------------------- 1 | # count number of DB files -------------------------------------------------------------------------------- /cmhealth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skatterbrainz/cmhealth/548d16aae24f7b05a9f2a9414da584a83369e72d/cmhealth.png -------------------------------------------------------------------------------- /cmhealth.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skatterbrainz/cmhealth/548d16aae24f7b05a9f2a9414da584a83369e72d/cmhealth.psd1 -------------------------------------------------------------------------------- /cmhealth.psm1: -------------------------------------------------------------------------------- 1 | ('public','private','tests') | Foreach-Object { 2 | Get-ChildItem -Path (Join-Path $PSScriptRoot -ChildPath $_) -Filter "*.ps1" | Foreach-Object { . $_.FullName } 3 | } 4 | -------------------------------------------------------------------------------- /docs/Get-CmHealthTests.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: cmhealth-help.xml 3 | Module Name: cmhealth 4 | online version: https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Get-CmHealthTests.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-CmHealthTests 9 | 10 | ## SYNOPSIS 11 | Display CMHealth Tests and description info 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-CmHealthTests [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Display CMHealth tests and additional descriptive information 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-CmHealthTests 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | Added in 0.3.8 40 | 41 | ## RELATED LINKS 42 | 43 | [https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Get-CmHealthTests.md](https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Get-CmHealthTests.md) 44 | 45 | -------------------------------------------------------------------------------- /docs/Reset-CmHealthConfig.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: cmhealth-help.xml 3 | Module Name: cmhealth 4 | online version: https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Reset-CmHealthConfig.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Reset-CmHealthConfig 9 | 10 | ## SYNOPSIS 11 | Replace existing cmhealth.json with default template 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Reset-CmHealthConfig [[-ConfigFile] ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Replace existing cmhealth.json with default template 21 | 22 | ## EXAMPLES 23 | 24 | ### Example 1 25 | ```powershell 26 | PS C:\> {{ Add example code here }} 27 | ``` 28 | 29 | {{ Add example description here }} 30 | 31 | ## PARAMETERS 32 | 33 | ### -ConfigFile 34 | Path and filename to cmhealth.json 35 | 36 | ```yaml 37 | Type: String 38 | Parameter Sets: (All) 39 | Aliases: 40 | 41 | Required: False 42 | Position: 1 43 | Default value: "$($env:TEMP)\cmhealth.json" 44 | Accept pipeline input: False 45 | Accept wildcard characters: False 46 | ``` 47 | 48 | ### CommonParameters 49 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 50 | 51 | ## INPUTS 52 | 53 | ## OUTPUTS 54 | 55 | ## NOTES 56 | Thank you again! 57 | 58 | ## RELATED LINKS 59 | 60 | [https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Reset-CmHealthConfig.md](https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Reset-CmHealthConfig.md) 61 | 62 | -------------------------------------------------------------------------------- /docs/Test-CmHealthDependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: cmhealth-help.xml 3 | Module Name: cmhealth 4 | online version: https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Test-CmHealthDependencies.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Test-CmHealthDependencies 9 | 10 | ## SYNOPSIS 11 | Check (and update) dependent PowerShell modules 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Test-CmHealthDependencies [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Check current install versions of dependent PowerShell modules against 21 | PowerShell Gallery and update them if desired 22 | 23 | ## EXAMPLES 24 | 25 | ### EXAMPLE 1 26 | ``` 27 | Test-CmHealthDependencies 28 | ``` 29 | 30 | Returns status of installed modules which are used by CMHealth 31 | 32 | ### EXAMPLE 2 33 | ``` 34 | Test-CmHealthDependencies -Update 35 | ``` 36 | 37 | Updates installed modules used by CMHealth if they are older than published on PS Galler 38 | 39 | ## PARAMETERS 40 | 41 | ### CommonParameters 42 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 43 | 44 | ## INPUTS 45 | 46 | ## OUTPUTS 47 | 48 | ## NOTES 49 | 50 | ## RELATED LINKS 51 | 52 | [https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Test-CmHealthDependencies.md](https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Test-CmHealthDependencies.md) 53 | 54 | -------------------------------------------------------------------------------- /docs/tests.md: -------------------------------------------------------------------------------- 1 | # CMHealth Tests 2 | 3 | ## Active Directory 4 | 5 | * Active Directory Schema Extended 6 | * AD SystemManagement Container, permissions 7 | 8 | ## Host / Operating System 9 | 10 | * AntiVirus product installations 11 | * Event Log Errors - Application 12 | * Event Log Errors - System 13 | * Disk Space 14 | * Disk Drive Volume Block Size 15 | * DNS registration 16 | * Driver Automation Tool installed 17 | * Firewall Port Exclusions 18 | * IESC Disabled 19 | * IIS Log Files 20 | * Installed Windows Components 21 | * Installed Windows Software Products 22 | * Host Memory 23 | * NO_SMS_ON_DRIVE.SMS on non-content disks 24 | * Supported Operating System Version 25 | * Unexpected Host Restart events 26 | * Installed Windows Roles and Features 27 | * Service Accounts and Permissions 28 | * Windows Update patch status 29 | 30 | ## ConfigMgr Client 31 | 32 | * Devices Missing a Client 33 | * Devices with an Old Client Version 34 | * Clients with EPP Infections 35 | 36 | ## ConfigMgr Site 37 | 38 | * Site Administrators 39 | * Install Account Permissions / Group Memberships 40 | * Last Site Backup 41 | * Site Certificate Expirations 42 | * MP Response Status 43 | * Package Distribution Errors 44 | * Expensive Queries 45 | * Max MIF File Setting 46 | * DP Group Memberships 47 | * Content Not Distributed 48 | * Content Missing 49 | * Component Status Messages 50 | * Component Status Summary 51 | * Collection Refresh Performance 52 | 53 | ## SQL Server 54 | 55 | * SQL Agent status 56 | * SQL Backup History 57 | * Database Backup Times 58 | * Database Collation Setting 59 | * Supported Databases 60 | * Disk Alignment Status 61 | * Database Recovery Model 62 | * Resource Wait Times 63 | * Log File Space Utilitization 64 | * SQL Role Members 65 | * SQL Server (Instance) Memory Allocation 66 | * SQL Server Version (Build, SP, CU) 67 | * Service Principal Name (SPN) 68 | * SQL Server Updates 69 | 70 | ## WSUS 71 | 72 | * Web Config Settings 73 | 74 | ## Backlog 75 | 76 | * MDT install info 77 | * ADK install info 78 | * Boot image configuration issues 79 | * SQL Maintenance implementation (Ola Ola Ola!) 80 | * WSUS maintenance implementation (Bryan Dam) 81 | * (your idea here!) 82 | -------------------------------------------------------------------------------- /optional/Test-CmCMGCostEstimate.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmCMGCostEstimate { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Estimated CMG Operational Cost Estimate", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "CMG Per Client Average Data usage", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select * from v_CloudCostEstimatorData" 19 | # returns: ID (aka sitecode), LaptopCount(int), DesktopCount(int), ServerCount(int), clientCount(int), AvgMPMBPerMonthPerClient(int), AvgContentMBPerMonthPerClient(int) 20 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 21 | if ($res.Count -gt 0) { 22 | $stat = $except 23 | $msg = "$($res.Count) items found" 24 | $res | Foreach-Object { 25 | $tempdata.Add( 26 | [pscustomobject]@{ 27 | SiteCode=$_.ID 28 | Laptops = $_.LaptopCount 29 | Desktops = $_.DesktopCount 30 | Servers = $_.ServerCount 31 | Clients = $_.clientCount 32 | AvgPerClientMPMB = $_.AvgMPMBPerMonthPerClient 33 | AvgPerClientContentMB = $_.AvgContentMBPerMonthPerClient 34 | } 35 | ) 36 | } 37 | } else { 38 | $msg = "No CMG costs are available" 39 | } 40 | } 41 | catch { 42 | $stat = 'ERROR' 43 | $msg = $_.Exception.Message -join ';' 44 | } 45 | finally { 46 | Set-CmhOutputData 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /optional/Test-CmClientEPPInfections.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientEPPInfections { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Check for Endpoint Protection Infections", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Query history of EP client infections", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select Name,EP_LastThreatName,EP_LastInfectionTime from dbo.v_CombinedDeviceResources where EP_LastThreatName IS NOT NULL order by Name" 19 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 20 | if ($res.Count -gt 0) { 21 | $stat = $except 22 | $msg = "$($res.Count) items found" 23 | $res | Foreach-Object { 24 | $tempdata.Add( 25 | [pscustomobject]@{ 26 | DeviceName = $_.Name 27 | ThreatName = $_.EP_LastThreatName 28 | DateTime = $_.EP_LastInfectionTime 29 | } 30 | ) 31 | } 32 | } 33 | } 34 | catch { 35 | $stat = 'ERROR' 36 | $msg = $_.Exception.Message -join ';' 37 | } 38 | finally { 39 | Set-CmhOutputData 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /optional/Test-HostDriverAutomationTool.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostDriverAutomationTool { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Check if Driver Automation Tool is installed", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check if Driver Automation Tool is installed", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [string]$latest = Get-CmHealthDefaultValue -KeySet "tools:DriverAutomationTool" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "WARNING" # assume not-installed is the baseline 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "Driver Automation Tool is not installed" # do not change this either 17 | $res = Get-WmiQueryResult -ClassName "Win32_Product" -Query "Name = 'Driver Automation Tool'" -Params $ScriptParams 18 | foreach ($app in $res) { 19 | $appver = $app.Version 20 | if ($appver -ge $latest) { 21 | $msg = "latest version is installed: $latest" 22 | $stat = "PASS" 23 | } else { 24 | $msg = "outdated version is installed: $appver" 25 | $stat = $except 26 | } 27 | $tempdata.Add( 28 | [pscustomobject]@{ 29 | ProductName = $app.Name 30 | Publisher = $app.Vendor 31 | Version = $app.Version 32 | Latest = $latest 33 | ProductCode = $app.IdentifyingNumber 34 | InstallDate = $app.InstallDate 35 | InstallPath = $app.InstallLocation 36 | } 37 | ) 38 | } # foreach 39 | } 40 | catch { 41 | $stat = 'ERROR' 42 | $msg = $_.Exception.Message -join ';' 43 | } 44 | finally { 45 | Set-CmhOutputData 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /optional/Test-HostInstalledSoftware.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostInstalledSoftware { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Installed Software Applications", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check for excessive junk installed on site server", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | $startTime = (Get-Date) 11 | [int]$MaxProducts = Get-CmHealthDefaultValue -KeySet "siteservers:InstalledSoftwareThreshold" -DataSet $CmHealthConfig 12 | Write-Log -Message "MaxProducts = $MaxProducts" 13 | try { 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | [array]$res = Get-WmiQueryResult -ClassName "Win32_Product" -Params $ScriptParams | Sort-Object Name 19 | Write-Log -Message "$($res.Count) products were returned" 20 | if ($res.Count -gt $MaxProducts) { 21 | $stat = $except 22 | $msg = "$($res.Count) items found. See TestData for item details" 23 | $res | Foreach-Object { 24 | $tempdata.Add( 25 | [pscustomobject]@{ 26 | ProductName = $_.Name 27 | Version = $_.Version 28 | Vendor = $_.Vendor 29 | ProductCode = $_.ProductCode 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | if ($cs) { $cs.Close(); $cs = $null } 41 | Set-CmhOutputData 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /optional/Test-SqlDbDedicated.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbDedicated { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Unsupported Databases", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Verify SQL Instance is dedicated to ConfigMgr site", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | $stat = "PASS" 13 | $except = "WARNING" 14 | $msg = "No issues found" 15 | $supported = Get-CmHealthDefaultValue -KeySet "sqlserver:LicensedDatabases" -DataSet $CmHealthConfig 16 | Write-Log -Message "Supported Names: $($supported -join ',')" 17 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 18 | if ($null -ne $ScriptParams.Credential) { 19 | $dbnames = Get-DbaDatabase -SqlInstance $ScriptParams.SqlInstance -SqlCredential $ScriptParams.Credential | Select-Object -ExpandProperty Name 20 | } else { 21 | $dbnames = Get-DbaDatabase -SqlInstance $ScriptParams.SqlInstance | Select-Object -ExpandProperty Name 22 | } 23 | $dblist1 = @() 24 | $dblist2 = @() 25 | $dbnames | ForEach-Object { 26 | Write-Log -Message "database name: $_" 27 | if (($_ -notmatch 'CM_') -and ($_ -notin $supported)) { 28 | Write-Log -Message "database is not supported: $($_)" 29 | $dblist1 += $($_).ToString() 30 | $isSupported = $False 31 | } else { 32 | Write-Log -Message "database is supported: $($_)" 33 | $dblist2 += $($_).ToString() 34 | $isSupported = $True 35 | } 36 | $tempdata.Add( 37 | [pscustomobject]@{ 38 | SqlInstance = $($ScriptParams.SqlInstance) 39 | Database = $($_) 40 | Supported = $isSupported 41 | } 42 | ) 43 | } 44 | if ($dblist1.Count -gt 0) { 45 | Write-Log -Message "$($dblist1.Count) unsupported names were found" 46 | $msg = "$($dblist1.Count) databases are not supported by MEMCM SQL licensing: $($dblist1 -join ',')" 47 | $stat = $except 48 | } else { 49 | Write-Log -Message "no unsupported names were found" 50 | $msg = "All databases are supported for MEMCM SQL licensing" 51 | } 52 | } 53 | catch { 54 | $msg = $_.Exception.Message -join ';' 55 | $stat = "ERROR" 56 | } 57 | finally { 58 | Set-CmhOutputData 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /private/buildnumbers_cm.csv: -------------------------------------------------------------------------------- 1 | Build,Name,Flight 2 | 8325,1511,GA 3 | 8355,1602,GA 4 | 8412,1606,GA 5 | 8458,1610,GA 6 | 8498,1702,GA 7 | 8540,1706,GA 8 | 8577,1710,GA 9 | 8634,1802,GA 10 | 8692,1806,GA 11 | 8740,1810,GA 12 | 8790,1902,GA 13 | 8842,1906,Preview 14 | 8853,1906,GA 15 | 8855,1907,Preview 16 | 8860,1908,Preview 17 | 8884,1909,Preview 18 | 8899,1910,GA 19 | 8909,1911,Preview 20 | 8913,1910,GA 21 | 8923,1912,Preview 22 | 8938,2001,Preview 23 | 8946,2001.2,Preview 24 | 8953,2002,Preview 25 | 8961,2002.2,Preview 26 | 8968,2002,GA 27 | 8974,2003,Preview 28 | 8991,2004,Preview 29 | 9023,2005,Preview 30 | 9012,2006,GA 31 | 9026,2007,Preview 32 | 9028,2008,Preview 33 | 9030,2009,Preview 34 | 9032,2010,Preview 35 | 9039,2010.2,Preview 36 | 9040,2010,GA 37 | 9042,2011,Preview 38 | 9043,2012,Preview 39 | 9044,2101,Preview 40 | 9045,2102,Preview 41 | 9047,2103,Preview 42 | 9049,2103,GA 43 | 9050,2104,Preview 44 | 9051,2105,Preview 45 | 9052,2015.2,Preview 46 | 9056,2106,Preview 47 | 9058,2107,GA 48 | 9059,2107,Preview 49 | 9060,2108,Preview 50 | 9061,2109,Preview 51 | 9068,2111,Hotfix 52 | 9070,2112,Preview 53 | 9071,2201,Preview 54 | 9072,2202,Preview 55 | 9075,2203,Preview 56 | 9078,2203,GA -------------------------------------------------------------------------------- /private/winclientbuildnumbers.csv: -------------------------------------------------------------------------------- 1 | build,product,version 2 | 10.0.10240,10,1507 3 | 10.0.10586,10,1511 4 | 10.0.14393,10,1607 5 | 10.0.15063,10,1703 6 | 10.0.16299,10,1709 7 | 10.0.17134,10,1803 8 | 10.0.17763,10,1809 9 | 10.0.18362,10,1903 10 | 10.0.18363,10,1909 11 | 10.0.19041,10,2004 12 | 10.0.19042,10,20H2 13 | 10.0.19043,10,21H1 14 | 10.0.19044,10,21H2 15 | 10.0.22000,11,21H1 16 | -------------------------------------------------------------------------------- /public/Get-CmHealthTests.ps1: -------------------------------------------------------------------------------- 1 | function Get-CmHealthTests { 2 | <# 3 | .SYNOPSIS 4 | Display CMHealth Tests and description info 5 | .DESCRIPTION 6 | Display CMHealth tests and additional descriptive information 7 | .EXAMPLE 8 | Get-CmHealthTests 9 | .NOTES 10 | Added in 0.3.8 11 | .LINK 12 | https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Get-CmHealthTests.md 13 | #> 14 | [CmdletBinding()] 15 | [OutputType()] 16 | param() 17 | $mpath = $(Split-Path (Get-Module cmhealth).Path) 18 | $tpath = "$($mpath)\tests" 19 | $tests = Get-ChildItem -Path $tpath -Filter "*.ps1" -ErrorAction Stop 20 | foreach ($test in $tests) { 21 | . $test.FullName 22 | $basename = $test.BaseName 23 | $ast = (Get-Command $basename).ScriptBlock.Ast 24 | $x = $ast.Body.ParamBlock.Parameters.Extent.Text 25 | $testname = $x[0].Split('=')[1].Trim() 26 | $testgroup = $x[1].Split('=')[1].Trim() 27 | $testdesc = $x[2].Split('=')[1].Trim() 28 | switch ($basename.Substring(5,2)) { 29 | 'ad' { $type = 'AD' } 30 | 'cm' { $type = 'CM' } 31 | 'sq' { $type = 'SQL' } 32 | 'ii' { $type = 'IIS' } 33 | 'ho' { $type = 'Host' } 34 | default { $basename.Substring(5,2) } 35 | } 36 | [pscustomobject]@{ 37 | Test = $basename 38 | Name = $testname.Replace('"', '') 39 | Group = $testgroup.Replace('"', '') 40 | Type = $type 41 | Description = $testdesc.Replace('"', '') 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /public/Reset-CmHealthConfig.ps1: -------------------------------------------------------------------------------- 1 | function Reset-CmHealthConfig { 2 | <# 3 | .SYNOPSIS 4 | Replace existing cmhealth.json with default template 5 | .DESCRIPTION 6 | Replace existing cmhealth.json with default template 7 | .PARAMETER ConfigFile 8 | Path and filename to cmhealth.json 9 | .NOTES 10 | Thank you again! 11 | .LINK 12 | https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Reset-CmHealthConfig.md 13 | #> 14 | [CmdletBinding()] 15 | [OutputType()] 16 | param ( 17 | [parameter(Mandatory=$False)][string]$ConfigFile = "$($env:TEMP)\cmhealth.json" 18 | ) 19 | if (Test-Path $ConfigFile) { 20 | Write-Verbose "removing cmhealth settings file: $($ConfigFile)" 21 | Get-Item -Path $ConfigFile | Remove-Item -Force 22 | } 23 | if ([string]::IsNullOrEmpty($LogFile)) { 24 | $LogFile = "$($env:TEMP)\cmhealth_$(Get-Date -f 'yyyy-MM-dd').log" 25 | } 26 | New-CmHealthConfig -Path $ConfigFile 27 | Write-Host "$ConfigFile has been reset to default values" -ForegroundColor Cyan 28 | } 29 | -------------------------------------------------------------------------------- /public/Show-CmHealthConfig.ps1: -------------------------------------------------------------------------------- 1 | function Get-CmHealthConfig { 2 | param() 3 | $Script:CmHealthConfig = Import-CmHealthSettings 4 | $Script:CmHealthConfig 5 | } -------------------------------------------------------------------------------- /public/Test-CmHealthDependencies.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmHealthDependencies { 2 | <# 3 | .SYNOPSIS 4 | Check (and update) dependent PowerShell modules 5 | .DESCRIPTION 6 | Check current install versions of dependent PowerShell modules against 7 | PowerShell Gallery and update them if desired 8 | .PARAMETER Update 9 | Optional. Update modules which older than PS Gallery versions 10 | .EXAMPLE 11 | Test-CmHealthDependencies 12 | Returns status of installed modules which are used by CMHealth 13 | .EXAMPLE 14 | Test-CmHealthDependencies -Update 15 | Updates installed modules used by CMHealth if they are older than published on PS Galler 16 | .LINK 17 | https://github.com/Skatterbrainz/cmhealth/blob/master/docs/Test-CmHealthDependencies.md 18 | #> 19 | [CmdletBinding()] 20 | [OutputType()] 21 | param() 22 | Write-Host "checking dependencie module versions" -ForegroundColor Cyan 23 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 24 | $modules = @('dbatools','carbon','adsips','pswindowsupdate') 25 | foreach ($module in $modules) { 26 | $iv = $(Get-Module $module -ListAvailable | Select-Object -First 1 -ExpandProperty Version) -join '.' 27 | $gv = $(Find-Module $module | Select-Object -ExpandProperty Version) -join '.' 28 | [pscustomobject]@{ 29 | Module = $module 30 | Installed = $iv 31 | Gallery = $gv 32 | IsCurrent = $([version]$iv -ge [version]$gv) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /reserve/Test-Example.ps1: -------------------------------------------------------------------------------- 1 | function Test-Example { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Short_Description", 5 | [parameter()][string] $TestGroup = "configuration_or_operation", 6 | [parameter()][string] $TestCategory = "AD,CM,SQL,HOST", 7 | [parameter()][string] $Description = "Detailed_description_of_this_test", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | <# 18 | ======================================================= 19 | | COMMENT: DELETE THIS BLOCK WHEN FINISHED: 20 | | 21 | | perform test and return result as an object... 22 | | $stat = $except (no need to set "PASS" since it's the default) 23 | | $msg = "custom message that N issues were found" 24 | | add supporting data to $tempdata array if it helps output 25 | | loop output into $tempdata.Add() array to return as TestData param in output 26 | ======================================================= 27 | #> 28 | 29 | <# 30 | ======================================================= 31 | COMMENT: EXAMPLE FOR SQL QUERY RELATED TESTS... DELETE THIS BLOCK IF NOT USED 32 | ======================================================= 33 | 34 | $query = "" 35 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 36 | if ($res.Count -gt 0) { 37 | $stat = $except 38 | $msg = "$($res.Count) items returned" 39 | #$res | Foreach-Object {$tempdata.Add( [pscustomobject]@{Name=$_.Name} )} 40 | } 41 | ======================================================= 42 | #> 43 | 44 | <# 45 | ======================================================= 46 | COMMENT: EXAMPLE FOR WMI/CIM QUERY RELATED TESTS... DELETE THIS BLOCK IF NOT USED 47 | ======================================================= 48 | 49 | $disks = Get-WmiQueryResult -ClassName "Win32_LogicalDisk" -Query "DriveType = 3" -Params $ScriptParams 50 | foreach ($disk in $disks) { 51 | $drv = $disk.DeviceID 52 | $size = $disk.Size 53 | $free = $disk.FreeSpace 54 | $used = $size - $free 55 | $pct = $([math]::Round($used / $size, 1)) * 100 56 | if ($pct -gt $MaxPctUsed) { 57 | $stat = $except 58 | $msg = "One or more disks are low on free space" 59 | } 60 | $tempdata.Add( 61 | [pscustomobject]@{ 62 | Drive = $drv 63 | Size = $size 64 | Used = $used 65 | PctUsed = $pct 66 | MaxPct = $MaxPctUsed 67 | } 68 | ) 69 | } # foreach 70 | ======================================================= 71 | #> 72 | } 73 | catch { 74 | $stat = 'ERROR' 75 | $msg = $_.Exception.Message -join ';' 76 | } 77 | finally { 78 | Set-CmhOutputData 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /samples/Install-DbMaintenanceSolution.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Configure DB Maintenance Solution and Scheduling 4 | 5 | .DESCRIPTION 6 | Does the following magical stuff that will blow your mind: 7 | 8 | * Creates a new database for Database Maintenance plan 9 | * Ignores whiney complaints about being unsupported with the CM/SQL bundle licensing which I NEVER advised you to do anyway 10 | * Installs Ola's solution 11 | * Creates and schedules IndexOptimize task to optimize your indexes, or indices, I keep forgetting which is which 12 | 13 | Go on, admit it, your mind has been blown. It's okay. Everyone needs their mind blown once a day. 14 | 15 | .PARAMETER SQLInstance 16 | Name of SQL host instance 17 | 18 | .PARAMETER DBName 19 | Name of new maintenance database 20 | 21 | .EXAMPLE 22 | .\Install-DbMaintenanceSolution.ps1 -SQLInstance "cm01.contoso.local" -DBName "dba" 23 | 24 | .NOTES 25 | 8/27/2021 26 | Original mind-blowing part by: Steve Thompson - if you see him, tell him you love him more than beer! 27 | Doodling and silly comments by: David Stein - if you see him, tell him you love Steve Thompson more. 28 | #> 29 | [CmdletBinding()] 30 | [OutputType([pscustomobject])] 31 | param ( 32 | [parameter(Mandatory=$False)][string]$SQLInstance = "localhost", 33 | [parameter(Mandatory=$False)][string]$Database = "DBA" 34 | ) 35 | 36 | # Create a new database on the localhost named DBA 37 | $param = @{ 38 | SqlInstance = $SQLInstance 39 | Name = $Database 40 | Owner = "sa" 41 | RecoveryModel = "Simple" 42 | } 43 | New-DbaDatabase @param 44 | 45 | # Install Ola Hallengrens Database Maintenance solution using the DBA database 46 | $param = @{ 47 | SqlInstance = $SQLInstance 48 | Database = $Database 49 | ReplaceExisting =-"InstallJobs" 50 | } 51 | Install-DbaMaintenanceSolution @param 52 | 53 | # Create a new SQL Server Agent Job to schedule the custom Agent Task 54 | $param = @{ 55 | SqlInstance = $SQLInstance 56 | Job = "OptimizeIndexes" 57 | Owner = "sa" 58 | Description = "Ola Hallengren says you should Optimize your Indexes" 59 | } 60 | New-DbaAgentJob @param 61 | 62 | $sqlcmd = "EXECUTE dbo.IndexOptimize 63 | @Databases = 'USER_DATABASES', 64 | @FragmentationLow = NULL, 65 | @FragmentationMedium = 'INDEX_REORGANIZE,INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE', 66 | @FragmentationHigh = 'INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE', 67 | @FragmentationLevel1 = 10, 68 | @FragmentationLevel2 = 40, 69 | @UpdateStatistics = 'ALL', 70 | @OnlyModifiedStatistics = 'Y', 71 | @LogToTable = 'Y'" 72 | 73 | # Create a new SQL Agent Task step with the optimal parameters for MEMCM 74 | $param = @{ 75 | SqlInstance = $SQLInstance 76 | Job = "OptimizeIndexes" 77 | StepName = "Step1" 78 | Database = $Database 79 | Command = $sqlcmd 80 | } 81 | New-DbaAgentJobStep @param 82 | 83 | # Optionally, create a schedule to run the SQL Agent Tast once a week on Sunday @ 1:00AM 84 | $param = @{ 85 | SqlInstance = $SQLInstance 86 | Job = "OptimizeIndexes" 87 | Schedule = "RunWeekly" 88 | FrequencyType = "Weekly" 89 | FrequencyInterval = "Sunday" 90 | StartTime = "010000" 91 | Force = $True 92 | } 93 | New-DbaAgentSchedule @param 94 | -------------------------------------------------------------------------------- /tests/Test-AdSchemaExtension.ps1: -------------------------------------------------------------------------------- 1 | function Test-AdSchemaExtension { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Active Directory Schema Extended", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "AD", 8 | [parameter()][string] $Description = "Verify AD schema extensions have been installed", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | $startTime = (Get-Date) 12 | $stat = "PASS" 13 | $except = "FAIL" 14 | try { 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | Write-Log -Message "Verifying for AD Schema extension" 17 | $strFilter = "(&(objectClass=mSSMSSite)(Name=*))" 18 | $objDomain = New-Object System.DirectoryServices.DirectoryEntry 19 | $objSearcher = New-Object System.DirectoryServices.DirectorySearcher 20 | $objSearcher.SearchRoot = $objDomain 21 | $objSearcher.PageSize = 1000 22 | $objSearcher.Filter = $strFilter 23 | $objSearcher.SearchScope = "Subtree" 24 | $colProplist = "name" 25 | foreach ($i in $colProplist){$objSearcher.PropertiesToLoad.Add($i) | Out-Null} 26 | $colResults = $objSearcher.FindAll() 27 | if ($colResults.Count -gt 0) { 28 | Write-Log -Message "schema has been extended" 29 | foreach ($item in $colResults) { 30 | $obj = Get-ADSIObject -Identity $item.Path.Substring(7) 31 | $msg = "Active Directory schema has been extended for configmgr" 32 | $tempdata.Add( 33 | [pscustomobject]@{ 34 | ObjectName = $obj.name 35 | Container = $obj.adspath 36 | DateCreated = $obj.whencreated 37 | DateChanged = $obj.whenchanged 38 | Status = 'Extended' 39 | } 40 | ) 41 | } 42 | } else { 43 | $stat = $except 44 | $msg = "Active Directory schema has NOT been extended for configmgr" 45 | } 46 | } 47 | catch { 48 | $stat = 'ERROR' 49 | $msg = $_.Exception.Message -join ';' 50 | } 51 | finally { 52 | Set-CmhOutputData 53 | } 54 | } -------------------------------------------------------------------------------- /tests/Test-AdSysMgtContainer.ps1: -------------------------------------------------------------------------------- 1 | function Test-AdSysMgtContainer { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Active Directory System Management Container", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "AD", 8 | [parameter()][string] $Description = "Verify System Management container has been created with delegated permissions", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | $startTime = (Get-Date) 12 | $stat = "PASS" 13 | $except = "FAIL" 14 | $msg = "No issues found" 15 | try { 16 | Write-Log -Message "Searching for AD container: System Management" 17 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 18 | $res = Get-ADSIObject -Identity "System Management" -ErrorAction SilentlyContinue 19 | if ($null -eq $res) { 20 | $stat = $except 21 | $msg = "System Management container was not found in the current/active domain" 22 | } 23 | } 24 | catch { 25 | $stat = "ERROR" 26 | $msg = $_.Exception.Message -join ';' 27 | } 28 | finally { 29 | Set-CmhOutputData 30 | } 31 | } -------------------------------------------------------------------------------- /tests/Test-CmAppDeploymentExceptions.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmAppDeploymentExceptions { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Application Deployment Exceptions", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Summary of application deployment failures", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select 19 | ads.Descript AS DeploymentName, 20 | ads.TargetCollectionID, 21 | coll.Name AS CollectionName, 22 | ads.AssignmentID, 23 | ads.DeploymentTime, 24 | case when ads.OfferTypeID = 0 then 'Required' 25 | else 'Available' END AS OfferType, 26 | ads.AlreadyPresent, 27 | (ads.Success + ads.Error + ads.InProgress + ads.Unknown + ads.RequirementsNotMet) as Total, 28 | ads.Success, 29 | ads.InProgress, 30 | ads.Unknown, 31 | ads.Error, 32 | ads.RequirementsNotMet 33 | from v_AppDeploymentSummary as ads inner join 34 | v_Collection as coll on ads.TargetCollectionID = coll.CollectionID 35 | where (ads.Error > 0) 36 | order by DeploymentName" 37 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 38 | if ($res.Count -gt 0) { 39 | $stat = $except 40 | $msg = "$($res.Count) items found" 41 | $res | Foreach-Object { 42 | $tempdata.Add( 43 | [pscustomobject]@{ 44 | Deployment = $_.DeploymentName 45 | CollectionID = $_.CollectionID 46 | CollectionName = $_.CollectionName 47 | OfferType = $_.OfferType 48 | Total = $_.Total 49 | Success = $_.Success 50 | InProgress = $_.InProgress 51 | Unknown = $_.Unknown 52 | Failed = $_.Error 53 | } 54 | ) 55 | } 56 | } 57 | } 58 | catch { 59 | $stat = 'ERROR' 60 | $msg = $_.Exception.Message -join ';' 61 | } 62 | finally { 63 | Set-CmhOutputData 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Test-CmBoundariesOrphaned.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmBoundariesOrphaned { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Orphaned Site Boundaries", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Validate Site Boundaries are in Boundary Groups", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" 15 | $except = "FAIL" 16 | $msg = "No issues found" 17 | $query = "select * from vSMS_Boundary where GroupCount < 1 and DisplayName not like '%Default-First-Site-Name'" 18 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 19 | if ($res.Count -gt 1) { 20 | $stat = $except 21 | $msg = "$($res.Count) boundaries found not in a boundary group" 22 | $res | Foreach-Object { 23 | $tempdata.Add( 24 | [pscustomobject]@{ 25 | Name = $($_.DisplayName) 26 | Scope = $($_.Value) 27 | } 28 | ) 29 | } 30 | } 31 | } 32 | catch { 33 | $stat = 'ERROR' 34 | $msg = $_.Exception.Message -join ';' 35 | } 36 | finally { 37 | Set-CmhOutputData 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Test-CmBoundaryDuplicates.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmBoundaryDuplicates { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Duplicate Site Boundaries", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Validate Site Boundaries are not duplicated", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" 15 | $except = "FAIL" 16 | $msg = "No issues found" 17 | $query = "select * from vSMS_Boundary" 18 | $boundaries = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 19 | $dupes = @($boundaries | Group-Object -Property BoundaryType,Value | Select-Object Count,Name) 20 | if (($dupes | Where-Object {$_.Count -gt 1}) -gt 0) { 21 | $stat = $except 22 | foreach ($dupe in $dupes) { 23 | $tempData.Add([pscustomobject]@{Boundary=$($dupe.Name);Count=$($dupe.Count)}) 24 | } 25 | } 26 | } 27 | catch { 28 | $stat = 'ERROR' 29 | $msg = $_.Exception.Message -join ';' 30 | } 31 | finally { 32 | Set-CmhOutputData 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Test-CmClientATPStatus.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientATPStatus { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "ATP Client Onboarding and Activity Status", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "ATP Devices onboarded and active", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select distinct [ATP_OnboardingState] as ATPOnboard, Count(*) as Devices 19 | from v_CombinedDeviceResources where Name not in 20 | ('x86 Unknown Computer (x86 Unknown Computer)','x64 Unknown Computer (x64 Unknown Computer)', 21 | 'Provisioning Device (Provisioning Device)') 22 | group by [ATP_OnboardingState]" 23 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 24 | if ($res.Count -gt 0) { 25 | $onboard = $res | Where-Object {$_.ATPOnboard -eq 1} 26 | $total = $($res.Devices | Measure-Object -Sum | Select-Object -ExpandProperty Sum) 27 | $pending = $total - $onboard 28 | if (($onboard -gt 0) -and ($pending -gt 0)) { 29 | $stat = $except 30 | $msg = "$($res.Count) items found" 31 | $res | Foreach-Object { 32 | $tempdata.Add( 33 | [pscustomobject]@{ 34 | ATPOnboard = $_.ATPOnboard 35 | Devices = $_.Devices 36 | } 37 | ) 38 | } 39 | } 40 | } 41 | } 42 | catch { 43 | $stat = 'ERROR' 44 | $msg = "$($_.Exception.Message -join ';')" 45 | $msg += " / trace: $($_.ScriptStackTrace -join ';')" 46 | } 47 | finally { 48 | Set-CmhOutputData 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Test-CmClientAssignmentFailures.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientAssignmentFailures { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Client Assignment Failures", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for clients that failed site assignment", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT 18 | FQDN AS MachineNameFQDN, 19 | NetBiosName AS MachineName, 20 | ClientVersion AS ClientVersion, 21 | AssignedSiteCode AS SiteCode, 22 | AssignmentBeginTime AS AssignmentStartTime, 23 | StateDescription AS FailureDescription, 24 | LastMessageParam AS DescriptionParam, 25 | LastMessageStateID 26 | FROM v_ClientDeploymentState 27 | WHERE LastMessageStateID > 500 AND LastMessageStateID < 700" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | if ($null -ne $res -and $res.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($res.Count) items found: $($res.MachineName -join ',')" 32 | } 33 | } 34 | catch { 35 | $stat = 'ERROR' 36 | $msg = $_.Exception.Message -join ';' 37 | } 38 | finally { 39 | Set-CmhOutputData 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Test-CmClientBoundaryExceptions.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientBoundaryExceptions { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Check client boundary groups", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for Clients not in a boundary group", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT DISTINCT 19 | cdr.Name, CASE WHEN (BoundaryGroups IS NULL) THEN 'NONE' 20 | ELSE BoundaryGroups END AS BoundaryGroups, 21 | cdr.ADSiteName, cdr.DeviceOS, cdr.DeviceOSBuild, cdr.LastMPServerName, 22 | cdr.SerialNumber, cdr.MACAddress, cdr.LastLogonUser, cs.Model0 AS Model, cs.Manufacturer0 AS Manufacturer 23 | FROM v_CombinedDeviceResources AS cdr INNER JOIN 24 | v_GS_COMPUTER_SYSTEM AS cs ON cdr.MachineID = cs.ResourceID 25 | WHERE (cdr.Name NOT IN ('x86 Unknown Computer (x86 Unknown Computer)', 'x64 Unknown Computer (x64 Unknown Computer)', 'Provisioning Device (Provisioning Device)')) 26 | ORDER BY cdr.Name" 27 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 28 | $set2 = $res | Where-Object {$_.BoundaryGroups -eq 'NONE'} 29 | if ($set2.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($set2.Count) clients found without an assigned boundary group" 32 | Write-Log -Message $msg -Category Warning 33 | $set2 | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | Name = $_.Name 37 | OS = "$($_.DeviceOS) $($_.DeviceOSBuild)" 38 | Manufacturer = $_.Manufacturer 39 | Model = $_.Model 40 | ADSite = $_.ADSiteName 41 | SerialNumber = $_.SerialNumber 42 | MACAddress = $_.MACAddress 43 | LastUser = $_.LastLogonUser 44 | } 45 | ) 46 | } 47 | } 48 | } 49 | catch { 50 | $stat = 'ERROR' 51 | $msg = $_.Exception.Message -join ';' 52 | } 53 | finally { 54 | Set-CmhOutputData 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Test-CmClientCoverage.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientCoverage { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Device Client Coverage Status", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Confirm AD computers managed by CM", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int]$Coverage = Get-CmHealthDefaultValue -KeySet "configmgr:ClientCoverageThresholdPercent" -DataSet $CmHealthConfig 14 | Write-Log -Message "coverage threshold = $Coverage percent" 15 | $stat = "PASS" 16 | $except = "WARNING" 17 | $msg = "Coverage meets stated threshold of $Coverage percent" 18 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 19 | [array]$adcomps = Get-ADSIComputer | Select-Object -ExpandProperty name 20 | $adcount = $adcomps.Count 21 | Write-Log -Message "AD computers = $adcount" 22 | [array]$cmcomps = Get-CimInstance -ClassName "SMS_CombinedDeviceResources" -Namespace "root/sms/site_$($ScriptParams.SiteCode)" -ErrorAction Stop | 23 | Where-Object {$_.IsClient -eq $True} | Select-Object -ExpandProperty Name 24 | $cmcount = $cmcomps.Count 25 | Write-Log -Message "CM computers = $cmcount" 26 | if (($adcount -gt 0) -and ($cmcount -gt 0)) { 27 | $delta1 = $cmcomps | Where-Object {$_.name -notin $adcomps} # CM device names not in AD 28 | $delta2 = $adcomps | Where-Object {$_ -notin $cmcomps.name} # AD computer names not in CM 29 | Write-Log -Message "there are $($delta1.Count) computers in configmgr which are not in active directory" 30 | Write-Log -Message "there are $($delta2.Count) computers in active directory which are not in configmgr" 31 | if (($delta1.Count -gt 0) -or ($delta2.Count -gt 0)) { 32 | $stat = $except 33 | $msg = "discrepancies found between configmgr and active directory computer coverage" 34 | Write-Log -Message $msg -Category Warning 35 | $d1names = $delta1.name -join ',' 36 | $d2names = $delta2 -join ',' 37 | $tempdata.Add( 38 | [pscustomobject]@{ 39 | ADComputers = $($adcomps.Count) 40 | CMComputers = $($cmcomps.Count) 41 | OnlyAD = $d2names 42 | OnlyCM = $d1names 43 | NotInAD = $($delta1.Count) 44 | NotInCM = $($delta2.Count) 45 | } 46 | ) 47 | } 48 | } else { 49 | $stat = $except 50 | $msg = "Unable to query environment data to validate this test" 51 | } 52 | } 53 | catch { 54 | $stat = 'ERROR' 55 | $msg = $_.Exception.Message -join ';' 56 | } 57 | finally { 58 | Set-CmhOutputData 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Test-CmClientDeploymentFailures.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientDeploymentFailures { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Client Deployment Failures", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for failed client deployments", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT 18 | FQDN AS MachineNameFQDN, 19 | NetBiosName AS MachineName, 20 | ClientVersion AS ClientVersion, 21 | AssignedSiteCode AS SiteCode, 22 | DeploymentBeginTime AS DeployStartTime, 23 | StateDescription AS FailureDescription, 24 | LastMessageParam AS DescriptionParam, 25 | LastMessageStateID 26 | FROM v_ClientDeploymentState 27 | WHERE LastMessageStateID < 100 AND LastMessageStateID > 400" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | if ($null -ne $res -and $res.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($res.Count) items found: $($res.MachineName -join ',')" 32 | } 33 | } 34 | catch { 35 | $stat = 'ERROR' 36 | $msg = $_.Exception.Message -join ';' 37 | } 38 | finally { 39 | Set-CmhOutputData 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Test-CmClientErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientErrors { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Clients with Errors", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for clients reporting errors", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | stat.MachineName, 19 | fcm.SiteCode, 20 | stat.Component, 21 | stat.MessageID, 22 | stat.MessageType, 23 | stat.Severity 24 | FROM v_StatusMessage stat 25 | INNER JOIN v_FullCollectionMembership_Valid fcm ON fcm.Name = stat.MachineName 26 | WHERE stat.Time > DATEADD(dd,-CONVERT(INT,7),GETDATE()) and 27 | stat.Severity=0xC0000000 AND stat.PerClient!=0 AND fcm.CollectionID = 'SMS00001' 28 | ORDER BY stat.MachineName" 29 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 30 | if ($res.Count -gt 0) { 31 | $stat = $except 32 | $msg = "$($res.Count) clients have reported errors" 33 | $res | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | ComputerName = $_.MachineName 37 | Component = $_.Component 38 | MessageID = $_.MessageID 39 | MessageType = $_.MessageType 40 | Severity = $_.Severity 41 | SiteCode = $_.SiteCode 42 | } 43 | ) 44 | } 45 | } 46 | } 47 | catch { 48 | $stat = 'ERROR' 49 | $msg = $_.Exception.Message -join ';' 50 | } 51 | finally { 52 | Set-CmhOutputData 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Test-CmClientHealth.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientHealth { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Client Health Summary", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "ConfigMgr Client Health Status", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | # credit to Trevor Jones for the following query at https://smsagent.blog/2016/02/05/client-health-find-all-ccmeval-failed-or-unknown/ 19 | $query = "Select sys.Name0 as 'ComputerName', 20 | sys.User_Name0 as 'UserName', 21 | cs.ClientStateDescription, 22 | DATEDIFF(day,sys.Creation_Date0,cs.LastActiveTime) as 'DaysActive', 23 | DATEDIFF(day,cs.LastHealthEvaluation,GetDate()) as 'DaysSinceLastEval', 24 | sys.Creation_Date0 as 'ClientRegistrationDate', 25 | cs.LastActiveTime, 26 | cs.LastHealthEvaluation, 27 | case when LastEvaluationHealthy = 1 then 'Pass' 28 | when LastEvaluationHealthy = 2 then 'Fail' 29 | when LastEvaluationHealthy = 3 then 'Unknown' 30 | end as 'Last Evaluation Healthy', 31 | case when cs.ClientRemediationSuccess = 1 then 'Pass' 32 | when cs.ClientRemediationSuccess = 2 then 'Fail' 33 | else '' 34 | end as 'ClientRemediationSuccess', 35 | case when LastHealthEvaluationResult = 1 then 'Not Yet Evaluated' 36 | when LastHealthEvaluationResult = 2 then 'Not Applicable' 37 | when LastHealthEvaluationResult = 3 then 'Evaluation Failed' 38 | when LastHealthEvaluationResult = 4 then 'Evaluated Remediated Failed' 39 | when LastHealthEvaluationResult = 5 then 'Not Evaluated Dependency Failed' 40 | when LastHealthEvaluationResult = 6 then 'Evaluated Remediated Succeeded' 41 | when LastHealthEvaluationResult = 7 then 'Evaluation Succeeded' 42 | end as 'LastHealthEvaluationResult', 43 | HealthCheckDescription, 44 | ResultDetail, 45 | ResultCode 46 | from dbo.v_CH_ClientSummary cs 47 | inner join v_R_System sys on cs.ResourceID = sys.ResourceID 48 | left join v_CH_EvalResults eval on cs.ResourceID = eval.ResourceID 49 | where cs.ClientStateDescription in ('Active/Fail','Active/Unknown') 50 | and DATEDIFF(day,sys.Creation_Date0,cs.LastActiveTime) > 7 51 | Order by ClientStateDescription,ComputerName" 52 | [array]$res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 53 | if ($res.Count -gt 0) { 54 | $stat = $except 55 | $msg = "$($res.Count) clients are reporting as not healthy" 56 | } 57 | $res | Foreach-Object { 58 | $tempdata.Add( 59 | [pscustomobject]@{ 60 | ComputerName = $_.ComputerName 61 | UserName = $_.UserName 62 | LastEval = $_.'Last Evaluation Healthy' 63 | Remediation = $_.'ClientRemediationSuccess' 64 | EvalResult = $_.'LastHealthEvaluationResult' 65 | } 66 | ) 67 | } 68 | } 69 | catch { 70 | $stat = 'ERROR' 71 | $msg = $_.Exception.Message -join ';' 72 | } 73 | finally { 74 | Set-CmhOutputData 75 | } 76 | } -------------------------------------------------------------------------------- /tests/Test-CmClientInactive.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientInactive { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Inactive Clients", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for inactive clients", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | fcm.ResourceID, 19 | fcm.Name, 20 | CASE WHEN fcm.IsObsolete = 1 THEN '*' ELSE '' END AS Obsolete, 21 | CASE WHEN fcm.IsBlocked = 1 THEN '*' ELSE '' END AS Blocked, 22 | chs.LastActiveTime as LastContactTime, 23 | fcm.SiteCode 24 | FROM v_FullCollectionMembership fcm 25 | INNER JOIN v_CH_ClientSummary chs ON chs.ResourceID = fcm.ResourceID AND chs.ClientActiveStatus = 0 26 | WHERE fcm.CollectionID = 'SMS00001' 27 | order by fcm.Name" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | if ($res.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($res.Count) inactive clients were found" 32 | Write-Log -Message $msg 33 | $res | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | Name = $_.Name 37 | SiteCode = $_.SiteCode 38 | ResourceID = $_.ResourceID 39 | Blocked = $_.Blocked 40 | Obsolete = $_.Obsolete 41 | LastContact = $_.LastContactTime 42 | } 43 | ) 44 | } 45 | } 46 | } 47 | catch { 48 | $stat = 'ERROR' 49 | $msg = $_.Exception.Message -join ';' 50 | } 51 | finally { 52 | Set-CmhOutputData 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Test-CmClientInventoryMissing.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientInventoryMissing { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Clients Missing Inventory Data", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Clients with no inventory data", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int] $DaysOld = Get-CmHealthDefaultValue -KeySet "configmgr:MaxClientInventoryDaysOld" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT DISTINCT fcm.Name, 19 | sys.Client_Version0, 20 | fcm.Domain, 21 | sys.User_Name0, 22 | fcm.SiteCode, 23 | chs.LastActiveTime AS AgentTime, 24 | chs.LastHW AS LastHWScan, 25 | chs.LastSW AS LastScanDate 26 | FROM v_FullCollectionMembership fcm 27 | INNER JOIN v_R_System sys ON fcm.ResourceID = sys.ResourceID 28 | INNER JOIN v_CH_ClientSummary chs ON chs.ResourceID = fcm.ResourceID AND chs.ClientActiveStatus = 0 29 | WHERE fcm.CollectionID = 'SMS00001' AND 30 | chs.LastHW IS NULL 31 | ORDER BY fcm.Name" 32 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 33 | if ($res.Count -gt 0) { 34 | $stat = $except 35 | $msg = "$($res.Count) clients inventory older than $($DaysOld) days" 36 | Write-Log -Message $msg 37 | $res | Foreach-Object { 38 | $tempdata.Add( 39 | [pscustomobject]@{ 40 | Name = $_.Name 41 | Client = $_.Client_Version0 42 | LastHW = $_.LastHWScan 43 | LastActive = $_.AgentTime 44 | SiteCode = $_.SiteCode 45 | } 46 | ) 47 | } 48 | } 49 | } 50 | catch { 51 | $stat = 'ERROR' 52 | $msg = $_.Exception.Message -join ';' 53 | } 54 | finally { 55 | Set-CmhOutputData 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Test-CmClientInventoryStale.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientInventoryStale { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Stale Client Inventory Data", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Clients with outdated or missing inventory data", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int] $DaysOld = Get-CmHealthDefaultValue -KeySet "configmgr:MaxClientInventoryDaysOld" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT DISTINCT fcm.Name, 19 | sys.Client_Version0, 20 | fcm.Domain, 21 | sys.User_Name0, 22 | fcm.SiteCode, 23 | chs.LastActiveTime AS AgentTime, 24 | chs.LastHW AS LastHWScan, 25 | chs.LastSW AS LastScanDate 26 | FROM v_FullCollectionMembership fcm 27 | INNER JOIN v_R_System sys ON fcm.ResourceID = sys.ResourceID 28 | INNER JOIN v_CH_ClientSummary chs ON chs.ResourceID = fcm.ResourceID AND chs.ClientActiveStatus = 0 29 | WHERE fcm.CollectionID = 'SMS00001' AND 30 | chs.LastHW < DATEADD(dd,-CONVERT(INT,$($DaysOld)),GETDATE()) 31 | ORDER BY fcm.Name" 32 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 33 | if ($res.Count -gt 0) { 34 | $stat = $except 35 | $msg = "$($res.Count) clients inventory older than $($DaysOld) days" 36 | Write-Log -Message $msg 37 | $res | Foreach-Object { 38 | $tempdata.Add( 39 | [pscustomobject]@{ 40 | Name = $_.Name 41 | Client = $_.Client_Version0 42 | LastHW = $_.LastHWScan 43 | LastActive = $_.AgentTime 44 | SiteCode = $_.SiteCode 45 | } 46 | ) 47 | } 48 | } 49 | } 50 | catch { 51 | $stat = 'ERROR' 52 | $msg = $_.Exception.Message -join ';' 53 | } 54 | finally { 55 | Set-CmhOutputData 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Test-CmClientLowDiskSpace.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientLowDiskSpace { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Clients with Low Disk Space", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Clients with low disk space on C`: drive", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select * from ( 19 | select cdr.Name, 20 | cdr.DeviceOS, 21 | cdr.ADSiteName, 22 | cdr.LastLogonUser, 23 | cdr.LastMPServerName, 24 | ld.Name0 as Drive, 25 | ROUND((ld.Size0/1024),2) as SizeGB, 26 | ROUND((ld.FreeSpace0/1024),2) as FreeSpaceGB, 27 | ROUND(((ld.Size0 - ld.FreeSpace0)/1024),2) as UsedGB, 28 | ROUND(((ld.Size0 - ld.FreeSpace0) / CONVERT(decimal,ld.Size0)),2) as PctUsed 29 | from v_CombinedDeviceResources cdr 30 | inner join v_GS_LOGICAL_DISK ld on ld.ResourceID = cdr.MachineID 31 | where ld.DeviceID0 = 'C:') as t1 32 | where PctUsed > 0.8 33 | order by Name" 34 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 35 | if ($res.Count -gt 0) { 36 | $stat = $except 37 | $msg = "$($res.Count) clients were found having more than 80% full on C drive" 38 | Write-Log -Message $msg 39 | $res | Foreach-Object { 40 | $tempdata.Add( 41 | [pscustomobject]@{ 42 | Computer = $_.Name 43 | OS = $_.DeviceOS 44 | ADSite = $_.ADSiteName 45 | LastUser = $_.LastLogonUser 46 | LastMP = $_.LastMPServerName 47 | Drive = $_.Drive 48 | SizeMB = $_.SizeGB 49 | UsedMB = $_.UsedGB 50 | FreeMB = $_.FreeSpaceGB 51 | PctUsed = [math]::Round(($_.PctUsed * 100),1) 52 | } 53 | ) 54 | } 55 | } 56 | } 57 | catch { 58 | $stat = 'ERROR' 59 | $msg = $_.Exception.Message -join ';' 60 | } 61 | finally { 62 | Set-CmhOutputData 63 | } 64 | } -------------------------------------------------------------------------------- /tests/Test-CmClientMissing.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientMissing { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Missing Clients", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for discovered devices without a client", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | fcm.ResourceID, 19 | fcm.Name, 20 | fcm.SiteCode, 21 | fcm.Domain, 22 | sys.Operating_System_Name_and0 as OSName 23 | FROM v_FullCollectionMembership fcm 24 | INNER JOIN v_R_System sys ON fcm.ResourceID = sys.ResourceID 25 | WHERE fcm.IsClient != 1 AND fcm.Name NOT LIKE '%Unknown%' AND fcm.CollectionID = 'SMS00001' 26 | AND sys.Operating_System_Name_and0 IS NOT NULL AND sys.Operating_System_Name_and0 <> '' 27 | ORDER BY fcm.Name" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | if ($res.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($res.Count) devices are missing a ConfigMgr client" 32 | Write-Log -Message $msg 33 | $res | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | ComputerName = $($_.Name) 37 | ResourceID = $($_.ResourceID) 38 | Domain = $($_.Domain) 39 | OS = $($_.OSName) 40 | SiteCode = $($_.SiteCode) 41 | } 42 | ) 43 | } 44 | } 45 | } 46 | catch { 47 | $stat = 'ERROR' 48 | $msg = $_.Exception.Message -join ';' 49 | } 50 | finally { 51 | Set-CmhOutputData 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Test-CmClientOldVersion.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientOldVersion { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Old Client Versions", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for clients with version older than site version", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT fcm.Name, 18 | sys.Client_Version0 as ClientVersion, 19 | fcm.Domain, 20 | sys.User_Name0 as UserName, 21 | fcm.SiteCode, 22 | st.Version as SiteVersion 23 | FROM v_FullCollectionMembership_Valid fcm 24 | INNER JOIN v_R_System_Valid sys ON fcm.ResourceID = sys.ResourceID 25 | INNER JOIN v_Site st ON st.SiteCode = fcm.SiteCode 26 | WHERE fcm.CollectionID = 'SMS00001' AND sys.Client_Version0 < st.Version" 27 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 28 | $siteversion = $res.SiteVersion 29 | if ($res.Count -gt 0) { 30 | $stat = $except 31 | $msg = "$($res.Count) devices have a client installed older than site version: $($siteversion)" 32 | Write-Log -Message $msg 33 | $res | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | ComputerName = $($_.Name) 37 | SiteCode = $($_.SiteCode) 38 | Version = $($_.ClientVersion) 39 | SiteVersion = $($_.SiteVersion) 40 | UserName = $($_.UserName) 41 | } 42 | ) 43 | } 44 | } 45 | } 46 | catch { 47 | $stat = 'ERROR' 48 | $msg = $_.Exception.Message -join ';' 49 | } 50 | finally { 51 | Set-CmhOutputData 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Test-CmClientUpdateAgentVersion.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientUpdateAgentVersion { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Clients with Old Windows Update Agent", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Clients with old Windows Update agent", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | # reference: https://support.microsoft.com/en-us/help/949104/how-to-update-the-windows-update-agent-to-the-latest-version#:~:text=9600.16422.-,The%20latest%20version%20of%20the%20Windows%20Update%20Agent%20for%20Windows,7600.256. 12 | try { 13 | $startTime = (Get-Date) 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT DISTINCT 19 | rsys.Netbios_Name0 AS MachineName, 20 | rsys.Client_Version0, 21 | uss.LastWUAVersion, 22 | fcm.SiteCode 23 | FROM v_UpdateScanStatus uss WITH (NOLOCK) 24 | JOIN v_ClientCollectionMembers ccm WITH (NOLOCK) ON uss.ResourceID = ccm.ResourceID 25 | JOIN v_SoftwareUpdateSource sus WITH (NOLOCK) ON sus.UpdateSource_ID = uss.UpdateSource_ID 26 | JOIN v_R_System_Valid rsys WITH (NOLOCK) ON rsys.ResourceID = uss.ResourceID 27 | JOIN v_FullCollectionMembership_VaLID fcm WITH (NOLOCK) ON uss.ResourceID = fcm.ResourceID AND fcm.CollectionID = 'SMS00001' 28 | INNER JOIN v_GS_OPERATING_SYSTEM ops ON rsys.ResourceID = ops.ResourceID 29 | WHERE ops.Version0 < '10.0' AND uss.LastWUAVersion < '7.6.7600.256' 30 | UNION 31 | SELECT DISTINCT 32 | rsys.Netbios_Name0 as MachineName, 33 | rsys.Client_Version0, 34 | uss.LastWUAVersion, 35 | fcm.SiteCode 36 | FROM v_UpdateScanStatus uss WITH (NOLOCK) 37 | JOIN v_ClientCollectionMembers ccm WITH (NOLOCK) ON uss.ResourceID = ccm.ResourceID 38 | JOIN v_SoftwareUpdateSource sus WITH (NOLOCK) ON sus.UpdateSource_ID = uss.UpdateSource_ID 39 | JOIN v_R_System_Valid rsys WITH (NOLOCK) ON rsys.ResourceID = uss.ResourceID 40 | JOIN v_FullCollectionMembership_VaLID fcm WITH (NOLOCK) ON uss.ResourceID = fcm.ResourceID AND fcm.CollectionID = 'SMS00001' 41 | INNER JOIN v_GS_OPERATING_SYSTEM ops ON rsys.ResourceID = ops.ResourceID 42 | WHERE ops.Version0 like '6.2.%' AND uss.LastWUAVersion < '7.8.9200.16693' 43 | UNION 44 | SELECT DISTINCT 45 | rsys.Netbios_Name0 as MachineName, 46 | rsys.Client_Version0, 47 | uss.LastWUAVersion, 48 | fcm.SiteCode 49 | FROM v_UpdateScanStatus uss WITH (NOLOCK) 50 | JOIN v_ClientCollectionMembers ccm WITH (NOLOCK) ON uss.ResourceID = ccm.ResourceID 51 | JOIN v_SoftwareUpdateSource sus WITH (NOLOCK) ON sus.UpdateSource_ID = uss.UpdateSource_ID 52 | JOIN v_R_System_Valid rsys WITH (NOLOCK) ON rsys.ResourceID = uss.ResourceID 53 | JOIN v_FullCollectionMembership_VaLID fcm WITH (NOLOCK) ON uss.ResourceID = fcm.ResourceID AND fcm.CollectionID = 'SMS00001' 54 | INNER JOIN v_GS_OPERATING_SYSTEM ops ON rsys.ResourceID = ops.ResourceID 55 | WHERE ops.Version0 > '6.2' AND uss.LastWUAVersion < '7.9.9600.16422'" 56 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 57 | if ($res.Count -gt 0) { 58 | $stat = $except 59 | $msg = "$($res.Count) items found: $($res.MachineName -join ',')" 60 | $res | Foreach-Object { 61 | $tempdata.Add( 62 | [pscustomobject]@{ 63 | Machine = $_.MachineName 64 | SiteCode = $_.SiteCode 65 | WUAVersion = $_.LastWUAVersion 66 | } 67 | ) 68 | } 69 | } 70 | } 71 | catch { 72 | $stat = 'ERROR' 73 | $msg = $_.Exception.Message -join ';' 74 | } 75 | finally { 76 | Set-CmhOutputData 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Test-CmClientUpdateDeploymentErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientUpdateDeploymentErrors { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Client Update Deployment Errors", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for clients with software update deployment errors", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | sys.Name0 AS MachineName, 19 | sys.Client_Version0 AS SMSClientVersion, 20 | sys.User_Name0 AS LastLoggedOnUser, 21 | assc.LastEnforcementMessageTime AS LastEnforcementTime, 22 | assc.LastEnforcementErrorID & 0x0000FFFF AS ErrorStatusID, 23 | isnull(assc.LastEnforcementErrorCode,0) AS ErrorCode, 24 | fcm.SiteCode 25 | FROM v_CIAssignment cia WITH (NOLOCK) 26 | JOIN v_UpdateAssignmentStatus_Live assc WITH (NOLOCK) ON assc.AssignmentID = cia.AssignmentID 27 | JOIN v_R_System sys WITH (NOLOCK) ON assc.ResourceID=sys.ResourceID and isnull(sys.Obsolete0,0) <> 1 28 | JOIN v_FullCollectionMembership_Valid fcm WITH (NOLOCK) ON assc.ResourceID = fcm.ResourceID 29 | WHERE assc.LastEnforcementErrorID & 0x0000FFFF <> 0 AND 30 | assc.LastEnforcementMessageID IN (6,9) AND assc.IsCompliant=0 AND 31 | fcm.CollectionID = 'SMS00001' 32 | ORDER BY sys.Name0" 33 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 34 | if ($res.Count -gt 0) { 35 | $stat = $except 36 | $msg = "$($res.Count) clients with update deployment errors found" 37 | Write-Log -Message $msg 38 | $res | Foreach-Object { 39 | $tempdata.Add( 40 | [pscustomobject]@{ 41 | Computer = $_.MachineName 42 | Client = $_.SMSClientVersion 43 | LastUser = $_.LastLoggedOnUser 44 | ErrorCode = $_.ErrorCode 45 | HexCode = Convert-DecErrToHex -DecimalNumber $($_.ErrorCode) 46 | SiteCode = $_.SiteCode 47 | } 48 | ) 49 | } 50 | } 51 | } 52 | catch { 53 | $stat = 'ERROR' 54 | $msg = $_.Exception.Message -join ';' 55 | } 56 | finally { 57 | Set-CmhOutputData 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Test-CmClientUpdateScanErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmClientUpdateScanErrors { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Client Update Scan Errors", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for clients with software update scan errors", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT DISTINCT 18 | rsys.Name0 as MachineName, 19 | rsys.Client_Version0 as SMSClientVersion, 20 | uss.LastWUAVersion as WUAVersion, 21 | rsys.User_Name0 as LastLoggedOnUser, 22 | uss.LastStatusMessageID & 0x0000FFFF as ErrorStatusID, 23 | uss.LastErrorCode as LastErrorCode, 24 | LastScanTime, 25 | fcm.SiteCode 26 | from v_UpdateScanStatus uss with (NOLOCK) 27 | join v_ClientCollectionMembers ccm with (NOLOCK) on uss.ResourceID = ccm.ResourceID 28 | join v_SoftwareUpdateSource sus with (NOLOCK) on sus.UpdateSource_ID = uss.UpdateSource_ID 29 | join v_R_System rsys with (NOLOCK) on rsys.ResourceID = uss.ResourceID 30 | join v_FullCollectionMembership_Valid fcm with (NOLOCK) on uss.ResourceID = fcm.ResourceID 31 | where uss.LastStatusMessageID <> 0 and fcm.CollectionID = 'SMS00001' 32 | ORDER BY rsys.Name0" 33 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 34 | if ($res.Count -gt 0) { 35 | $stat = $except 36 | $msg = "$($res.Count) clients with update scan errors found" 37 | Write-Log -Message $msg 38 | $res | Foreach-Object { 39 | $tempdata.Add( 40 | [pscustomobject]@{ 41 | ComputerName = $_.MachineName 42 | Client = $_.SMSClientVersion 43 | WUAVersion = $_.WUAVersion 44 | LastUser = $_.LastLoggedOnUser 45 | ErrorCode = $_.LastErrorCode 46 | HexCode = Convert-DecErrToHex -DecimalNumber $($_.LastErrorCode) 47 | } 48 | ) 49 | } 50 | } 51 | } 52 | catch { 53 | $stat = 'ERROR' 54 | $msg = $_.Exception.Message -join ';' 55 | } 56 | finally { 57 | Set-CmhOutputData 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Test-CmCollectionRefresh.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | Adapted from example: 4 | Source: https://model-technology.com/blog/troubleshooting-slow-collection-evaluation-in-sccm-2012-part-3-aka-how-to-identify-collection-update-loitering/ 5 | By original author: Steve Bowman 6 | #> 7 | function Test-CmCollectionRefresh { 8 | [CmdletBinding()] 9 | [OutputType()] 10 | param ( 11 | [parameter()][string] $TestName = "Collection Refresh Performance", 12 | [parameter()][string] $TestGroup = "configuration", 13 | [parameter()][string] $TestCategory = "CM", 14 | [parameter()][string] $Description = "Validate Collections refresh impact on performance", 15 | [parameter()][hashtable] $ScriptParams 16 | ) 17 | try { 18 | $startTime = (Get-Date) 19 | [int]$maxcolls = Get-CmHealthDefaultValue -KeySet "configmgr:MaxCollectionRefreshCount" -DataSet $CmHealthConfig 20 | Write-Log -Message "max collections allowed = $maxcolls" 21 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 22 | $stat = "PASS" 23 | $except = "WARNING" 24 | $msg = "No issues found" 25 | $query = "Select 26 | case 27 | when RefreshType = 1 then 'Manual' 28 | when RefreshType = 2 then 'Scheduled' 29 | when RefreshType = 4 then 'Incremental' 30 | when RefreshType = 6 then 'Scheduled and Incremental' 31 | else 'Unknown' 32 | end as RefreshType, 33 | SiteID, CollectionName, MemberCount, 34 | case when CollectionType = 2 then 'Device' 35 | else 'User' 36 | end as CollectionType 37 | from dbo.v_Collections 38 | where RefreshType in (2,4,6) 39 | order by CollectionName" 40 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 41 | if ($res.Count -gt $maxcolls) { 42 | $stat = $except 43 | $msg = "$($cc.Count) collections are set to incremental and/or scheduled refresh." 44 | $msg += "Maximum recommended limit is $maxcolls" 45 | Write-Log -Message $msg 46 | $res | Foreach-Object { 47 | $tempdata.Add( 48 | [pscustomobject]@{ 49 | ID = $_.SiteID 50 | Name = $_.CollectionName 51 | Type = $_.CollectionType 52 | RefreshType = $_.RefreshType 53 | Members = $_.MemberCount 54 | } 55 | ) 56 | } 57 | } 58 | } 59 | catch { 60 | $stat = 'ERROR' 61 | $msg = $_.Exception.Message -join ';' 62 | } 63 | finally { 64 | Set-CmhOutputData 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Test-CmComponentErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmComponentErrors { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Site Component Errors", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Site Component Errors and Warnings", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "Select 19 | ComponentName, 20 | ComponentType, 21 | Case 22 | when Status = 0 then 'OK' 23 | when Status = 1 then 'Warning' 24 | when Status = 2 then 'Critical' 25 | End as 'Status', 26 | Case 27 | when State = 0 then 'Stopped' 28 | when State = 1 then 'Started' 29 | when State = 2 then 'Paused' 30 | when State = 3 then 'Installing' 31 | when State = 4 then 'Re-installing' 32 | when State = 5 then 'De-installing' 33 | End as 'State', 34 | Case 35 | When AvailabilityState = 0 then 'Online' 36 | When AvailabilityState = 3 then 'Offline' 37 | When AvailabilityState = 4 then 'Unknown' 38 | End as 'AvailabilityState', 39 | Infos, 40 | Warnings, 41 | Errors 42 | from vSMS_ComponentSummarizer 43 | where TallyInterval = N'0001128000100008' 44 | and MachineName = 'vm-sccmsite-01.uhs.med' 45 | and SiteCode = 'S02' 46 | and Status in (1,2) 47 | Order by Status,ComponentName" 48 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 49 | if ($res.Count -gt 0) { 50 | $stat = $except 51 | $msg = "$($res.Count) component status issues found" 52 | $res | Foreach-Object { 53 | $tempdata.Add( 54 | [pscustomobject]@{ 55 | ComponentName = $_.ComponentName 56 | ComponentType = $_.ComponentType 57 | Status = $_.Status 58 | State = $_.State 59 | AvailabilityState = $_.AvailabilityState 60 | Info = $_.Infos 61 | Warnings = $_.Warnings 62 | Errors = $_.Errors 63 | } 64 | ) 65 | } 66 | } 67 | } 68 | catch { 69 | $stat = 'ERROR' 70 | $msg = $_.Exception.Message -join ';' 71 | } 72 | finally { 73 | Set-CmhOutputData 74 | } 75 | } -------------------------------------------------------------------------------- /tests/Test-CmComponentStatusMessages.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmComponentStatusMessages { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Component Status Exceptions", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Get component status exception messages", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int] $DaysBack = Get-CmHealthDefaultValue -KeySet "configmgr:ComponentErrorsMaxDaysOld" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT DISTINCT 19 | sm.Component, sm.MessageID, sm.MachineName, sm.Severity, sm.MessageType, sma.AttributeValue 20 | FROM dbo.vStatusMessages AS sm LEFT OUTER JOIN 21 | dbo.v_StatMsgAttributes AS sma ON sm.RecordID = sma.RecordID 22 | WHERE (sm.Severity IN (-1073741824, -2147483648)) AND 23 | (sm.Component NOT IN ('Advanced Client', 'Windows Installer SourceList Update Agent', 24 | 'Desired Configuration Management', 'Software Updates Scan Agent', 25 | 'File Collection Agent', 'Hardware Inventory Agent', 26 | 'Software Distribution', 'Software Inventory Agent')) AND 27 | (sm.Time >= DATEADD(dd, -CONVERT(INT,$($DaysBack)), GETDATE()))" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | Write-Log -Message "returned $($res.Count) items" 30 | if ($res.Count -gt 0) { 31 | $stat = $except 32 | $msg = "$($res.Count) items found within the last $DaysBack days" 33 | Write-Log -Message $msg 34 | $res | Foreach-Object { 35 | Write-Log -Message "$($_.Component) - $($_.MessageID) - $($_.MachineName)" 36 | $tempdata.Add( 37 | [pscustomobject]@{ 38 | Component = $_.Component 39 | MessageID = $_.MessageID 40 | ComputerName = $_.MachineName 41 | Severity = $_.Severity 42 | MessageType = $_.MessageType 43 | Attribute = $_.AttributeValue 44 | } 45 | ) 46 | } 47 | } else { 48 | Write-Log -Message "no results were returned" 49 | } 50 | } 51 | catch { 52 | $stat = 'ERROR' 53 | $msg = $_.Exception.Message -join ';' 54 | } 55 | finally { 56 | Set-CmhOutputData 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Test-CmComponentStatusSummary.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmComponentStatusSummary { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Component Status Summary", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check Component Status summary counts", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" 15 | $except = "FAIL" 16 | $msg = "No issues found" 17 | $query = "SELECT 18 | ComponentName, Errors, Infos, Warnings, 19 | CASE WHEN Status = 0 THEN 'OK' 20 | WHEN Status = 1 THEN 'Warning' 21 | WHEN Status = 2 THEN 'Critical' 22 | END AS Status 23 | FROM v_ComponentSummarizer 24 | WHERE 25 | TallyInterval='0001128000100008' AND 26 | SiteCode = '$($ScriptParams.SiteCode)' AND 27 | Errors > 0" 28 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 29 | if ($res.Count -gt 0) { 30 | $c1 = $($res | Where-Object Status -eq 'Critical').Count 31 | $c2 = $($res | Where-Object Status -eq 'Warning').Count 32 | if ($c1 -gt 0) { 33 | $stat = $except 34 | } elseif ($c2 -gt 0) { 35 | $stat = 'WARNING' 36 | } 37 | $msg = "Component status since 12:00 = $c1 critical, $c2 warning out of $($res.count) total" 38 | $res | Foreach-Object { 39 | $tempdata.Add( 40 | [pscustomobject]@{ 41 | Component = $_.ComponentName 42 | Errors = $_.Errors 43 | Status = $_.Status 44 | } 45 | ) 46 | } 47 | } 48 | } 49 | catch { 50 | $stat = 'ERROR' 51 | $msg = $_.Exception.Message -join ';' 52 | } 53 | finally { 54 | Set-CmhOutputData 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Test-CmContentDistErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmContentDistErrors { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Content Distribution Errors", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check for content with distribution errors", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" 15 | $except = "WARNING" 16 | $msg = "No issues found" 17 | $query = "SELECT 18 | cds.PkgID as PackageID, pkg.Name, pkg.Version, cds.TargeteddDPCount, 19 | cds.NumberInstalled, cds.NumberInProgress, cds.NumberErrors, pkg.PackageType 20 | FROM dbo.v_ContDistStatSummary AS cds INNER JOIN 21 | dbo.v_Package AS pkg ON cds.PkgID = pkg.PackageID 22 | WHERE (cds.NumberErrors > 0)" 23 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 24 | if ($res.Count -gt 1) { 25 | $stat = $except 26 | $msg = "$($res.Count) packages with distribution errors" 27 | $res | Foreach-Object {$tempdata.Add("PkgID=$($_.PkgID),Name=$($_.Name),Errors=$($_.NumberErrors)")} 28 | } 29 | } 30 | catch { 31 | $stat = 'ERROR' 32 | $msg = $_.Exception.Message -join ';' 33 | } 34 | finally { 35 | Set-CmhOutputData 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Test-CmContentMissing.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmContentMissing { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Packages Missing Content", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for items missing content for distribution", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $query = "SELECT 17 | SourceSite, 18 | SoftwareName, 19 | Targeted, 20 | NumberInstalled, 21 | NumberErrors, 22 | NumberInProgress, 23 | NumberUnknown, 24 | CASE ObjectType 25 | WHEN 0 THEN 'Package' 26 | WHEN 3 THEN 'Driver Package' 27 | WHEN 5 THEN 'Software Update Package' 28 | WHEN 257 THEN 'Operating System Image' 29 | WHEN 258 THEN 'Boot Image' 30 | WHEN 259 THEN 'Operating System Installer' 31 | WHEN 512 THEN 'Application' 32 | ELSE 'Unknown ID ' + CONVERT(VARCHAR(200), ObjectType) 33 | END AS ObjectTypeName 34 | FROM fn_ListObjectContentExtraInfo(1033) AS SMS_ObjectContentExtraInfo 35 | WHERE Targeted > 0 AND NumberInstalled <> Targeted" 36 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 37 | if ($null -ne $res -and $res.Count -gt 0) { 38 | $stat = $except 39 | $msg = "$($res.Count) items missing content: $($res.SoftwareName -join ',')" 40 | $res | Foreach-Object { 41 | $tempdata.Add( 42 | [pscustomobject]@{ 43 | Name = $($_.SoftwareName) 44 | Type = $($_.ObjectTypeName) 45 | Errors = $($_.NumberErrors) 46 | } 47 | ) 48 | } 49 | } 50 | } 51 | catch { 52 | $stat = 'ERROR' 53 | $msg = $_.Exception.Message -join ';' 54 | } 55 | finally { 56 | Set-CmhOutputData 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Test-CmContentNotDistributed.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmContentNotDistributed { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Content Not Distributed", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for content not distributed", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $query = "SELECT 17 | SourceSite, 18 | SoftwareName, 19 | CASE ObjectType 20 | WHEN 0 THEN 'Package' 21 | WHEN 3 THEN 'Driver Package' 22 | WHEN 5 THEN 'Software Update Package' 23 | WHEN 257 THEN 'Operating System Image' 24 | WHEN 258 THEN 'Boot Image' 25 | WHEN 259 THEN 'Operating System Installer' 26 | WHEN 512 THEN 'Application' 27 | ELSE 'Unknown ID ' + CONVERT(VARCHAR(200), ObjectType) 28 | END AS ObjectTypeName 29 | FROM fn_ListObjectContentExtraInfo(1033) AS SMS_ObjectContentExtraInfo 30 | WHERE Targeted = 0 31 | ORDER BY SoftwareName" 32 | $res = @(Invoke-DbaQuery -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Query $query) 33 | if ($res.Count -gt 1) { 34 | $stat = $except 35 | $msg = "$($res.Count) packages with content are not distributed" 36 | $res | Foreach-Object { 37 | $tempdata.Add( 38 | [pscustomobject]@{ 39 | Name = $_.SoftwareName 40 | PackageType = $_.ObjectTypeName 41 | SourceSite = $_.SourceSite 42 | } 43 | ) 44 | } 45 | } 46 | } 47 | catch { 48 | $stat = 'ERROR' 49 | $msg = $_.Exception.Message -join ';' 50 | } 51 | finally { 52 | Set-CmhOutputData 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Test-CmDPDiskSpace.ps1: -------------------------------------------------------------------------------- 1 | function Test-CMDPDiskSpace { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Distribution Point Disk Space", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check disk space status on all DPs", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxPctUsed = Get-CmHealthDefaultValue -KeySet "siteservers:DiskSpaceMaxPercent" -DataSet $CmHealthConfig 13 | Write-Log -Message "MaxPctUsed = $MaxPctUsed" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $issues = 0 19 | Write-Log -Message "requesting list of DP servers" 20 | $query = "select ServerName from dbo.v_DistributionPointInfo order by ServerName" 21 | [array]$dplist = Invoke-DbaQuery -SqlInstance $SqlInstance -Database $Database -Query $query | Select-Object -ExpandProperty ServerName 22 | Write-Log -Message "$($dplist.Count) DP server names returned" 23 | [string]$myFQDN=(Get-CimInstance Win32_ComputerSystem).DNSHostName+"."+(Get-CimInstance Win32_ComputerSystem).Domain 24 | [int]$index=1 25 | foreach ($dp in $dplist) { 26 | $res = $null; $cs = $null 27 | if ($dp -ne $myFQDN) { 28 | Write-Log -Message "connecting to remote DP [$index of $($dplist.Count)]: $dp" 29 | $cs = New-CimSession -Credential $ScriptParams.Credential -Authentication Negotiate -ComputerName $dp -ErrorAction SilentlyContinue 30 | if ($null -ne $cs) { 31 | $res = @(Get-CimInstance -CimSession $cs -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -ErrorAction SilentlyContinue) 32 | } else { 33 | $res = $null 34 | } 35 | } else { 36 | Write-Log -Message "connecting to local DP [$index of $($dplist.Count)]: $dp" 37 | $res = @(Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3") 38 | } 39 | if ($res.Count -gt 0) { 40 | foreach ($disk in $res) { 41 | $size = $disk.Size 42 | $free = $disk.FreeSpace 43 | $used = $size - $free 44 | $pct = $([math]::Round($used / $size, 1)) * 100 45 | if ($pct -gt $MaxPctUsed) { 46 | $tempData.Add( 47 | [pscustomobject]@{ 48 | Computer = $($dp) 49 | Drive = $($disk.DeviceID) 50 | SizeGB = [math]::Round($size / 1GB, 1) 51 | PctUsed = $pct 52 | } 53 | ) 54 | $stat = $except 55 | $issues++ 56 | } else { 57 | $tempData.Add( 58 | [pscustomobject]@{ 59 | Computer = $($dp) 60 | Drive = $($disk.DeviceID) 61 | SizeGB = [math]::Round($size / 1GB, 1) 62 | PctUsed = $pct 63 | } 64 | ) 65 | } 66 | } # foreach 67 | if ($issues -gt 0) { $msg = "$issues issues were found" } 68 | } else { 69 | Write-Warning "DP disk information is not available: $dp" 70 | $tempData.Add( 71 | [pscustomobject]@{ 72 | Computer = $($dp) 73 | Drive = $null 74 | SizeGB = $null 75 | PctUsed = $null 76 | } 77 | ) 78 | } 79 | $index++ 80 | } # foreach 81 | } 82 | catch { 83 | $stat = 'ERROR' 84 | $msg = $_.Exception.Message -join ';' 85 | } 86 | finally { 87 | Set-CmhOutputData 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Test-CmDatabaseSize.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmDatabaseSize { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Database Size", 5 | [parameter()][string] $TestGroup = "database", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Validate CM site database file size", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$maxUtilization = Get-CmHealthDefaultValue -KeySet "sqlserver:DatabaseFileSizeMaxPercent" -DataSet $CmHealthConfig 13 | [int]$PerDevData = Get-CmHealthDefaultValue -KeySet "sqlserver:DataSizePerCMClientMB" -DataSet $CmHealthConfig 14 | $maxUtilization = $maxUtilization * 0.1 15 | $devData = $PerDevData * 1MB 16 | Write-Log -Message "Max Utilization Percent = $maxUtilization" 17 | Write-Log -Message "Per Device Data MB = $PerDevData" 18 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 19 | $stat = "PASS" 20 | $except = "WARNING" 21 | $msg = "Correct configuration" 22 | $query = "select distinct ResourceID,Name0 from v_R_System" 23 | $devices = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 24 | Write-Log -Message "calculating expected space requirements" 25 | $devSizeMB = (($devices.Count * $devData) + $devData) / 1MB 26 | $recSize = $devSizeMB * $maxUtilization 27 | Write-Log -Message "expected space: $devSizeMB MB (at $($devices.Count) devices)" 28 | if ($null -ne $ScriptParams.Credential) { 29 | $dbSizeMB = (Get-DbaDatabase -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -SqlCredential $ScriptParams.Credential).SizeMB 30 | } else { 31 | $dbSizeMB = (Get-DbaDatabase -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database).SizeMB 32 | } 33 | Write-Log -Message "rounding result to precision 2" 34 | $dbSizeMB = [math]::Round($dbSizeMB,2) 35 | Write-Log -Message "actual space: $dbSizeMB MB" 36 | $pct = [math]::Round(($devSizeMB / $dbSizeMB) * 100, 1) 37 | Write-Log -Message "actual utilization: $pct`%" 38 | if ($pct -gt $recSize) { 39 | $stat = $except 40 | $msg = "Current DB size is $dbSizeMB MB ($pct percent of recommended). Recommended: $recSize MB" 41 | } else { 42 | $msg = "Current DB size is $dbSizeMB MB ($pct percent of recommended). Recommended: $recSize MB" 43 | } 44 | } 45 | catch { 46 | $stat = 'ERROR' 47 | $msg = $_.Exception.Message -join ';' 48 | } 49 | finally { 50 | Set-CmhOutputData 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Test-CmDpNoGroups.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmDpNoGroups { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "DP Servers Not in a DP Group", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for DPs which are not in a DP group", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "WARNING" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT ServerName FROM v_DistributionPointInfo WHERE GroupCount < 1" 17 | if ($null -ne $ScriptParams.Credential) { 18 | $res = @(Invoke-DbaQuery -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Query $query -SqlCredential $ScriptParams.Credential) 19 | } else { 20 | $res = @(Invoke-DbaQuery -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Query $query) 21 | } 22 | if ($null -ne $res -and $res.Count -gt 0) { 23 | $stat = $except 24 | $msg = "$($res.Count) items found: $($res.ServerName -join ',')" 25 | $res | Foreach-Object {$tempdata.Add($_.ServerName)} 26 | } 27 | } 28 | catch { 29 | $stat = 'ERROR' 30 | $msg = $_.Exception.Message -join ';' 31 | } 32 | finally { 33 | Set-CmhOutputData 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Test-CmInvMaxMIFSize.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmInvMaxMIFSize { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Max MIF File Size", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Validate inventory loader maximum MIF file size setting", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxMIF = Get-CmHealthDefaultValue -KeySet "configmgr:MaxMIFSizeRegistryValue" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $res = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\SMS\COMPONENTS\SMS_INVENTORY_DATA_LOADER" -Name "Max MIF Size" | Select-Object -ExpandProperty "Max MIF Size") 18 | if ($res -lt $MaxMIF) { 19 | $stat = $except 20 | $msg = "Max MIF size is $res (hex) which should be 3200000 (hex) or $MaxMIF. Refer to https://www.recastsoftware.com/resources/change-the-maximum-file-size-of-a-mif" 21 | $tempdata.Add( 22 | [pscustomobject]@{ 23 | CurrentMax = "$res (hex)" 24 | Recommended = "3200000 ($MaxMIF hex)" 25 | } 26 | ) 27 | } 28 | } 29 | catch { 30 | $stat = 'ERROR' 31 | $msg = $_.Exception.Message -join ';' 32 | } 33 | finally { 34 | Set-CmhOutputData 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Test-CmMpResponse.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmMpResponse { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Management Point Response", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Validate MP web service reponse", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | $startTime = (Get-Date) 11 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 12 | $stat = "PASS" 13 | $except = "FAIL" 14 | $msg = "No issues found" 15 | $query = "SELECT srs.ServerName,srs.SiteCode,vs.SiteName,vst.AD_Site_Name0 as ADSite, 16 | vs.ReportingSiteCode as Parent,vs.Installdir 17 | FROM v_SystemResourceList as srs 18 | LEFT JOIN v_site vs on srs.ServerName = vs.ServerName 19 | LEFT JOIN v_R_System_Valid vst on LEFT(srs.ServerName, CHARINDEX('.', srs.ServerName) - 1) = vst.Netbios_Name0 20 | WHERE srs.RoleName = 'SMS Management Point' 21 | ORDER BY srs.ServerName" 22 | $servers = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | foreach ($server in $servers.ServerName) { 24 | $URL1 = "http://$Server/sms_mp/.sms_aut?mpcert" 25 | $URL2 = "http://$Server/sms_mp/.sms_aut?mplist" 26 | Write-Log -Message "submitting mp requests: $URL1 $URL2" 27 | $WEBObject1 = [System.Net.WebRequest]::Create($URL1) 28 | $WEBObject2 = [System.Net.WebRequest]::Create($URL2) 29 | $WEBObject1.AuthenticationLevel = "None" 30 | $WEBObject2.AuthenticationLevel = "None" 31 | $WEBObject1.Timeout = 7000 32 | $WEBObject2.Timeout = 7000 33 | try { 34 | $WEBResponse1 = $WEBObject1.GetResponse() 35 | $MpcertStatus = $WEBResponse1.StatusCode 36 | $MpcertStatusCode = ($WEBResponse1.Statuscode -as [int]) 37 | $WEBResponse1.Close() 38 | $WEBResponse2 = $WEBObject2.GetResponse() 39 | $MplistStatus = $WEBResponse2.StatusCode 40 | $MplistStatusCode = ($WEBResponse2.Statuscode -as [int]) 41 | $WEBResponse2.Close() 42 | if (($MpcertStatusCode -ne "200") -or ($MplistStatusCode -ne "200")) { 43 | $stat = $except 44 | $msg = "Invalid web response" 45 | } 46 | $tempdata.Add( 47 | [pscustomobject]@{ 48 | SiteServer = $server 49 | MPCertStatus = $MpcertStatusCode 50 | MPCertUrl = $URL1 51 | MPListStatus = $MplistStatusCode 52 | MPListUrl = $URL2 53 | } 54 | ) 55 | } 56 | catch { 57 | $MpcertStatus = $_.Exception.Response.StatusCode 58 | $MpcertStatusCode = ( $_.Exception.Response.StatusCode -as [int]) 59 | $MplisttStatus = $_.Exception.Response.StatusCode 60 | $MplisttStatusCode = ( $_.Exception.Response.StatusCode -as [int]) 61 | $stat = "ERROR" 62 | $msg = "Response: MPCERT $($Mpcertstatus) $($MpcertStatusCode) / MPLIST $($MplisttStatus) $($MplisttStatusCode)" 63 | } 64 | } # foreach 65 | 66 | Set-CmhOutputData 67 | } 68 | -------------------------------------------------------------------------------- /tests/Test-CmPackageDistErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmPackageDistErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Package Distribution Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for Packages with content distribution errors", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "FAIL" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT 17 | pkg.PackageID, pkg.Name, cds.NumberInstalled, cds.NumberInProgress, 18 | cds.NumberErrors, pkg.Description, pkg.PkgSourcePath 19 | FROM dbo.v_ContDistStatSummary AS cds INNER JOIN 20 | dbo.v_Package AS pkg ON cds.PkgID = pkg.PackageID 21 | WHERE (cds.NumberErrors > 0)" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | if ($null -ne $res -and $res.Count -gt 0) { 24 | $stat = $except 25 | $msg = "$($res.Count) items found: $($res.Name -join ',')" 26 | $res | Foreach-Object { 27 | $tempdata.Add( 28 | [pscustomobject]@{ 29 | ID = $($_.PackageID) 30 | Name = $($_.Name) 31 | Errors = $($_.NumberErrors) 32 | SourcePath = $($_.PkgSourcePath) 33 | } 34 | ) 35 | } 36 | } 37 | } 38 | catch { 39 | $stat = 'ERROR' 40 | $msg = $_.Exception.Message -join ';' 41 | } 42 | finally { 43 | Set-CmhOutputData 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Test-CmQueryExpensiveRule.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmQueryExpensiveRule { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Expensive Query Membership Rules", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for queries which are processing-intensive", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $query = "select CollectionName,SiteID,QueryName,SQL,WQL 18 | from dbo.Collection_Rules_SQL c1 19 | INNER JOIN dbo.Collection_Rules c2 ON c1.CollectionID = c2.CollectionID 20 | INNER JOIN v_Collections c3 ON c1.CollectionID = c3.CollectionID 21 | where c3.SiteID not like 'SMS%'" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | foreach ($row in $res) { 24 | if ($row.SQL -match "'%" -and $row.SQL -match "%'") { 25 | $stat = $except 26 | $tempdata.Add( 27 | [pscustomobject]@{ 28 | QueryName = $row.QueryName 29 | Collection = $row.CollectionName 30 | CollectionID = $row.SiteID 31 | Message = "LIKE with a leading and trailing wildcard" 32 | Query = $row.WQL 33 | } 34 | ) 35 | } elseif ($row.SQL -match "'%") { 36 | $stat = $except 37 | $tempdata.Add( 38 | [pscustomobject]@{ 39 | QueryName = $row.QueryName 40 | Collection = $row.CollectionName 41 | CollectionID = $row.SiteID 42 | Message = "LIKE with a leading wildcard" 43 | Query = $row.WQL 44 | } 45 | ) 46 | } 47 | } # foreach 48 | if ($tempdata.Count -gt 0) { 49 | $stat = $except 50 | $msg = "$($tempdata.Count) expensive queries were found" 51 | } 52 | } 53 | catch { 54 | $stat = 'ERROR' 55 | $msg = $_.Exception.Message -join ';' 56 | } 57 | finally { 58 | Set-CmhOutputData 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Test-CmSiteAdminsCount.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteAdminsCount { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Site Admins Membership Count", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check if more users are full admins than should be", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int]$defaultSetting = Get-CmHealthDefaultValue -KeySet "configmgr:MaxAdministratorsCount" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "select LogonName,DisplayName,IsGroup,AdminSID from v_Admins" 19 | [array]$res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 20 | if ($res.Count -gt $defaultSetting) { 21 | $stat = $except 22 | $msg = "$($res.Count) items found" 23 | $res | Foreach-Object { 24 | $tempdata.Add( 25 | [pscustomobject]@{ 26 | LogonName = $_.LogonName 27 | DisplayName = $_.Displayname 28 | IsGroup = $_.IsGroup 29 | SID = $_.AdminSID 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | Set-CmhOutputData 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Test-CmSiteCertificates.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteCertificates { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Site Certificate Expirations", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check certificate expiration dates", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [int]$expdays = 30 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | $stat = "PASS" # do not change this 17 | $except = "WARNING" # or "FAIL" 18 | $msg = "No issues found" # do not change this either 19 | $query = "SELECT SiteCode,RoleID,RoleName,State,Configuration,MessageID,LastEvaluatingTime,Param1 20 | FROM dbo.vCM_SiteConfiguration where RoleName like '%Certificate'" 21 | Write-Log -Message "submitting query" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | Write-Log -Message "returned $($res.Count) certificate records" 24 | $ecount = 0 25 | foreach ($row in $res) { 26 | [string]$cfg = $($row.Configuration -replace "`n",",") 27 | [datetime]$exp = $($cfg -split 'Expires:')[1].Trim() 28 | Write-Log -Message "expiration date is $exp" 29 | if ((New-TimeSpan -Start (Get-Date) -End $exp).Days -lt $expdays) { 30 | Write-Verbose "expiration less than $expdays days" 31 | $stat = $except 32 | $msgx = "Certificate about to expire or has expired" 33 | $ecount++ 34 | } else { 35 | $msgx = "Valid" 36 | } 37 | $tempdata.Add( 38 | [pscustomobject]@{ 39 | RoleName = $row.RoleName 40 | Details = $msgx 41 | Configuration = $row.Configuration 42 | Expiration = $exp 43 | } 44 | ) 45 | } 46 | if ($ecount -gt 0) { 47 | $stat = $except 48 | $msg = "$($ecount) of $($res.Count) certificates expired or will expire within $expdays days" 49 | } 50 | } 51 | catch { 52 | $stat = 'ERROR' 53 | $msg = $_.Exception.Message -join ';' 54 | } 55 | finally { 56 | Set-CmhOutputData 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Test-CmSiteInstallAccountRoles.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteInstallAccountRoles { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "ConfigMgr Install Account Roles and Permissions", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check if site install account has appropriate permissions/roles", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | [array]$localAdmins = Get-LocalGroupMember -Group "Administrators" -ErrorAction Stop 19 | [array]$sysadmins = Get-DbaServerRoleMember -SqlInstance $ScriptParams.SqlInstance -ServerRole "sysadmin" -ErrorAction Stop 20 | 21 | $query = "SELECT TOP (1) LogonName FROM dbo.vRBAC_Permissions WHERE CategoryID = 'SMS00ALL'" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | $isLocalAdmin = $False 24 | $isDomainAdmin = $False 25 | $isEntAdmin = $False 26 | $isSchemaAdmin = $False 27 | $isSysAdmin = $False 28 | if ($null -ne $res) { 29 | [string]$msg = @() 30 | $username = $res.LogonName 31 | $basename = $($username -split '\\')[1] 32 | if ($localAdmins.Name -contains $username) { 33 | Write-Log -Message "install account is a direct member of local Administrators group" 34 | $isLocalAdmin = $True 35 | } else { 36 | Write-Log -Message "install account is not a member of local Administrators group" 37 | $stat = $except 38 | $msg += "install account is not a local Administrators group member" 39 | } 40 | if ($sysadmins -contains $username) { 41 | Write-Log -Message "install account is a direct member of SQL sysadmins group" 42 | $isSysAdmin = $True 43 | } 44 | $dagroup = Get-ADSIGroupMember -Identity "Domain Admins" | Select-Object -expand name 45 | if ($dagroup -contains $basename) { 46 | Write-Log -Message "install account is a direct member of Domain Admins group" 47 | $isDomainAdmin = $True 48 | $stat = $except 49 | $msg += "install account is in Domain Admins group" 50 | } 51 | $eagroup = Get-ADSIGroupMember -Identity "Enterprise Admins" | Select-Object -expand name 52 | if ($eagroup -contains $basename) { 53 | Write-Log -Message "install account is a direct member of Enterprise Admins group" 54 | $isEntAdmin = $True 55 | $stat = $except 56 | $msg += "install account is in Enterprise Admins group" 57 | } 58 | $sagroup = Get-ADSIGroupMember -Identity "Schema Admins" | Select-Object -expand name 59 | if ($sagroup -contains $basename) { 60 | Write-Log -Message "install account is a direct member of Schema Admins group" 61 | $isSchemaAdmin = $True 62 | $stat = $except 63 | $msg += "install account is in Schema Admins group" 64 | } 65 | $tempdata.Add([pscustomobject]@{ 66 | InstallAccount = $username 67 | IsLocalAdmin = $isLocalAdmin 68 | IsDomainAdmin = $isDomainAdmin 69 | IsEnterpriseAdmin = $isEntAdmin 70 | IsSchemaAdmin = $isSchemaAdmin 71 | IsSqlSysAdmin = $isSysAdmin 72 | }) 73 | } else { 74 | Write-Warning "unable to query site installation account from database" 75 | } 76 | } 77 | catch { 78 | $stat = 'ERROR' 79 | $msg += $_.Exception.Message -join ';' 80 | } 81 | finally { 82 | Set-CmhOutputData 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Test-CmSiteLastBackup.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteLastBackup { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Last Site Backup", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Validate last ConfigMgr site backup status", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | [int]$DaysBack = Get-CmHealthDefaultValue -KeySet "sqlserver:SiteBackupMaxDaysOld" -DataSet $CmHealthConfig 14 | Write-Log -Message "threshold (days back) = $DaysBack" 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | $stat = "PASS" 17 | $except = "FAIL" 18 | $msg = "No issues found" 19 | $query = "DECLARE @starttime as DATETIME, 20 | @endtime AS DATETIME, @id as INT, @sitecode CHAR(3), @numberofdays INT 21 | SET @sitecode = '$($ScriptParams.SiteCode)' 22 | SET @numberofdays = $($DaysBack) 23 | SELECT TOP 1 @starttime = smsgs.Time 24 | FROM v_StatusMessage smsgs 25 | WHERE 26 | smsgs.Time >= DATEADD(dd,-CONVERT(INT,@NumberofDays),GETDATE()) AND 27 | smsgs.MessageID = 5055 AND 28 | smsgs.sitecode = @sitecode 29 | ORDER BY smsgs.Time DESC 30 | 31 | SELECT TOP 1 @endtime = smsgs.Time, @id = smsgs.MessageID 32 | FROM v_StatusMessage smsgs 33 | WHERE 34 | smsgs.Time >= DATEADD(dd,-CONVERT(INT,@NumberofDays),GETDATE()) and 35 | smsgs.MessageID IN (5035, 5000, 5002, 5004, 5006, 5008, 5017, 5018, 5019, 5022, 5024, 5025, 5026, 5027, 5032, 5033, 5043, 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053) AND 36 | smsgs.sitecode = @sitecode 37 | ORDER BY smsgs.Time DESC 38 | 39 | IF (@starttime IS NOT NULL) 40 | SELECT @starttime AS StartTime, 41 | CASE 42 | WHEN (@starttime > @endtime) THEN NULL 43 | ELSE @endtime 44 | END AS EndTime, 45 | CASE 46 | WHEN (@starttime > @endtime) THEN 'Last Backup did not finish' 47 | WHEN (@endtime is NULL) THEN 'Last Backup did not finish' 48 | WHEN (@id = 5035) THEN 'SMS Site Backup completed successfully with zero errors but still there could be some warnings' 49 | WHEN (@id != 5035) THEN 'SMS Site Backup failed to completed successfully' 50 | END AS 'Comments'" 51 | #Write-Log -Message "submitting the following query to the SQL instance:" 52 | #Write-Log -Message $query 53 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 54 | if ($null -eq $res) { 55 | $stat = $except 56 | $msg = "No backup status found. Verify backups are enabled." 57 | } else { 58 | if ($res.Comments -notmatch "completed successfully with zero") { 59 | $stat = $except 60 | $msg = "$($res.Comments)" 61 | } else { 62 | $msg = $res.Comments 63 | } 64 | } 65 | } 66 | catch { 67 | $stat = 'ERROR' 68 | $msg = $_.Exception.Message -join ';' 69 | } 70 | finally { 71 | Set-CmhOutputData 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Test-CmSiteMaintenanceTasks.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteMaintenanceTasks { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Check Site Maintenance Tasks", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Check site maintenance task settings", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | $MaxDisabled = 2 14 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | $stat = "PASS" # do not change this 17 | $except = "WARNING" # or "FAIL" 18 | $msg = "No issues found" # do not change this either 19 | $query = "SELECT TaskName, 20 | CASE IsEnabled WHEN 1 THEN 'YES' 21 | ELSE 'NO' 22 | END AS IsEnabled, 23 | CASE DeleteOlderThan WHEN 0 THEN NULL ELSE DeleteOlderThan END AS DeleteOlderThan, 24 | CASE BeginTime WHEN 0 THEN '0:00' ELSE LEFT(CONVERT(VARCHAR(4), BeginTime),(CASE WHEN (LEN(BeginTime)-2) <=0 THEN 0 ELSE (LEN(BeginTime)-2) END)) + ':' + RIGHT(CONVERT(VARCHAR(4), BeginTime),2) END AS BeginTime, 25 | CASE LatestBeginTime WHEN 0 THEN '0:00' ELSE LEFT(CONVERT(VARCHAR(4), LatestBeginTime),(CASE WHEN (LEN(LatestBeginTime)-2) <=0 THEN 0 ELSE (len(LatestBeginTime)-2) END)) + ':' + RIGHT(CONVERT(VARCHAR(4), LatestBeginTime),2) END AS LatestBeginTime, 26 | ISNULL((CASE CAST(DaysOfWeek & 1 as bit) WHEN 1 THEN 'Sunday, ' END), '') + 27 | ISNULL((CASE CAST(DaysOfWeek & 2 as bit) WHEN 1 THEN 'Monday, ' END), '') + 28 | ISNULL((CASE CAST(DaysOfWeek & 4 as bit) WHEN 1 THEN 'Tuesday, ' END), '') + 29 | ISNULL((CASE CAST(DaysOfWeek & 8 as bit) WHEN 1 THEN 'Wednesday, ' END), '') + 30 | ISNULL((CASE CAST(DaysOfWeek & 16 as bit) WHEN 1 THEN 'Thursday, ' END), '') + 31 | ISNULL((CASE CAST(DaysOfWeek & 32 as bit) WHEN 1 THEN 'Friday, ' END), '') + 32 | ISNULL((CASE CAST(DaysOfWeek & 64 as bit) WHEN 1 THEN 'Saturday' END), '') AS DaysOfWeek 33 | FROM vSMS_SC_SQL_Task WHERE SiteCode = '$($ScriptParams.SiteCode)'" 34 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 35 | $disabled = $res | Where-Object {$_.IsEnabled -ne 'YES'} 36 | if ($disabled.Count -gt $MaxDisabled) { 37 | $stat = $except 38 | } 39 | $msg = "$($disabled.Count) of $($res.Count) maintenance tasks are disabled" 40 | $res | Foreach-Object { 41 | $tempdata.Add( 42 | [pscustomobject]@{ 43 | TaskName = $_.TaskName 44 | Enabled = $_.IsEnabled 45 | DeleteOlderThan = $_.DeleteOlderThan 46 | StartTime = $_.BeginTime 47 | LatestStart = $_.LatestBeginTime 48 | DaysOfWeek = $_.DaysOfWeek 49 | } 50 | ) 51 | } 52 | } 53 | catch { 54 | $stat = 'ERROR' 55 | $msg = $_.Exception.Message -join ';' 56 | } 57 | finally { 58 | Set-CmhOutputData 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Test-CmSiteStatusMessages.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmSiteStatusMessages { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Site Status Messages", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Site status messages with recent errors", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $res = Get-SiteStatusMessages -Params $ScriptParams 18 | $count1 = $($res | Where-Object {$_.Severity -eq 'Error'}).Count 19 | $count2 = $($res | Where-Object {$_.Severity -eq 'Warning'}).Count 20 | #$xcount = ($res | Where-Object {$_.Severity -in ('Error','Warning')}).Count 21 | if ($($count1+$count2) -gt 0) { 22 | $stat = $except 23 | $msg = "Site status message counts: Error=$($count1), Warning=$($count2). Total=$($count1+$count2). Review Monitoring > System Status for more details" 24 | $res | Foreach-Object { $tempdata.Add( $_ ) } 25 | } 26 | } 27 | catch { 28 | $stat = 'ERROR' 29 | $msg = $_.Exception.Message -join ';' 30 | } 31 | finally { 32 | Set-CmhOutputData 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Test-CmUnknownDevices.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUnknownDevices { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Check for Unknown Devices", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Devices named UNKNOWN* or MININT* left from failed imaging events", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $query = "SELECT sys.Name0, sys.ResourceID, cdr.SMSID, cdr.MACAddress, cdr.SerialNumber 18 | FROM dbo.v_R_System AS sys LEFT OUTER JOIN 19 | dbo.v_CombinedDeviceResources AS cdr ON sys.ResourceID = cdr.MachineID 20 | WHERE (sys.Name0 LIKE '%Unknown%') OR (sys.Name0 LIKE 'MININT%') 21 | ORDER BY sys.Name0" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | if ($res.Count -gt 0) { 24 | $stat = $except 25 | $msg = "$($res.Count) items returned" 26 | $res | Foreach-Object { 27 | $tempdata.Add( 28 | [pscustomobject]@{ 29 | Name = $_.Name0 30 | SMSID = $_.SMSID 31 | MAC = $_.MACAddress 32 | SerialNumber = $_.SerialNumber 33 | } 34 | ) 35 | } 36 | } 37 | } 38 | catch { 39 | $stat = 'ERROR' 40 | $msg = $_.Exception.Message -join ';' 41 | } 42 | finally { 43 | Set-CmhOutputData 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Test-CmUpdateADRErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUpdateADRErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "ADR Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Check for ADR Rule Errors", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "WARNING" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT Name, LastRunTime, LastErrorCode, LastErrorTime FROM vSMS_AutoDeployments WHERE LastErrorCode IS NOT NULL" 17 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 18 | if ($null -ne $res -and $res.Count -gt 0) { 19 | $stat = $except 20 | $msg = "$($res.Count) items found: $($res.Name -join ',')" 21 | $res | Foreach-Object { 22 | $tempdata.Add( 23 | [pscustomobject]@{ 24 | Name = $($_.Name) 25 | LastError = $($_.LastErrorCode) 26 | } 27 | ) 28 | } 29 | } 30 | } 31 | catch { 32 | $stat = 'ERROR' 33 | $msg = $_.Exception.Message -join ';' 34 | } 35 | finally { 36 | Set-CmhOutputData 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Test-CmUpdateCompliance.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUpdateCompliance { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Software Update Compliance Summary", 6 | [parameter()][string] $TestGroup = "operation", 7 | [parameter()][string] $TestCategory = "CM", 8 | [parameter()][string] $Description = "Summary of required updates not yet installed", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = @" 19 | SELECT 20 | vRS.Netbios_Name0 AS 'DeviceName', 21 | vSN_Status.StateDescription, 22 | vCCI.CategoryInstanceName AS 'UpdateClassification', 23 | vUI.Title, 24 | vUI.Description, 25 | vUI.InfoURL, 26 | vUI.ArticleID, 27 | vUI.BulletinID, 28 | vUI.MaxExecutionTime, 29 | vUCS.LastStatusChangeTime, 30 | vUCS.LastErrorCode 31 | FROM 32 | dbo.v_Update_ComplianceStatus AS vUCS LEFT OUTER JOIN 33 | dbo.v_UpdateInfo AS vUI ON vUCS.CI_ID = vUI.CI_ID LEFT OUTER JOIN 34 | dbo.v_R_System AS vRS ON vUCS.ResourceID = vRS.ResourceID LEFT OUTER JOIN 35 | dbo.v_StateNames AS vSN_Status ON vUCS.Status = vSN_Status.StateID LEFT OUTER JOIN 36 | dbo.v_CICategoryInfo AS vCCI ON vCCI.CI_ID = vUCS.CI_ID 37 | WHERE 38 | (LTRIM(vRS.Netbios_Name0) <> '') AND 39 | (vCCI.CategoryTypeName = 'UpdateClassification') AND 40 | (vSN_Status.TopicType = 500) AND 41 | (vUI.CIType_ID = 8) AND 42 | (vUI.IsSuperseded = 0) AND 43 | (vCCI.CategoryInstanceName IN ('Security Updates', 'Critical Updats')) AND 44 | (vSN_Status.StateDescription = 'Update is required') 45 | ORDER BY 46 | 'DeviceName', vUI.Title 47 | "@ 48 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 49 | if ($res.Count -gt 0) { 50 | $stat = $except 51 | $msg = "$($res.Count) items found" 52 | $res | Foreach-Object { 53 | $dataset = [pscustomobject]@{ 54 | DeviceName = $($_.DeviceName) 55 | UpdateClassification = $($_.UpdateClassification) 56 | State = $($_.StateDescription) 57 | Article = $($_.ArticleID) 58 | Title = $($_.Title) 59 | LastError = $($_.LastErrorCode) 60 | } 61 | $tempdata.Add($dataset) 62 | } 63 | } else { 64 | Write-Log -Message "no issues found" 65 | } 66 | } 67 | catch { 68 | $stat = 'ERROR' 69 | $msg = $_.Exception.Message -join ';' 70 | } 71 | finally { 72 | Set-CmhOutputData 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Test-CmUpdateDeploymentErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUpdateDeploymentErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Software Update Deployment Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Update Deployment Error Messages", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "WARNING" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT DISTINCT 17 | fcm.ResourceID, 18 | fcm.Name, 19 | sys.AD_Site_Name0 as ADSiteName, 20 | sys.User_Name0 as UserName, 21 | ISNULL(assc.LastEnforcementErrorCode,0) AS ErrorCode, 22 | ISNULL(assc.LastEnforcementErrorCode,0) AS Message 23 | FROM v_CIAssignment cia WITH (NOLOCK) 24 | JOIN v_UpdateAssignmentStatus_Live assc WITH (NOLOCK) ON assc.AssignmentID = cia.AssignmentID 25 | JOIN v_R_System sys WITH (NOLOCK) ON assc.ResourceID=sys.ResourceID AND ISNULL(sys.Obsolete0,0) <> 1 26 | JOIN v_FullCollectionMembership_Valid fcm WITH (NOLOCK) ON assc.ResourceID = fcm.ResourceID 27 | WHERE assc.LastEnforcementErrorID & 0x0000FFFF <> 0 AND 28 | assc.LastEnforcementMessageID in (6,9) AND 29 | assc.IsCompliant=0 AND fcm.CollectionID = 'SMS00001' 30 | ORDER BY fcm.Name" 31 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 32 | if ($res.Count -gt 0) { 33 | $stat = $except 34 | $msg = "$($res.Count) deployment errors found" 35 | $res | Foreach-Object { 36 | $tempdata.Add( 37 | [pscustomobject]@{ 38 | Resource = $($_.ResourceID) 39 | Computer = $($_.Name) 40 | UserName = $($_.UserName) 41 | ADSite = $($_.ADSiteName) 42 | ErrorCode = $($_.ErrorCode) 43 | HexCode = Convert-DecErrToHex -DecimalNumber $($_.ErrorCode) 44 | } 45 | ) 46 | } 47 | } 48 | } 49 | catch { 50 | $stat = 'ERROR' 51 | $msg = $_.Exception.Message -join ';' 52 | } 53 | finally { 54 | Set-CmhOutputData 55 | } 56 | } -------------------------------------------------------------------------------- /tests/Test-CmUpdateErrorSolutions.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUpdateErrorSolutions { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Software Update Error Solutions", 5 | [parameter()][string] $TestGroup = "operations", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Update Error Solution Details", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "WARNING" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT DISTINCT 17 | assc.LastEnforcementErrorCode as ErrorCode, 18 | assc.LastEnforcementMessageID as Message 19 | FROM v_CIAssignment cia WITH (NOLOCK) 20 | JOIN v_UpdateAssignmentStatus_Live assc WITH (NOLOCK) on assc.AssignmentID = cia.AssignmentID 21 | JOIN v_R_System sys WITH (NOLOCK) on assc.ResourceID=sys.ResourceID AND ISNULL(sys.Obsolete0,0) <> 1 22 | JOIN v_FullCollectionMembership_Valid fcm WITH (NOLOCK) on assc.ResourceID = fcm.ResourceID 23 | WHERE assc.LastEnforcementErrorID <> 0 24 | AND assc.LastEnforcementMessageID in (6,9) 25 | AND assc.IsCompliant=0 26 | AND fcm.CollectionID = 'SMS00001'" 27 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 28 | if ($null -ne $res -and $res.Count -gt 0) { 29 | $stat = $except 30 | $msg = "$($res.Count) items were found" 31 | $res | Foreach-Object { 32 | $tempdata.Add( 33 | [pscustomobject]@{ 34 | Error = $($_.ErrorCode) 35 | Message = $($_.Message) 36 | } 37 | ) 38 | } 39 | } 40 | } 41 | catch { 42 | $stat = 'ERROR' 43 | $msg = $_.Exception.Message -join ';' 44 | } 45 | finally { 46 | Set-CmhOutputData 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Test-CmUpdateScanErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmUpdateScanErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Clients with Update Scan Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Update scanning errors", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" # do not change this 14 | $except = "WARNING" 15 | $msg = "No issues found" # do not change this either 16 | $query = "SELECT DISTINCT 17 | uss.ResourceID, uss.ScanTime, uss.LastScanState, uss.LastErrorCode, uss.LastWUAVersion, cdr.Name 18 | FROM v_UpdateScanStatus AS uss INNER JOIN 19 | v_CombinedDeviceResources AS cdr ON uss.ResourceID = cdr.MachineID 20 | WHERE (uss.LastErrorCode <> 0) 21 | ORDER BY cdr.Name" 22 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 23 | if ($res.Count -gt 0) { 24 | $stat = $except 25 | $msg = "$($res.Count) update scan errors were found" 26 | $res | Foreach-Object { 27 | $tempdata.Add( 28 | [pscustomobject]@{ 29 | Name = $_.Name 30 | ID = $_.ResourceID 31 | Error = $_.LastErrorCode 32 | LastScan = $_.ScanTime 33 | } 34 | ) 35 | } 36 | } 37 | } 38 | catch { 39 | $stat = 'ERROR' 40 | $msg = $_.Exception.Message -join ';' 41 | } 42 | finally { 43 | Set-CmhOutputData 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Test-CmWsusLastSync.ps1: -------------------------------------------------------------------------------- 1 | function Test-CmWsusLastSync { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Last SUP WSUS Synchronization", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "CM", 7 | [parameter()][string] $Description = "Validate last WSUS synchronization result", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$DaysBack = Get-CmHealthDefaultValue -KeySet "wsus:LastSyncMaxDaysOld" -DataSet $CmHealthConfig 13 | Write-Log -Message "DaysBack = $DaysBack" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No sync errors within the past $DaysBack days" 18 | $query = "DECLARE @starttime AS DATETIME, @endtime AS DATETIME, @id AS INT, @sitecode CHAR(3) 19 | SELECT @sitecode = '$($ScriptParams.SiteCode)' 20 | SELECT TOP 1 @starttime = smsgs.Time 21 | FROM v_StatusMessage smsgs 22 | WHERE 23 | smsgs.Time >= DATEADD(dd,-CONVERT(INT,$($DaysBack)),GETDATE()) AND 24 | smsgs.MessageID = 6701 AND 25 | smsgs.sitecode = @sitecode 26 | ORDER BY smsgs.Time DESC 27 | 28 | SELECT TOP 1 @endtime = smsgs.Time, @id = smsgs.MessageID 29 | FROM v_StatusMessage smsgs 30 | WHERE 31 | smsgs.Time >= DATEADD(dd,-CONVERT(INT,$($DaysBack)),GETDATE()) AND 32 | smsgs.MessageID IN (6702, 6703) AND 33 | smsgs.sitecode = @sitecode 34 | ORDER BY smsgs.Time DESC 35 | 36 | IF (@starttime IS NOT NULL) AND (@endtime IS NOT NULL) 37 | SELECT @starttime as StartTime, 38 | CASE 39 | WHEN (@starttime > @endtime) THEN NULL 40 | ELSE @endtime 41 | END AS EndTime, 42 | CASE 43 | WHEN (@starttime > @endtime) THEN 'Last WSUS Sync did not finish' 44 | WHEN (@id = 6702) THEN 'Success' 45 | WHEN (@id = 6703) THEN 'Error' 46 | END AS 'Comments'" 47 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 48 | if ($null -eq $res) { 49 | throw "No status found. Confirm SUP and WSUS are configured." 50 | } else { 51 | if ($res.Comments -ne 'Success') { 52 | $stat = $except 53 | $msg = "Status = $($res.Comments). Refer to https://damgoodadmin.com/2018/10/17/latest-software-maintenance-script-making-wsus-suck-slightly-less/" 54 | } 55 | } 56 | } 57 | catch { 58 | $stat = 'ERROR' 59 | $msg = $_.Exception.Message -join ';' 60 | } 61 | finally { 62 | Set-CmhOutputData 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Test-HostAntiVirus.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostAntiVirus { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "AntiVirus Product Installations", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check for third-party antivirus software installations", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $apps = Get-WmiQueryResult -ClassName "Win32_Product" -Query "" -Params $ScriptParams 18 | $apps | Foreach-Object { 19 | $appname = $_.Name 20 | foreach ($pn in ('McAfee','Sophos','Symantec','antivirus','malware','security','endpoint')) { 21 | if ($appname -match $pn) { 22 | Write-Log -Message "match found: $appname" 23 | $tempdata.Add( 24 | [pscustomobject]@{ 25 | ProductName = $_.Name 26 | Vendor = $_.Vendor 27 | Version = $_.Version 28 | DisplayName = $_.Caption 29 | } 30 | ) 31 | } 32 | } # foreach 33 | } 34 | $msg = "For more information, refer to https://docs.microsoft.com/en-us/troubleshoot/mem/configmgr/recommended-antivirus-exclusions" 35 | } 36 | catch { 37 | $stat = 'ERROR' 38 | $msg = $_.Exception.Message -join ';' 39 | } 40 | finally { 41 | Set-CmhOutputData 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Test-HostAppLogErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostAppLogErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Application Event Log Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check for recent Application log errors and warnings", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int] $MaxHours = 24 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = @" 19 | 20 | 21 | 22 | 23 | 24 | "@ 25 | [array]$computers = $ScriptParams.ComputerName 26 | if ($ScriptParams.ComputerName -ne $ScriptParams.SqlInstance) { 27 | $computers += $ScriptParams.SqlInstance 28 | } 29 | $count1 = 0 # errors 30 | $count2 = 0 # warnings 31 | foreach ($computer in $computers) { 32 | Write-Log -Message "computer: $computer" 33 | if ($computer -ne $env:COMPUTERNAME) { 34 | if ($ScriptParams.Credential) { 35 | $res = @(Get-WinEvent -LogName Application -FilterXPath $query -ComputerName $computer -Credential $ScriptParams.Credential -ErrorAction SilentlyContinue) 36 | } else { 37 | $res = @(Get-WinEvent -LogName Application -FilterXPath $query -ComputerName $computer -ErrorAction SilentlyContinue) 38 | } 39 | } else { 40 | $computer = $env:COMPUTERNAME 41 | $res = @(Get-WinEvent -LogName Application -FilterXPath $query -ErrorAction SilentlyContinue) 42 | } 43 | $vwarnings = $res | Where-Object {$_.LevelDisplayName -eq 'Warning'} 44 | $verrors = $res | Where-Object {$_.LevelDisplayName -eq 'Error'} 45 | $count1 += $verrors.Count 46 | $count2 += $vwarnings.Count 47 | if ($vwarnings.Count -gt 0) { 48 | $stat = $except 49 | $vwarnings | ForEach-Object { 50 | $tempdata.Add( 51 | [pscustomobject]@{ 52 | Computer = $_.MachineName 53 | Level = $_.LevelDisplayName 54 | ID = $_.Id 55 | Provider = $_.ProviderName 56 | Log = $_.LogName 57 | TimeCreated = $_.TimeCreated 58 | Message = $_.Message 59 | } 60 | ) 61 | } 62 | } 63 | if ($verrors.Count -gt 0) { 64 | $stat = $except 65 | $res | Foreach-Object { 66 | $tempdata.Add( 67 | [pscustomobject]@{ 68 | Computer = $_.MachineName 69 | Level = $_.LevelDisplayName 70 | ID = $_.Id 71 | Provider = $_.ProviderName 72 | Log = $_.LogName 73 | TimeCreated = $_.TimeCreated 74 | Message = $_.Message 75 | } 76 | ) 77 | } 78 | } 79 | } # foreach 80 | $msg = "$($count1) Errors and $($count2) Warnings occurred in the Application log within the past $MaxHours hours" 81 | } 82 | catch { 83 | $stat = 'ERROR' 84 | $msg = $_.Exception.Message -join ';' 85 | } 86 | finally { 87 | Set-CmhOutputData 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Test-HostDiskSpace.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostDiskSpace { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Disk Space Health", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate logical disk space health", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxPctUsed = Get-CmHealthDefaultValue -KeySet "siteservers:DiskSpaceMaxPercent" -DataSet $CmHealthConfig 13 | Write-Log -Message "MaxPctUsed = $MaxPctUsed" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No issues found" 18 | [array]$disks = Get-WmiQueryResult -ClassName "Win32_LogicalDisk" -Query "DriveType=3" -Params $ScriptParams 19 | foreach ($disk in $disks) { 20 | $drv = $disk.DeviceID 21 | $size = $disk.Size 22 | $free = $disk.FreeSpace 23 | $used = $size - $free 24 | $pct = $([math]::Round($used / $size, 1)) * 100 25 | if ($pct -gt $MaxPctUsed) { 26 | $stat = $except 27 | $msg = "One or more disks are low on free space" 28 | } 29 | $tempdata.Add( 30 | [pscustomobject]@{ 31 | Drive = $drv 32 | SizeGB = [math]::Round($size / 1GB, 1) 33 | UsedGB = [math]::Round($used / 1GB, 1) 34 | PctUsed = $pct 35 | MaxPct = $MaxPctUsed 36 | } 37 | ) 38 | } # foreach 39 | } 40 | catch { 41 | $stat = 'ERROR' 42 | $msg = $_.Exception.Message -join ';' 43 | } 44 | finally { 45 | Set-CmhOutputData 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Test-HostDnsRegistration.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostDnsRegistration { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Validate Host DNS A-record Registration", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate Host DNS A-record Registration", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | if ($ScriptParams.ComputerName -eq "localhost") { 18 | $name = $(Get-WmiObject win32_computersystem).DNSHostName+"."+$(Get-WmiObject win32_computersystem).Domain 19 | } else { 20 | $name = $ScriptParams.ComputerName 21 | } 22 | [array]$res = $(Resolve-DnsName -Name $name -Type A) 23 | if ($res.Count -gt 1) { 24 | $stat = $except 25 | $msg = "$($res.Count) items returned" 26 | } 27 | $res | Foreach-Object { 28 | $tempdata.Add( 29 | [pscustomobject]@{ 30 | HostName = $_.Name 31 | RecordType = $_.Type 32 | TTL = $_.TTL 33 | IPAddress = $_.IPAddress 34 | } 35 | ) 36 | } 37 | } 38 | catch { 39 | $stat = 'ERROR' 40 | $msg = $_.Exception.Message -join ';' 41 | } 42 | finally { 43 | Set-CmhOutputData 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Test-HostDriveBlockSize.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostDriveBlockSize { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Logical Drive Block Allocation", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate disk format block size", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$bsize = Get-CmHealthDefaultValue -KeySet "siteservers:DiskFormatBlockSize" -DataSet $CmHealthConfig 13 | Write-Log -Message "block size required = $bsize" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "WARNING" 17 | $msg = "Success" 18 | [array]$vols = Get-WmiQueryResult -ClassName "Win32_Volume" -Query "DriveType=3" -Params $ScriptParams | 19 | Where-Object {$_.DriveLetter} 20 | foreach ($vol in $vols) { 21 | if ($vol.BlockSize -ne $bsize) { 22 | $res = "FAIL" 23 | $stat = $except 24 | $msg = "1 or more disks are not formatted to the recommended block size: $bsize bytes. " 25 | $msg += "Refer to https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/configs/site-size-performance-guidelines#example-disk-configurations" 26 | } else { 27 | $res = 'PASS' 28 | } 29 | $tempdata.Add([pscustomobject]@{ 30 | Computer = $ScriptParams.ComputerName 31 | Drive = $vol.DriveLetter 32 | BlockSize = $vol.BlockSize 33 | Required = $bsize 34 | Result = $res 35 | }) 36 | } # foreach 37 | } 38 | catch { 39 | $stat = 'ERROR' 40 | $msg = $_.Exception.Message -join ';' 41 | } 42 | finally { 43 | if ($cs) { $cs.Close(); $cs = $null } 44 | Set-CmhOutputData 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Test-HostFirewallPorts.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostFirewallPorts { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Network Firewall Ports", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Test open firewall TCP ports", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | # reference: https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/ports 13 | [string]$Ports = Get-CmHealthDefaultValue -KeySet "siteservers:tcpports" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $ErrorActionPreference = 'SilentlyContinue' 19 | [array]$complist = @($ScriptParams.ComputerName) 20 | if ($ScriptParams.ComputerName -ne $ScriptParams.SqlInstance) { 21 | $complist += $ScriptParams.SqlInstance 22 | } 23 | foreach ($computer in $complist) { 24 | foreach ($port in $ports.split(',')) { 25 | if (Test-NetConnection -ComputerName $computer -Port $port -InformationLevel Quiet) { 26 | $pstat = 'open' 27 | } else { 28 | $pstat = 'blocked' 29 | $stat = $except 30 | $msg = "One or more TCP ports are blocked. Refer to https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/ports" 31 | } 32 | Write-Log -Message "computer=$computer, port=$port, status=$pstat" 33 | $tempdata.Add( 34 | [pscustomobject]@{ 35 | ComputerName = $computer 36 | PortNumber = $port 37 | Status = $pstat 38 | } 39 | ) 40 | } 41 | } 42 | } 43 | catch { 44 | $stat = 'ERROR' 45 | $msg = $_.Exception.Message -join ';' 46 | } 47 | finally { 48 | Set-CmhOutputData 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Test-HostIESCDisabled.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostIESCDisabled { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "IESC Feature Disabled", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Disable Internet Explorer Enhanced Security Configuration", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | $stat = "PASS" 13 | $except = "FAIL" 14 | $msg = "IE Enhanced Security Configuration (IESC) is already disabled" 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | $AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" 17 | $UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" 18 | if ((Get-ItemProperty -Path $AdminKey -Name "IsInstalled" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty IsInstalled) -ne 0) { 19 | Write-Log -Message "configuration is not compliant (is not disabled)" 20 | $stat = $except 21 | $msg = "IE Enhanced Security Configuration (IESC) is currently enabled" 22 | } else { 23 | Write-Log -Message "registry key was not found" 24 | } 25 | } 26 | catch { 27 | $stat = 'ERROR' 28 | $msg = $_.Exception.Message -join ';' 29 | } 30 | finally { 31 | Set-CmhOutputData 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Test-HostIISLogFiles.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostIISLogFiles { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "IIS Log Files", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate IIS Log File retention", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxDaysOld = Get-CmHealthDefaultValue -KeySet "iis:LogFilesMaxDaysOld" -DataSet $CmHealthConfig 13 | [int]$MaxSpacePct = Get-CmHealthDefaultValue -KeySet "iis:LogFilesMaxSpacePercent" -DataSet $CmHealthConfig 14 | Write-Log -Message "MaxDaysOld = $MaxDaysOld" 15 | Write-Log -Message "MaxSpacePct = $MaxSpacePct" 16 | 17 | $stat = "PASS" 18 | $except = "WARNING" 19 | $msg = "No issues found" 20 | 21 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 22 | 23 | if (!(Get-Module WebAdministration -ListAvailable)) { throw "WebAdministration module not installed. Please install RSAT" } 24 | Import-Module WebAdministration 25 | $LogsBase = $(Get-Item 'IIS:\Sites\Default Web Site').logfile.directory -replace '%SystemDrive%', "$($env:SYSTEMDRIVE)" 26 | $IISLogsPath = Join-Path $LogsBase -ChildPath "W3SVC1" 27 | #$IISLogsDrive = Split-Path $IISLogsPath -Qualifier 28 | 29 | #$disksize = $(Get-WmiQueryResult -ClassName 'Win32_LogicalDisk' -Filter "DeviceID = '$IISLogsDrive'" | Select-Object -ExpandProperty Size 30 | #$disksizeMB = $disksize / 1MB 31 | #$disksizeGB = $disksize / 1GB 32 | 33 | $logs = Get-ChildItem -Path $IISLogsPath -Filter "*.log" 34 | #$logspace = $logs | Measure-Object -Property Length -Sum | Select-Object -ExpandProperty Sum 35 | #$logspaceGB = $logspace / 1GB 36 | 37 | $OldLogs = @($logs | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$MaxDaysOld) }) 38 | 39 | if ($OldLogs.Count -gt 0) { 40 | Write-Log -Message "$($oldLogs.Count) older log files were found" 41 | $stat = $except 42 | $msg = "$($OldLogs.Count) of $numlogs IIS logs older than $MaxDaysOld days old" 43 | } 44 | $tempdata.Add([pscustomobject]@{ 45 | Status = $stat 46 | Message = $msg 47 | Note = "Path = $IISLogsPath" 48 | }) 49 | } 50 | catch { 51 | $stat = 'ERROR' 52 | $msg = $_.Exception.Message -join ';' 53 | } 54 | finally { 55 | Set-CmhOutputData 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Test-HostInstalledComponents.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostInstalledComponents { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Installed Software Components", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate CM prerequisites and support components", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | $applist = Get-CmHealthDefaultValue -KeySet "applications" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" 15 | $except = "FAIL" 16 | $msg = "All required components are installed" 17 | $reg64 = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall 18 | $reg32 = Get-ChildItem -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall 19 | $reg64 | ForEach-Object { 20 | if ($_.Property -contains 'DisplayName') { 21 | $pn = $_.GetValue('DisplayName') 22 | $pv = $_.GetValue('DisplayVersion') 23 | if ($pn -in $applist.ProductName) { 24 | $app = $applist | Where-Object {$_.ProductName -eq $pn} 25 | if ($app.Version -ge $pv) { 26 | $compliant = $except 27 | } else { 28 | $compliant = $True 29 | } 30 | $tempdata.Add([pscustomobject]@{ 31 | ProductName = $pn 32 | Version = $pv 33 | Required = $app.Version 34 | Compliant = $compliant 35 | Platform = 64 36 | }) 37 | } 38 | } 39 | } 40 | $app = $null 41 | $pn = $null 42 | $pv = $null 43 | $reg32 | ForEach-Object { 44 | if ($_.Property -contains 'DisplayName') { 45 | $pn = $_.GetValue('DisplayName') 46 | $pv = $_.GetValue('DisplayVersion') 47 | if ($pn -in $applist.ProductName) { 48 | $app = $applist | Where-Object {$_.ProductName -eq $pn} 49 | if ($app.Version -ge $pv) { 50 | $compliant = $False 51 | } else { 52 | $compliant = $True 53 | } 54 | $tempdata.Add([pscustomobject]@{ 55 | ProductName = $pn 56 | Version = $pv 57 | Required = $app.Version 58 | Compliant = $compliant 59 | Platform = 32 60 | }) 61 | } 62 | } 63 | } 64 | } 65 | catch { 66 | $stat = 'ERROR' 67 | $msg = $_.Exception.Message -join ';' 68 | } 69 | finally { 70 | Set-CmhOutputData 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Test-HostMemory.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostMemory { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Server Memory Allocation", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Verify site system has at least minimum required memory", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MinMemory = Get-CmHealthDefaultValue -KeySet "siteservers:MinimumMemoryGB" -DataSet $CmHealthConfig 13 | $MinMem = $($MinMemory * 1GB) 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "WARNING" 17 | $msg = "No issues found" 18 | $SystemInfo = Get-WmiQueryResult -ClassName "Win32_OperatingSystem" -Params $ScriptParams 19 | $TotalRAM = $SystemInfo.TotalVisibleMemorySize/1MB 20 | $FreeRAM = $SystemInfo.FreePhysicalMemory/1MB 21 | $UsedRAM = $TotalRAM - $FreeRAM 22 | $RAMPercentFree = ($FreeRAM / $TotalRAM) * 100 23 | $TotalRAM = [Math]::Round($TotalRAM, 2) 24 | $FreeRAM = [Math]::Round($FreeRAM, 2) 25 | $UsedRAM = [Math]::Round($UsedRAM, 2) 26 | $RAMPercentFree = [Math]::Round($RAMPercentFree, 2) 27 | Write-Log -Message "total memory = $TotalRAM" 28 | Write-Log -Message "minimum allowed memory = $MinMemory" 29 | if ($TotalRAM -lt $MinMemory) { 30 | $stat = $except 31 | $msg = "$($TotalRam) GB is below the minimum recommended $MinMemory GB" 32 | $res | Foreach-Object { 33 | $tempdata.Add( 34 | [pscustomobject]@{ 35 | Total = $($TotalRam) 36 | Expected = $($MinMemory) 37 | } 38 | ) 39 | } 40 | } elseif ($RAMPercentFree -lt 10) { 41 | $stat = $except 42 | $msg = "Less than 10 percent memory is available" 43 | $res | Foreach-Object { 44 | $tempdata.Add( 45 | [pscustomobject]@{ 46 | PctFree = $($RAMPercentFree) 47 | Expected = 10 48 | } 49 | ) 50 | } 51 | } else { 52 | $res | Foreach-Object { 53 | $tempdata.Add( 54 | [pscustomobject]@{ 55 | Total = $($TotalRam) 56 | Expected = $($MinMemory) 57 | } 58 | ) 59 | } 60 | } 61 | } 62 | catch { 63 | $stat = 'ERROR' 64 | $msg = $_.Exception.Message -join ';' 65 | } 66 | finally { 67 | Set-CmhOutputData 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Test-HostNoSmsOnDriveFile.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostNoSmsOnDriveFile { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SMS Content Drive Exclusion", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Confirm NO_SMS_ON_DRIVE.SMS file resides on appropriate disks", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | $stat = "PASS" 13 | $except = "WARNING" 14 | $msg = "All non-CM disks are excluded" 15 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 16 | if ($ScriptParams.Credential) { 17 | $cs = New-CimSession -Credential $ScriptParams.Credential -Authentication Negotiate -ComputerName $ScriptParams.ComputerName -ErrorAction Stop 18 | $disks = Get-CimInstance -CimSession $cs -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DeviceID 19 | foreach ($disk in $disks) { 20 | $fpth = "$($disk)\\NO_SMS_ON_DRIVE.sms" 21 | $clib = "$($disk)\\SMSPKGSIG" 22 | if (Get-CimInstance -Query "SELECT Name FROM CIM_Directory WHERE Name='$clib'" -CimSession $cs -ErrorAction SilentlyContinue) { 23 | $tempdata.Add([pscustomobject]@{ 24 | Test = $TestName 25 | Status = "PASS" 26 | Message = "$($disk) appears to be a content library drive (not excluded)" 27 | }) 28 | } else { 29 | if (Get-CimInstance -CimSession $cs -Query "SELECT Name FROM CIM_DataFile WHERE Name = '$fpth'" -ErrorAction SilentlyContinue) { 30 | $tempdata.Add([pscustomobject]@{ 31 | Test = $TestName 32 | Status = "PASS" 33 | Message = "$($disk) is excluded from ConfigMgr content storage" 34 | }) 35 | } else { 36 | $tempdata.Add([pscustomobject]@{ 37 | Test = $TestName 38 | Status = $except 39 | Message = "$($_.DeviceID) is not excluded from ConfigMgr content storage" 40 | }) 41 | } 42 | } 43 | } # foreaach 44 | } else { 45 | $disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $ScriptParams.ComputerName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DeviceID 46 | foreach ($disk in $disks) { 47 | $fpth = "$($disk)\NO_SMS_ON_DRIVE.sms" 48 | $clib = "$($disk)\SMSPKGSIG" 49 | if (Test-Path $clib) { 50 | $tempdata.Add([pscustomobject]@{ 51 | Test = $TestName 52 | Status = "PASS" 53 | Message = "$($disk) appears to be a content library drive (not excluded)" 54 | }) 55 | } else { 56 | $tempdata.Add([pscustomobject]@{ 57 | Test = $TestName 58 | Status = $except 59 | Message = "$($disk) is not excluded from ConfigMgr content storage" 60 | }) 61 | } 62 | } 63 | } 64 | } 65 | catch { 66 | $stat = 'ERROR' 67 | $msg = $_.Exception.Message -join ';' 68 | } 69 | finally { 70 | if ($cs) { $cs.Close(); $cs = $null } 71 | Set-CmhOutputData 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Test-HostOperatingSystem.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostOperatingSystem { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Supported Operating System", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate supported operating system for CM site system roles", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | $supported = @(Get-CmHealthDefaultValue -KeySet "siteservers:SupportedOperatingSystems" -DataSet $CmHealthConfig) 13 | Write-Log -Message "Supported OS list = $($supported -join ',')" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No issues found" 18 | $osdata = Get-WmiQueryResult -ClassName "Win32_OperatingSystem" -Params $ScriptParams 19 | $osname = $osdata.Caption 20 | $osbuild = $osdata.BuildNumber 21 | $matched = (($supported | Foreach-Object {$osname -match $_}) -eq $True) 22 | if ($matched -ne $true) { 23 | $stat = $except 24 | $msg = "Unsupported operating system for site system roles: $osname $osbuild" 25 | $tempdata.Add("Supported: $($supported -join ',')") 26 | } else { 27 | $msg = "$($osdata.Caption) $($osdata.BuildNumber)" 28 | } 29 | } 30 | catch { 31 | $stat = 'ERROR' 32 | $msg = $_.Exception.Message -join ';' 33 | } 34 | finally { 35 | if ($cs) { $cs.Close(); $cs = $null } 36 | Set-CmhOutputData 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Test-HostRestarts.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostRestarts { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Unplanned Server Restarts", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check for unplanned system restarts", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $query = @" 18 | 19 | 20 | 21 | 22 | 23 | "@ 24 | if ($ScriptParams.ComputerName -ne $env:COMPUTERNAME) { 25 | Write-Log -Message "running on $($ScriptParams.ComputerName)" 26 | if ($null -ne $ScriptParams.Credential) { 27 | Write-Log -Message "credential was provided" 28 | $res = Get-WinEvent -LogName "System" -FilterXPath $query -ComputerName $ScriptParams.ComputerName -Credential $ScriptParams.Credential -ErrorAction SilentlyContinue 29 | } else { 30 | $res = Get-WinEvent -LogName "System" -FilterXPath $query -ComputerName $ScriptParams.ComputerName -ErrorAction SilentlyContinue 31 | } 32 | } else { 33 | Write-Log -Message "running on localhost" 34 | $res = Get-WinEvent -LogName "System" -FilterXPath $query -ErrorAction SilentlyContinue 35 | } 36 | if ($res.Count -gt 0) { 37 | $stat = $except 38 | $msg = "$($res.Count) items found" 39 | $winevent = [pscustomobject]@{TimeCreated = $_.TimeCreated; Computer = $_.MachineName; Message = $_.Message} 40 | $res | Foreach-Object {$tempdata.Add($winevent)} 41 | } 42 | } 43 | catch { 44 | $stat = 'ERROR' 45 | $msg = $_.Exception.Message -join ';' 46 | } 47 | finally { 48 | Set-CmhOutputData 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Test-HostServerFeatures.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostServerFeatures { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Installed Windows Features", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Validate Windows Server roles and features for CM site systems", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "FAIL" 15 | $msg = "No issues found" 16 | Import-Module ServerManager 17 | if ($ScriptParams.ComputerName -ne $env:COMPUTERNAME) { 18 | if ($ScriptParams.Credential) { 19 | $features = Get-WindowsFeature -ComputerName $ScriptParams.ComputerName -Credential $ScriptParams.Credential -ErrorAction Stop | Sort-Object Name 20 | } else { 21 | $features = Get-WindowsFeature -ComputerName $ScriptParams.ComputerName -ErrorAction Stop | Sort-Object Name 22 | } 23 | } else { 24 | $features = Get-WindowsFeature -ErrorAction Stop | Sort-Object Name 25 | } 26 | $LogFile = Join-Path $env:TEMP "serverfeatures.log" 27 | $flist = @($CmHealthConfig.windowsfeatures.Feature) 28 | if ($flist.Count -lt 1) { throw "failed to read features list from cmhealth settings file" } 29 | $exceptions = 0 30 | [System.Collections.Generic.List[PSObject]]$missing = @() 31 | foreach ($feature in $features) { 32 | if ($feature.Name -in $flist) { 33 | if ($feature.Installed -ne $True) { 34 | Write-Log -Message "feature not installed: $($feature.Name)" 35 | $exceptions++ 36 | $tempdata.Add( 37 | [pscustomobject]@{ 38 | Feature = $feature.Name 39 | Status = $except 40 | Message = "Not installed" 41 | } 42 | ) 43 | $missing.Add($feature.Name) 44 | } 45 | else { 46 | Write-Log -Message "feature is installed: $($feature.Name)" 47 | $tempdata.Add( 48 | [pscustomobject]@{ 49 | Feature = $feature.Name 50 | Status = "PASS" 51 | Message = "Already installed" 52 | } 53 | ) 54 | } 55 | } 56 | } # foreach 57 | if ($exceptions -gt 0) { 58 | $stat = $except 59 | $msg = "$exceptions features are missing: $($missing -join ',')" 60 | } 61 | } 62 | catch { 63 | $stat = 'ERROR' 64 | $msg = $_.Exception.Message -join ';' 65 | } 66 | finally { 67 | Set-CmhOutputData 68 | } 69 | } -------------------------------------------------------------------------------- /tests/Test-HostServices.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostServices { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Windows Services Health", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Verify auto-start services are running", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $services = Get-WmiQueryResult -ClassName "Win32_Service" -Query "startmode = 'auto' and state != 'running'" -Params $ScriptParams 17 | if ($services.Count -gt 0) { 18 | $stat = $except 19 | $services | Foreach-Object { 20 | $tempdata.Add( 21 | [pscustomobject]@{ 22 | Name = $_.Name 23 | StartMode = $_.StartMode 24 | State = $_.State 25 | } 26 | ) 27 | } 28 | $msg = "$($services.Count) stopped services: $($services.Name -join ',')" 29 | } 30 | } 31 | catch { 32 | $stat = 'ERROR' 33 | $msg = $_.Exception.Message -join ';' 34 | } 35 | finally { 36 | if ($cs) { $cs.Close(); $cs = $null } 37 | Set-CmhOutputData 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Test-HostSystemLogErrors.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostSystemLogErrors { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "System Event Log Errors", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check for recent System log errors and warnings", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int] $MaxHours = 24 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $query = @" 19 | 20 | 21 | 22 | 23 | 24 | "@ 25 | [array]$computers = $ScriptParams.ComputerName 26 | if ($ScriptParams.ComputerName -ne $ScriptParams.SqlInstance) { 27 | $computers += $ScriptParams.SqlInstance 28 | } 29 | foreach ($computer in $computers) { 30 | Write-Log -Message "computer: $computer" 31 | if ($computer -ne $env:COMPUTERNAME) { 32 | if ($ScriptParams.Credential) { 33 | $res = @(Get-WinEvent -LogName System -FilterXPath $query -ComputerName $computer -Credential $ScriptParams.Credential -ErrorAction SilentlyContinue) 34 | } else { 35 | $res = @(Get-WinEvent -LogName System -FilterXPath $query -ComputerName $computer -ErrorAction SilentlyContinue) 36 | } 37 | } else { 38 | $computer = $env:COMPUTERNAME 39 | $res = @(Get-WinEvent -LogName System -FilterXPath $query -ErrorAction SilentlyContinue) 40 | } 41 | $vwarnings = $res | Where-Object {$_.LevelDisplayName -eq 'Warning'} 42 | $verrors = $res | Where-Object {$_.LevelDisplayName -eq 'Error'} 43 | if (($verrors.Count -gt 0) -or ($vwarnings.Count -gt 0)) { 44 | $msg = "$($verrors.Count) Errors and $($vwarnings.Count) Warnings occurred in the System log within the past $MaxHours hours" 45 | $stat = $except 46 | $res | Where-Object {$_.LevelDisplayName -in ('Warning','Error')} | Foreach-Object { 47 | $tempdata.Add( 48 | [pscustomobject]@{ 49 | Computer = $_.MachineName 50 | Level = $_.LevelDisplayName 51 | ID = $_.Id 52 | Provider = $_.ProviderName 53 | Log = $_.LogName 54 | TimeCreated = $_.TimeCreated 55 | Message = $_.Message 56 | } 57 | ) 58 | } 59 | } 60 | } # foreach 61 | } 62 | catch { 63 | $stat = 'ERROR' 64 | $msg = $_.Exception.Message -join ';' 65 | } 66 | finally { 67 | Set-CmhOutputData 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Test-HostWindowsUpdates.ps1: -------------------------------------------------------------------------------- 1 | function Test-HostWindowsUpdates { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Windows Update Compliance", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "HOST", 7 | [parameter()][string] $Description = "Check if server is up to date on patches", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $res = Get-WindowsUpdate -ComputerName $ScriptParams.ComputerName -WindowsUpdate -ErrorAction Stop 17 | if ($res.Count -gt 0) { 18 | Write-Log -Message "$($res.Count) updates are not installed" 19 | $stat = $except 20 | $msg = "$($res.Count) Microsoft updates are waiting to be installed" 21 | $res | Foreach-Object { 22 | $tempdata.Add( 23 | [pscustomobject]@{ 24 | KB = $($_.KB) 25 | Title = $($_.Title) 26 | ReleaseDate = $($_.LastDeploymentChangeTime) 27 | } 28 | ) 29 | } 30 | } 31 | } 32 | catch { 33 | $stat = 'ERROR' 34 | if ($_.CategoryInfo -match 'PermissionDenied') { 35 | $msg = "PsWindowsUpdate module dependency - does not support remote credentials" 36 | } else { 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | } 40 | finally { 41 | Set-CmhOutputData 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Test-SqlAgentJobStatus.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlAgentJobStatus { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Agent Job Status", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate SQL Agent Job status", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$HoursBack = Get-CmHealthDefaultValue -KeySet "sqlserver:SqlAgentJobStatusHoursBack" -DataSet $CmHealthConfig 13 | Write-Log -Message "hours back = $HoursBack" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No errors in the past $($HoursBack) hours" 18 | if ($null -ne $ScriptParams.Credential) { 19 | $params = @{ 20 | SqlInstance = $ScriptParams.SqlInstance 21 | StartDate = (Get-Date).AddHours(-$HoursBack) 22 | SqlCredential = $ScriptParams.Credential 23 | } 24 | } else { 25 | $params = @{ 26 | SqlInstance = $ScriptParams.SqlInstance 27 | StartDate = (Get-Date).AddHours(-$HoursBack) 28 | } 29 | } 30 | $res = @(Get-DbaAgentJobHistory @params | Where-Object {$_.Status -ne "Succeeded"}) 31 | if ($res.Count -gt 0) { 32 | $stat = $except 33 | $res | Foreach-Object { 34 | $tempdata.Add( 35 | [pscustomobject]@{ 36 | Name = $_.Job 37 | Step = $_.StepName 38 | RunDate = $_.RunDate 39 | Status = $_.Status 40 | Message = $_.Message 41 | } 42 | ) 43 | } 44 | $msg = "$($res.Count) SQL Agent Jobs have failed within the past $HoursBack hours" 45 | } 46 | } 47 | catch { 48 | $stat = 'ERROR' 49 | $msg = $_.Exception.Message -join ';' 50 | } 51 | finally { 52 | Set-CmhOutputData 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Test-SqlDatabaseFileGrowth.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDatabaseFileGrowth { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database File Growth", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Results of SQL file auto-growth configuration", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | $MaxRunTimeSeconds = 30 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $query = "SELECT 19 | s.database_name, 20 | CASE s.[type] 21 | WHEN 'D' THEN 'Full' 22 | WHEN 'I' THEN 'Differential' 23 | WHEN 'L' THEN 'Transaction Log' 24 | END AS BackupType, 25 | CAST(CAST(s.backup_size / 1000000 AS INT) AS VARCHAR(14)) + ' ' + 'MB' AS bkSize, 26 | CAST(DATEDIFF(second, s.backup_start_date, 27 | s.backup_finish_date) AS VARCHAR(4)) + ' ' + 'Seconds' TimeTaken, 28 | s.backup_start_date 29 | FROM msdb.dbo.backupset s 30 | INNER JOIN msdb.dbo.backupmediafamily m ON s.media_set_id = m.media_set_id 31 | WHERE 32 | s.backup_start_date >= DATEADD(dd,-CONVERT(INT,7),GETDATE()) 33 | ORDER BY s.database_name, backup_finish_date DESC, backup_start_date ASC" 34 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 35 | $res | Foreach-Object { 36 | if ($_.TimeTaken -gt $MaxRunTimeSeconds) { 37 | $stat = $except 38 | $msg = "one or more growth tasks took longer than $MaxRunTimeSeconds seconds" 39 | } 40 | $tempdata.Add( 41 | [pscustomobject]@{ 42 | Database = $_.database_name 43 | BackupType = $_.BackupType 44 | BackupSize = $_.bkSize 45 | RunTime = $_.TimeTaken 46 | BackupStart = $_.backup_start_date 47 | } 48 | ) 49 | } 50 | } 51 | catch { 52 | $stat = 'ERROR' 53 | $msg = $_.Exception.Message -join ';' 54 | } 55 | finally { 56 | Set-CmhOutputData 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Test-SqlDatabaseFileInfo.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDatabaseFileInfo { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Instance Database File Info", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Database and Log file sizes and growth settings", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $query = "CREATE TABLE ##temp_DatabaseAnalysis 18 | (DatabaseName sysname, Name sysname, physical_name NVARCHAR(500), size DECIMAL (18,2), FreeSpace DECIMAL (18,2) ) 19 | EXEC sp_msforeachdb ' 20 | USE [?]; 21 | INSERT INTO ##temp_DatabaseAnalysis (DatabaseName, Name, physical_name, Size, FreeSpace) 22 | SELECT DB_NAME() AS [DatabaseName], Name, physical_name, 23 | CAST(CAST(ROUND(CAST(size as decimal) * 8.0/1024.0,2) as DECIMAL(18,2)) AS NVARCHAR) Size, 24 | CAST(CAST(ROUND(CAST(size as decimal) * 8.0/1024.0,2) as DECIMAL(18,2)) - CAST(FILEPROPERTY(name, ''SpaceUsed'') * 8.0/1024.0 as DECIMAL(18,2)) as NVARCHAR) As FreeSpace 25 | FROM sys.database_files 26 | ' 27 | SELECT db.name, db.recovery_model_desc, 28 | CASE 29 | WHEN mf.type_desc = 'ROWS' THEN 'Database' 30 | ELSE 'Logs' 31 | END AS type_desc, mf.physical_name, 32 | (mf.size*8)/1024 as Size, 33 | CASE 34 | WHEN mf.max_size = -1 THEN 'Unlimited' 35 | ELSE CAST(mf.max_size as VARCHAR(200)) 36 | END AS Max_Size, 37 | tmp.FreeSpace, 38 | CASE 39 | WHEN mf.is_percent_growth = 1 THEN CAST(mf.growth as VARCHAR(200)) + '%' 40 | ELSE CAST(mf.growth as VARCHAR(200)) + ' MB' 41 | END AS Growth, 42 | (SELECT COUNT(1) FROM sys.master_files mf1 WHERE mf1.type_desc = 'ROWS' AND db.database_id = mf1.database_id ) AS CountDataFile, 43 | (SELECT COUNT(1) FROM sys.master_files mf1 WHERE mf1.type_desc = 'LOG' AND db.database_id = mf1.database_id ) AS CountLogFile 44 | FROM sys.master_files mf INNER JOIN sys.databases db ON db.database_id = mf.database_id 45 | INNER JOIN ##temp_DatabaseAnalysis tmp ON mf.physical_name = tmp.physical_name 46 | DROP TABLE ##temp_DatabaseAnalysis" 47 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 48 | $res | Foreach-Object { 49 | $tempdata.Add( 50 | [PSCustomObject]@{ 51 | Name = $_.Name 52 | RecoverModel = $_.recovery_model_desc 53 | Type = $_.type_desc 54 | FilePath = $_.physical_name 55 | SizeMB = $_.Size 56 | MaxSizeMB = $_.Max_Size 57 | FreeSpaceMB = $_.FreeSpace 58 | Growth = $_.Growth 59 | DBFiles = $_.CountDataFile 60 | LogFiles = $_.CountLogFile 61 | } 62 | ) 63 | } 64 | if ($res.Count -gt 0) { 65 | $stat = $except 66 | $msg = "$($res.Count) items found" 67 | #$res | Foreach-Object {$tempdata.Add( [pscustomobject]@{Name=$_.Name} )} 68 | } 69 | } 70 | catch { 71 | $stat = 'ERROR' 72 | $msg = $_.Exception.Message -join ';' 73 | } 74 | finally { 75 | Set-CmhOutputData 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Test-SqlDatabaseNameDefault.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDatabaseNameDefault { 2 | [CmdletBinding()] 3 | [OutputType()] 4 | param ( 5 | [parameter()][string] $TestName = "Check if SQL DB name uses default format", 6 | [parameter()][string] $TestGroup = "configuration", 7 | [parameter()][string] $TestCategory = "SQL", 8 | [parameter()][string] $Description = "Check if site database name is using the CM_XXX format", 9 | [parameter()][hashtable] $ScriptParams 10 | ) 11 | try { 12 | $startTime = (Get-Date) 13 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" # or "FAIL" 17 | $msg = "No issues found" # do not change this either 18 | $dbname = $($CmhParams.Database) 19 | $dbtest = "CM_$($CmhParams.SiteCode)" 20 | if ($dbname -ne $dbtest) { 21 | $stat = $except 22 | $msg = "$dbname is not using the default naming format CM_(SiteCode)" 23 | Write-Log -Message $msg -Category $except 24 | } else { 25 | $msg = "$dbname is using the default naming format" 26 | Write-Log -Message $msg 27 | } 28 | $tempdata.Add( 29 | [pscustomobject]@{ 30 | Status = $stat 31 | ComputerName = $reghost 32 | DatabaseName = $dbname 33 | Message = $msg 34 | } 35 | ) 36 | } 37 | catch { 38 | $stat = 'ERROR' 39 | $msg = $_.Exception.Message -join ';' 40 | } 41 | finally { 42 | Set-CmhOutputData 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Test-SqlDbBackupHistory.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbBackupHistory { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Backup History", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate CM SQL database backup history", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$DaysBack = Get-CmHealthDefaultValue -KeySet "sqlserver:SiteBackupMaxDaysOld" -DataSet $CmHealthConfig 13 | Write-Log -Message "daysback = $DaysBack" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No issues found" 18 | if ($null -ne $ScriptParams.Credential) { 19 | $bh = Get-DbaDbBackupHistory -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Since (Get-Date).AddDays(-$DaysBack) -SqlCredential $ScriptParams.Credential 20 | } else { 21 | $bh = Get-DbaDbBackupHistory -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Since (Get-Date).AddDays(-$DaysBack) 22 | } 23 | if ($bh.Count -lt 1) { 24 | $stat = $except 25 | $msg = "No backups were completed in the last $DaysBack days" 26 | } else { 27 | $lbu = $bh[0] 28 | if ($lbu.Type -ne "Full") { 29 | $stat = $except 30 | $msg = "Last backup was not a FULL backup" 31 | } else { 32 | $msg = "Last Full backup was at $($lbu.End)" 33 | } 34 | } 35 | } 36 | catch { 37 | $stat = 'ERROR' 38 | $msg = $_.Exception.Message -join ';' 39 | } 40 | finally { 41 | Set-CmhOutputData 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Test-SqlDbBackupTimes.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbBackupTimes { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Backup Times", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check for backups that took too long to finish", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int] $DaysBack = Get-CmHealthDefaultValue -KeySet "sqlserver:SiteBackupMaxDaysOld" -DataSet $CmHealthConfig 13 | [int] $MaxRunTime = Get-CmHealthDefaultValue -KeySet "wsus:SiteBackupMaxRuntime" -DataSet $CmHealthConfig 14 | if (!$MaxRunTime) { 15 | $MaxRunTime = 300 # seconds 16 | } 17 | Write-Log -Message "daysback = $DaysBack" 18 | Write-Log -Message "maxruntime = $MaxRunTime" 19 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 20 | $stat = "PASS" 21 | $except = "WARNING" 22 | $msg = "No issues found" 23 | $query = "Select * From ( 24 | SELECT 25 | s.database_name AS [Database], 26 | CASE s.[type] 27 | WHEN 'D' THEN 'Full' 28 | WHEN 'I' THEN 'Differential' 29 | WHEN 'L' THEN 'Transaction Log' 30 | END AS BackupType, 31 | CAST(CAST(s.backup_size / 1000000 AS INT) AS VARCHAR(14)) + ' ' + 'MB' AS Size, 32 | DATEDIFF(second, s.backup_start_date, s.backup_finish_date) Seconds, 33 | s.backup_start_date AS StartDate 34 | FROM msdb.dbo.backupset s 35 | INNER JOIN msdb.dbo.backupmediafamily m ON s.media_set_id = m.media_set_id 36 | WHERE 37 | s.backup_start_date >= DATEADD(dd,-CONVERT(INT, $DaysBack),GETDATE()) 38 | ) T1 39 | WHERE T1.Seconds > $MaxRunTime 40 | ORDER BY T1.Seconds DESC" 41 | if ($null -ne $ScriptParams.Credential) { 42 | $res = @(Invoke-DbaQuery -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Query $query -SqlCredential $ScriptParams.Credential) 43 | } else { 44 | $res = @(Invoke-DbaQuery -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Query $query) 45 | } 46 | if ($res.Count -gt 0) { 47 | $stat = $except 48 | $msg = "$($res.Count) backups took longer than $MaxRunTime seconds" 49 | $res | Foreach-Object { 50 | $tempdata.Add( 51 | [pscustomobject]@{ 52 | Database = $_.Database 53 | BackupType = $_.BackupType 54 | Size = $_.Size 55 | StartDate = $_.StartDate 56 | Duration = "$($_.Seconds) sec" 57 | } 58 | ) 59 | } 60 | } 61 | } 62 | catch { 63 | $stat = 'ERROR' 64 | $msg = $_.Exception.Message -join ';' 65 | } 66 | finally { 67 | Set-CmhOutputData 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Test-SqlDbCollation.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbCollation { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Collation", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate SQL database collation configuration", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [string]$Collation = Get-CmHealthDefaultValue -KeySet "sqlserver:DefaultCollation" -DataSet $CmHealthConfig 13 | Write-Log -Message "collation = $Collation" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No issues found" 18 | if ($null -ne $ScriptParams.Credential) { 19 | $coll = Test-DbaDbCollation -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -SqlCredential $ScriptParams.Credential 20 | } else { 21 | $coll = Test-DbaDbCollation -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database 22 | } 23 | if ($coll.DatabaseCollation -ne $Collation) { 24 | $stat = $except 25 | $msg = "Collection is $($coll.DatabaseCollation) but should be $Collation" 26 | } 27 | $tempdata.Add( 28 | [pscustomobject]@{ 29 | Collation = $coll.DatabaseCollation 30 | Required = $Collation 31 | Status = $stat 32 | } 33 | ) 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | Set-CmhOutputData 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Test-SqlDbDiskAlignment.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbDiskAlignment { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Server Disk Alignment Status", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate disk alignment with SQL recommended practices", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | if ($null -ne $ScriptParams.Credential) { 17 | $da = Test-DbaDiskAlignment -ComputerName $ScriptParams.SqlInstance -SqlCredential $ScriptParams.Credential -EnableException -ErrorAction SilentlyContinue 18 | } else { 19 | $da = Test-DbaDiskAlignment -ComputerName $ScriptParams.SqlInstance -EnableException -ErrorAction SilentlyContinue 20 | } 21 | if ($da -eq $false) { 22 | $stat = $except 23 | $msg = "One or more disks are not aligned using recommended practices" 24 | } 25 | $tempdata.Add($da) 26 | } 27 | catch { 28 | $stat = 'ERROR' 29 | $msg = $_.Exception.Message -join ';' 30 | } 31 | finally { 32 | Set-CmhOutputData 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Test-SqlDbRecoveryModel.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbRecoveryModel { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Recovery Models", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate database recovery model settings", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [string]$DefaultModel = Get-CmHealthDefaultValue -KeySet "sqlserver:RecoveryModel" -DataSet $CmHealthConfig 13 | Write-Log -Message "default recovery model = $DefaultModel" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "Correct configuration" 18 | if ($null -ne $ScriptParams.Credential) { 19 | $rm = (Get-DbaDbRecoveryModel -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -ErrorAction SilentlyContinue -SqlCredential $ScriptParams.Credential).RecoveryModel 20 | } else { 21 | $rm = (Get-DbaDbRecoveryModel -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -ErrorAction SilentlyContinue).RecoveryModel 22 | } 23 | if ($rm -ne $DefaultModel) { 24 | $stat = $except 25 | $msg = "Recovery model is currently set to $rm" 26 | } 27 | } 28 | catch { 29 | $stat = 'ERROR' 30 | $msg = $_.Exception.Message -join ';' 31 | } 32 | finally { 33 | Set-CmhOutputData 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Test-SqlDbResourceWaits.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDbResourceWaits { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Resource Wait Times", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check for excessive resource wait times", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $query = "SELECT 17 | CAST(100.0 * SUM(signal_wait_time_ms) / SUM (wait_time_ms) AS NUMERIC(20,2)) AS [CPUWaits_Pct], 18 | CAST(100.0 * SUM(wait_time_ms - signal_wait_time_ms) / SUM (wait_time_ms) AS NUMERIC(20,2)) AS [ResourceWaits_Pct] 19 | FROM sys.dm_os_wait_stats OPTION (RECOMPILE)" 20 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 21 | if ($res.CPUWaits_Pct -gt 10 -or $res.ResourceWaits_Pct -gt 50) { 22 | $stat = $except 23 | $msg = "Excessive CPU waits: CPU=$($res.CPUWaits_Pct) Resources=$($res.ResourceWaits_Pct)" 24 | $res | Foreach-Object { 25 | $tempdata.Add( 26 | [pscustomobject]@{ 27 | SqlInstance = $ScriptParams.SqlInstance 28 | CPUWaitsPct = $($_.CPUWaits_Pct) 29 | ResourceWaitsPct = $($_.ResourceWaits_Pct) 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | Set-CmhOutputData 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Test-SqlDiskSpace.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlDiskSpace { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Instance Disk Space", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check disk space status on SQL Instance", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxPctUsed = Get-CmHealthDefaultValue -KeySet "siteservers:DiskSpaceMaxPercent" -DataSet $CmHealthConfig 13 | Write-Log -Message "MaxPctUsed = $MaxPctUsed" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" # do not change this 16 | $except = "WARNING" 17 | $msg = "No issues found" # do not change this either 18 | $issues = 0 19 | Write-Log -Message "requesting list of DP servers" 20 | $query = "select ServerName from dbo.v_DistributionPointInfo order by ServerName" 21 | [array]$dplist = Invoke-DbaQuery -SqlInstance $SqlInstance -Database $Database -Query $query | Select-Object -ExpandProperty ServerName 22 | Write-Log -Message "$($dplist.Count) DP server names returned" 23 | [string]$myFQDN=(Get-CimInstance Win32_ComputerSystem).DNSHostName+"."+(Get-CimInstance Win32_ComputerSystem).Domain 24 | [int]$index=1 25 | foreach ($dp in $dplist) { 26 | $res = $null; $cs = $null 27 | if ($dp -ne $myFQDN) { 28 | Write-Log -Message "connecting to remote DP [$index of $($dplist.Count)]: $dp" 29 | $cs = New-CimSession -Credential $ScriptParams.Credential -Authentication Negotiate -ComputerName $dp -ErrorAction SilentlyContinue 30 | if ($null -ne $cs) { 31 | $res = @(Get-CimInstance -CimSession $cs -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -ErrorAction SilentlyContinue) 32 | } else { 33 | $res = $null 34 | } 35 | } else { 36 | Write-Log -Message "connecting to local DP [$index of $($dplist.Count)]: $dp" 37 | $res = @(Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3") 38 | } 39 | if ($res.Count -gt 0) { 40 | foreach ($disk in $res) { 41 | $size = $disk.Size 42 | $free = $disk.FreeSpace 43 | $used = $size - $free 44 | $pct = $([math]::Round($used / $size, 1)) * 100 45 | if ($pct -gt $MaxPctUsed) { 46 | $tempData.Add( 47 | [pscustomobject]@{ 48 | Computer = $($dp) 49 | Drive = $($disk.DeviceID) 50 | SizeGB = [math]::Round($size / 1GB, 1) 51 | PctUsed = $pct 52 | } 53 | ) 54 | $stat = $except 55 | $issues++ 56 | } else { 57 | $tempData.Add( 58 | [pscustomobject]@{ 59 | Computer = $($dp) 60 | Drive = $($disk.DeviceID) 61 | SizeGB = [math]::Round($size / 1GB, 1) 62 | PctUsed = $pct 63 | } 64 | ) 65 | } 66 | } # foreach 67 | if ($issues -gt 0) { $msg = "$issues issues were found" } 68 | } else { 69 | Write-Warning "DP disk information is not available: $dp" 70 | $tempData.Add( 71 | [pscustomobject]@{ 72 | Computer = $($dp) 73 | Drive = $null 74 | SizeGB = $null 75 | PctUsed = $null 76 | } 77 | ) 78 | } 79 | $index++ 80 | } # foreach 81 | } 82 | catch { 83 | $stat = 'ERROR' 84 | $msg = $_.Exception.Message -join ';' 85 | } 86 | finally { 87 | Set-CmhOutputData 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Test-SqlHostOS.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlHostOS { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Host Operating System", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check for supported operating system version", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $os = Get-DbaOperatingSystem -ComputerName $ScriptParams.SqlInstance 18 | if ($os.Version -notin ('9200','9600','10.0.14393','10.0.17763','10.0.18362','10.0.18363','10.0.19041','10.0.20348')) { 19 | $stat = $except 20 | $msg = "Possibly unsupported operating system" 21 | } 22 | $os | Foreach-Object { 23 | $tempdata.Add( 24 | [pscustomobject]@{ 25 | OSCaption = $_.OSVersion 26 | Version = $_.Version 27 | Architecture = $_.Architecture 28 | LastBootTime = $_.LastBootTime 29 | TimeZone = $_.TimeZone 30 | Language = $_.LanguageNative 31 | } 32 | ) 33 | } 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | Set-CmhOutputData 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Test-SqlIndexFragmentation.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlIndexFragmentation { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Index Fragmentation Status", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate SQL database index fragmentation status", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MinValue = Get-CmHealthDefaultValue -KeySet "sqlserver:IndexFragThresholdPercent" -DataSet $CmHealthConfig 13 | Write-Log -Message "min index fragmentation pct = $MinValue" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "WARNING" 17 | $msg = "No indexes were fragmented more than $MinValue percent" 18 | $query = "SELECT dbschemas.[name] as 'Schema', 19 | dbtables.[name] as 'Table', 20 | dbindexes.[name] as 'Index', 21 | indexstats.avg_fragmentation_in_percent, 22 | indexstats.page_count 23 | FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats 24 | INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id] 25 | INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id] 26 | INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] 27 | AND indexstats.index_id = dbindexes.index_id 28 | WHERE indexstats.database_id = DB_ID() and indexstats.avg_fragmentation_in_percent > $($MinValue) 29 | ORDER BY indexstats.avg_fragmentation_in_percent desc" 30 | $res = Get-CmSqlQueryResult -Query $query -Params $ScriptParams 31 | $result = $res | ForEach-Object { 32 | [pscustomobject]@{ 33 | Schema = $_.Schema 34 | Table = $_.Table 35 | Index = $_.Index 36 | AvgFragPct = [math]::Round($_.avg_fragmentation_in_percent,2) 37 | PageCount = $_.PageCount 38 | } 39 | } 40 | if ($result.Count -gt 1) { 41 | $stat = $except 42 | $msg = "$($result.Count) indexes were fragmented more than $MinValue percent" 43 | $result | Foreach-Object { 44 | $tempdata.Add( 45 | [pscustomobject]@{ 46 | Table = $($_.Table) 47 | Index = $($_.Index) 48 | FragPct=$($_.AvgFragPct) 49 | } 50 | ) 51 | } 52 | } 53 | } 54 | catch { 55 | $stat = 'ERROR' 56 | $msg = $_.Exception.Message -join ';' 57 | } 58 | finally { 59 | Set-CmhOutputData 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Test-SqlLogSpace.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlLogSpace { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Log Space Usage", 5 | [parameter()][string] $TestGroup = "operation", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check for SQL logs with excessive space used", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "WARNING" # or "FAIL" 16 | $msg = "No issues found" # do not change this either 17 | $logs = Get-DbaDbLogSpace -SqlInstance $ScriptParams.SqlInstance 18 | foreach ($log in $logs) { 19 | if ($log.LogSpaceUsedPercent -gt 50) { 20 | Write-Log -Message "log space warning: $($log.Database)" 21 | $msg = "Log space warning (greater than 50 percent used)" 22 | $stat = $except 23 | $tempdata.Add( 24 | [pscustomobject]@{ 25 | Instance = $log.SqlInstance 26 | Database = $log.Database 27 | LogSize = $log.LogSize 28 | PercentUsed = $log.LogSpaceUsedPercent 29 | LogSpaceUsed = $log.LogSpaceUsed 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | catch { 36 | $stat = 'ERROR' 37 | $msg = $_.Exception.Message -join ';' 38 | } 39 | finally { 40 | Set-CmhOutputData 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Test-SqlRoleMembers.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlRoleMembers { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Database Role Members", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate SQL database ownership role", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | if ($null -ne $ScriptParams.Credential) { 17 | $rmembers = @(Get-DbaDbRole -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Role "db_owner" -SqlCredential $ScriptParams.Credential | Get-DbaDbRoleMember ) 18 | } else { 19 | $rmembers = @(Get-DbaDbRole -SqlInstance $ScriptParams.SqlInstance -Database $ScriptParams.Database -Role "db_owner" | Get-DbaDbRoleMember ) 20 | } 21 | if ($rmembers.Count -lt 1) { 22 | $stat = $except 23 | $msg = "Incorrect or unassigned ownership role" 24 | } 25 | } 26 | catch { 27 | $stat = 'ERROR' 28 | $msg = $_.Exception.Message -join ';' 29 | } 30 | finally { 31 | Set-CmhOutputData 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Test-SqlServerMemory.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlServerMemory { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Server Memory Allocation", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Validate maximum memory allocation of SQL instance", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [int]$MaxMemAllocation = Get-CmHealthDefaultValue -KeySet "sqlserver:MaxMemAllocationPercent" -DataSet $CmHealthConfig 13 | Write-Log -Message "MaxMemAllocation = $MaxMemAllocation" 14 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 15 | $stat = "PASS" 16 | $except = "FAIL" 17 | $msg = "No issues found" 18 | $unlimited = 2147483647 19 | # get total memory allocated to SQL Server in MB 20 | if ($null -ne $ScriptParams.Credential) { 21 | $cmax = (Get-DbaMaxMemory -SqlInstance $ScriptParams.SqlInstance -EnableException -ErrorAction Stop -SqlCredential $ScriptParams.Credential).MaxValue 22 | } else { 23 | $cmax = (Get-DbaMaxMemory -SqlInstance $ScriptParams.SqlInstance -EnableException -ErrorAction Stop).MaxValue 24 | } 25 | Write-Log -Message "current sql limit = $cmax MB" 26 | # get total physical memory of host in MB 27 | if ($ScriptParams.Credential) { 28 | $tmem = (Get-DbaComputerSystem -ComputerName $ScriptParams.SqlInstance -EnableException -ErrorAction SilentlyContinue -Credential $ScriptParams.Credential).TotalPhysicalMemory.Megabyte 29 | } else { 30 | $tmem = (Get-DbaComputerSystem -ComputerName $ScriptParams.SqlInstance -EnableException -ErrorAction SilentlyContinue).TotalPhysicalMemory.Megabyte 31 | } 32 | $tmem = [math]::Round($tmem, 0) 33 | Write-Log -Message "total physical memory = $tmem MB" 34 | $target = $tmem * $($MaxMemAllocation * 0.1) 35 | Write-Log -Message "target memory = $target" 36 | $target = [math]::Round($target, 0) 37 | Write-Log -Message "target memory = $target (rounded)" 38 | if ($cmax -eq $unlimited) { 39 | $stat = $except 40 | $msg = "Current SQL Server max memory is unlimited. Should be limited to $MaxMemAllocation percent of total physical memory." 41 | } else { 42 | if ($cmax -gt $tmem) { 43 | $stat = $except 44 | $msg = "Current limit $($cmax) MB is greater than physical $($tmem) MB - possibly due to virtual dynamic memory" 45 | } elseif ($cmax -gt $target) { 46 | $stat = "WARNING" 47 | $msg = "Current limit $($cmax) MB is greater than $MaxMemAllocation percent physical $($tmem) MB or $($target) MB" 48 | } 49 | } 50 | } 51 | catch { 52 | $stat = 'ERROR' 53 | $msg = $_.Exception.Message -join ';' 54 | } 55 | finally { 56 | Set-CmhOutputData 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Test-SqlServerVersion.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlServerVersion { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "Check SQL Server Version", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Check if SQL Server is a supported version", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | #[int]$Setting = Get-CmHealthDefaultValue -KeySet "keygroup:keyname" -DataSet $CmHealthConfig 13 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 14 | $stat = "PASS" # do not change this 15 | $except = "FAIL" # or "WARNING" 16 | $msg = "No issues found" # do not change this either 17 | $res = Get-DbaBuildReference -SqlInstance $ScriptParams.SqlInstance -Update 18 | $fname = "SQL Server $($res.NameLevel) $($res.SPLevel) $($res.CULevel)" 19 | $expdate = $res.SupportedUntil 20 | $supported = $(New-TimeSpan -Start (Get-Date) -End $expdate).TotalDays 21 | if ($supported -le 0) { 22 | $stat = $except 23 | $msg = "FAIL! $fname support has expired!" 24 | } elseif ($supported -le 30) { 25 | Write-Log -Message "Warning! Support will end in $supported days" 26 | $stat = "WARNING" 27 | $msg = "$fname support is will expire within $supported days" 28 | } else { 29 | $msg = "$fname support is supported until $($res.SupportedUntil)" 30 | Write-Log -Message "$fname is supported until $($res.SupportedUntil)" 31 | } 32 | $tempdata.Add( 33 | [pscustomobject]@{ 34 | SqlInstance = $res.SqlInstance 35 | SqlVersion = $fname 36 | Build = $res.Build 37 | NameLevel = $res.NameLevel 38 | SupportEnds = $res.SupportedUntil 39 | Message = $msg 40 | } 41 | ) 42 | } 43 | catch { 44 | $stat = 'ERROR' 45 | $msg = $_.Exception.Message -join ';' 46 | } 47 | finally { 48 | Set-CmhOutputData 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Test-SqlServiceSPN.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlServiceSPN { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Service Principal Names (SPNs)", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Verify SQL instance Service Principal Name registration", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | $sqlserver = $ScriptParams.SqlInstance 17 | Write-Log -Message "instance name = $sqlserver" 18 | $domain = $(Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty Domain) 19 | Write-Log -Message "domain suffix = $domain" 20 | $fqdn = "$($sqlserver)" 21 | $spn = "MSSQLSvc/$($fqdn)*" 22 | Write-Log -Message "SPN name = $spn" 23 | $res = $(SetSpn -T "$domain" -F -Q "$spn").Split("`n").Trim() 24 | if ($res -contains "No such SPN found.") { 25 | $stat = $except 26 | $msg = "No MSSQLSvc SPNs have been registered for $fqdn" 27 | } 28 | foreach ($sp in $res) { 29 | if (![string]::IsNullOrEmpty($sp) -and (-not($sp.StartsWith("Checking") -or $sp.StartsWith("Existing SPN")))) { 30 | $tempdata.Add( 31 | [pscustomobject]@{ 32 | HostName = $fqdn 33 | SPN = $sp 34 | } 35 | ) 36 | } 37 | } 38 | } 39 | catch { 40 | $stat = 'ERROR' 41 | $msg = $_.Exception.Message -join ';' 42 | } 43 | finally { 44 | Set-CmhOutputData 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Test-SqlUpdates.ps1: -------------------------------------------------------------------------------- 1 | function Test-SqlUpdates { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter()][string] $TestName = "SQL Server Update Status", 5 | [parameter()][string] $TestGroup = "configuration", 6 | [parameter()][string] $TestCategory = "SQL", 7 | [parameter()][string] $Description = "Verify SQL Updates and Service Packs", 8 | [parameter()][hashtable] $ScriptParams 9 | ) 10 | try { 11 | $startTime = (Get-Date) 12 | [System.Collections.Generic.List[PSObject]]$tempdata = @() # for detailed test output to return if needed 13 | $stat = "PASS" 14 | $except = "WARNING" 15 | $msg = "No issues found" 16 | if ($null -ne $ScriptParams.Credential) { 17 | Write-Log -Message "connecting with explicit credentials" 18 | try { 19 | $res = Test-DbaBuild -Latest -SqlInstance $ScriptParams.SqlInstance -SqlCredential $ScriptParams.Credential -ErrorAction SilentlyContinue 20 | if ($null -eq $res) { 21 | $bcurrent = $null 22 | $btarget = $null 23 | $stat = "ERROR" 24 | $msg = "Unable to connect to SQL instance ($($ScriptParams.SqlInstance))" 25 | } elseif ($res.Compliant -ne $True) { 26 | $bcurrent = $res.BuildLevel 27 | $btarget = $res.BuildTarget 28 | $stat = $except 29 | $msg = "SQL $($res.NameLevel) build level is $($bcurrent), but should be $($btarget): SP: $($res.SPTarget) CU: $($res.CULevel)" 30 | } 31 | $res | Foreach-Object { 32 | $tempdata.Add( 33 | [pscustomobject]@{ 34 | Build = $_.BuildLevel 35 | Target = $_.BuildTarget 36 | } 37 | ) 38 | } 39 | } 40 | catch { 41 | $res = Get-DbaBuild -SqlInstance $ScriptParams.SqlInstance 42 | $tempdata.Add( 43 | [PSCustomObject]@{ 44 | SqlInstance = $res.SqlInstance 45 | Build = $res.Build 46 | NameLevel = $res.NameLevel 47 | SPLevel = $res.SPLevel 48 | CULevel = $res.CULevel 49 | KBLevel = $res.KBLevel 50 | BuildLevel = $res.BuildLevel 51 | SupportedUntil = $res.SupportedUntil 52 | } 53 | ) 54 | } 55 | } else { 56 | Write-Log -Message "connecting with default credentials" 57 | try { 58 | $res = Test-DbaBuild -Latest -SqlInstance $ScriptParams.SqlInstance -ErrorAction SilentlyContinue 59 | if ($null -eq $res) { 60 | $bcurrent = $null 61 | $btarget = $null 62 | $stat = "ERROR" 63 | $msg = "Unable to connect to SQL instance ($($ScriptParams.SqlInstance))" 64 | } elseif ($res.Compliant -ne $True) { 65 | $bcurrent = $res.BuildLevel 66 | $btarget = $res.BuildTarget 67 | $stat = $except 68 | $msg = "SQL $($res.NameLevel) build level is $($bcurrent), but should be $($btarget): SP: $($res.SPTarget) CU: $($res.CULevel)" 69 | } 70 | $res | Foreach-Object { 71 | $tempdata.Add( 72 | [pscustomobject]@{ 73 | Build = $_.BuildLevel 74 | Target = $_.BuildTarget 75 | } 76 | ) 77 | } 78 | } 79 | catch { 80 | $res = Get-DbaBuild -SqlInstance $ScriptParams.SqlInstance 81 | $tempdata.Add( 82 | [PSCustomObject]@{ 83 | SqlInstance = $res.SqlInstance 84 | Build = $res.Build 85 | NameLevel = $res.NameLevel 86 | SPLevel = $res.SPLevel 87 | CULevel = $res.CULevel 88 | KBLevel = $res.KBLevel 89 | BuildLevel = $res.BuildLevel 90 | SupportedUntil = $res.SupportedUntil 91 | } 92 | ) 93 | } 94 | } 95 | } 96 | catch { 97 | $stat = 'ERROR' 98 | $msg = $_.Exception.Message -join ';' 99 | } 100 | finally { 101 | Set-CmhOutputData 102 | } 103 | } 104 | --------------------------------------------------------------------------------