checks/Agentv5.Tests.ps1
# So the v5 files need to be handled differently. # We will start with a BeforeDiscovery which will gather the Instance Information up front # Gather the instances we know are not contactable BeforeDiscovery { # Gather the instances we know are not contactable [string[]]$NotContactable = (Get-PSFConfig -Module dbachecks -Name global.notcontactable).Value # Get all the tags in use in this run $Tags = Get-CheckInformation -Check $Check -Group Agent -AllChecks $AllChecks -ExcludeCheck $ChecksToExclude $InstancesToTest = @(Get-Instance).ForEach{ # just add it to the Not Contactable list if ($NotContactable -notcontains $psitem) { $Instance = $psitem try { $InstanceSMO = Connect-DbaInstance -SqlInstance $Instance -ErrorAction SilentlyContinue -ErrorVariable errorvar } catch { $NotContactable += $Instance } if ($NotContactable -notcontains $psitem) { if ($null -eq $InstanceSMO.version) { $NotContactable += $Instance } # ToDo: Give cool message about Agent not existing on Express Edition?! elseif (($InstanceSMO).Edition -like "Express Edition*") {} else { # Get the relevant information for the checks in one go to save repeated trips to the instance and set values for Not Contactable tests if required Get-AllAgentInfo -Instance $InstanceSMO -Tags $Tags } } } } Write-PSFMessage -Message "Instances = $($InstancesToTest.Name)" -Level Verbose Set-PSFConfig -Module dbachecks -Name global.notcontactable -Value $NotContactable } Describe "Database Mail XPs" -Tag DatabaseMailEnabled, CIS, security -ForEach $InstancesToTest { $skip = Get-DbcConfigValue skip.agent.databasemailenabled Context "Testing Database Mail XPs on <_.Name>" { It "Testing Database Mail XPs is set to <_.DatabaseMailEnabled> on <_.Name>" -Skip:$skip { $PSItem.DatabaseMailEnabled | Should -Be $PSItem.ConfigValues.DatabaseMailEnabled -Because 'The Database Mail XPs setting should be set correctly' } } } Describe "SQL Agent Account" -Tag AgentServiceAccount, ServiceAccount -ForEach $InstancesToTest { $skipServiceState = Get-DbcConfigValue skip.agent.servicestate $skipServiceStartMode = Get-DbcConfigValue skip.agent.servicestartmode Context "Testing SQL Agent is running on <_.Name>" { It "SQL Agent should be running for <_.InstanceName> on <_.Name>" -Skip:$skipServiceState { $PSItem.Agent.State | Should -Be "Running" -Because 'The agent service is required to run SQL Agent jobs' } } if ($PSItem.IsClustered) { It "SQL Agent service should have a start mode of Manual for FailOver Clustered Instance <_.InstanceName> on <_.Name>" -Skip:$skipServiceStartMode { $PSItem.Agent.StartMode | Should -Be "Manual" -Because 'Clustered Instances required that the Agent service is set to manual' } } else { It "SQL Agent service should have a start mode of Automatic for standalone instance <_.InstanceName> on <_.Name>" -Skip:$skipServiceStartMode { $PSItem.Agent.StartMode | Should -Be "Automatic" -Because 'Otherwise the Agent Jobs wont run if the server is restarted' } } } Describe "DBA Operators" -Tag DbaOperator, Operator -ForEach $InstancesToTest { $skipOperatorName = Get-DbcConfigValue skip.agent.operatorname $skipOperatorEamil = Get-DbcConfigValue skip.agent.operatoremail Context "Testing DBA Operators exists on <_.Name>" { It "The Operator <_.ExpectedOperatorName> exists on <_.Name>" -Skip:$skipOperatorName -ForEach ($PSItem.Operator | Where-Object ExpectedOperatorName -ne 'null') { $PSItem.ExpectedOperatorName | Should -BeIn $PSItem.ActualOperatorName -Because 'This Operator is expected to exist' } It "The Operator email <_.ExpectedOperatorEmail> is correct on <_.Name>" -Skip:$skipOperatorEamil -ForEach ($PSItem.Operator | Where-Object ExpectedOperatorEmail -ne 'null') { $PSItem.ExpectedOperatorEmail | Should -BeIn $PSItem.ActualOperatorEmail -Because 'This operator email is expected to exist' } } } # Describe "Failsafe Operator" -Tags FailsafeOperator, Operator, $filename { # if ($NotContactable -contains $psitem) { # Context "Testing failsafe operator exists on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing failsafe operator exists on $psitem" { # $failsafeoperator = Get-DbcConfigValue agent.failsafeoperator # It "The Failsafe Operator exists on $psitem" { # (Connect-DbaInstance -SqlInstance $psitem).JobServer.AlertSystem.FailSafeOperator | Should -Be $failsafeoperator -Because 'The failsafe operator will ensure that any job failures will be notified to someone if not set explicitly' # } # } # } # } # Describe "Database Mail Profile" -Tags DatabaseMailProfile, $filename { # if ($NotContactable -contains $psitem) { # Context "Testing database mail profile is set on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing database mail profile is set on $psitem" { # $databasemailprofile = Get-DbcConfigValue agent.databasemailprofile # It "The Database Mail profile $databasemailprofile exists on $psitem" { # ((Get-DbaDbMailProfile -SqlInstance $InstanceSMO).Name -contains $databasemailprofile) | Should -Be $true -Because 'The database mail profile is required to send emails' # } # } # } # } # Describe "Agent Mail Profile" -Tags AgentMailProfile, $filename { # if ($NotContactable -contains $psitem) { # Context "Testing SQL Agent Alert System database mail profile is set on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing SQL Agent Alert System database mail profile is set on $psitem" { # $agentmailprofile = Get-DbcConfigValue agent.databasemailprofile # It "The SQL Server Agent Alert System should have an enabled database mail profile on $psitem" { # (Get-DbaAgentServer -SqlInstance $InstanceSMO).DatabaseMailProfile | Should -Be $agentmailprofile -Because 'The SQL Agent Alert System needs an enabled database mail profile to send alert emails' # } # } # } # } # Describe "Failed Jobs" -Tags FailedJob, $filename { # if ($NotContactable -contains $psitem) { # Context "Checking for failed enabled jobs on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # $maxdays = Get-DbcConfigValue agent.failedjob.since # $startdate = (Get-Date).AddDays( - $maxdays) # Context "Checking for failed enabled jobs since $startdate on $psitem" { # $excludecancelled = Get-DbcConfigValue agent.failedjob.excludecancelled # @(Get-DbaAgentJob -SqlInstance $psitem | Where-Object { $Psitem.IsEnabled -and ($psitem.LastRunDate -gt $startdate) }).ForEach{ # if ($psitem.LastRunOutcome -eq "Unknown") { # It -Skip "We chose to skip this as $psitem's last run outcome is unknown on $($psitem.SqlInstance)" { # $psitem.LastRunOutcome | Should -Be "Succeeded" -Because 'All Agent Jobs should have succeed this one is unknown - you need to investigate the failed jobs' # } # } # elseif (($psitem.LastRunOutcome -eq "Cancelled") -and ($excludecancelled -eq $true)) { # It -Skip "We chose to skip this as $psitem's last run outcome is cancelled on $($psitem.SqlInstance)" { # $psitem.LastRunOutcome | Should -Be "Succeeded" -Because 'All Agent Jobs should have succeed this one is unknown - you need to investigate the failed jobs' # } # } # else { # It "$psitem's last run outcome is $($psitem.LastRunOutcome) on $($psitem.SqlInstance)" { # $psitem.LastRunOutcome | Should -Be "Succeeded" -Because 'All Agent Jobs should have succeed - you need to investigate the failed jobs' # } # } # } # } # } # } # Describe "Valid Job Owner" -Tags ValidJobOwner, $filename { # [string[]]$targetowner = Get-DbcConfigValue agent.validjobowner.name # if ($NotContactable -contains $psitem) { # Context "Testing job owners on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing job owners on $psitem" { # @(Get-DbaAgentJob -SqlInstance $psitem -EnableException:$false).ForEach{ # It "Job $($psitem.Name) - owner $($psitem.OwnerLoginName) should be in this list ( $( [String]::Join(", ", $targetowner) ) ) on $($psitem.SqlInstance)" { # $psitem.OwnerLoginName | Should -BeIn $TargetOwner -Because "The account that is the job owner is not what was expected" # } # } # } # } # } # Describe "Invalid Job Owner" -Tags InValidJobOwner, $filename { # [string[]]$targetowner = Get-DbcConfigValue agent.invalidjobowner.name # if ($NotContactable -contains $psitem) { # Context "Testing job owners on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing job owners on $psitem" { # @(Get-DbaAgentJob -SqlInstance $psitem -EnableException:$false).ForEach{ # It "Job $($psitem.Name) - owner $($psitem.OwnerLoginName) should not be in this list ( $( [String]::Join(", ", $targetowner) ) ) on $($psitem.SqlInstance)" { # $psitem.OwnerLoginName | Should -Not -BeIn $TargetOwner -Because "The account that is the job owner has been defined as not valid" # } # } # } # } # } # Describe "Agent Alerts" -Tags AgentAlert, $filename { # $severity = Get-DbcConfigValue agent.alert.Severity # $messageid = Get-DbcConfigValue agent.alert.messageid # $AgentAlertJob = Get-DbcConfigValue agent.alert.Job # $AgentAlertNotification = Get-DbcConfigValue agent.alert.Notification # $skip = Get-DbcConfigValue skip.agent.alert # if ($NotContactable -contains $psitem) { # Context "Testing Agent Alerts Severity exists on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # Context "Testing Agent Alerts MessageID exists on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # $alerts = Get-DbaAgentAlert -SqlInstance $psitem # Context "Testing Agent Alerts Severity exists on $psitem" { # ForEach ($sev in $severity) { # It "Severity $sev Alert should exist on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.Severity -eq $sev }) | Should -be $true -Because "Recommended Agent Alerts to exists http://blog.extreme-advice.com/2013/01/29/list-of-errors-and-severity-level-in-sql-server-with-catalog-view-sysmessages/" # } # It "Severity $sev Alert should be enabled on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.Severity -eq $sev }).IsEnabled | Should -be $true -Because "Configured alerts should be enabled" # } # if ($AgentAlertJob) { # It "A job name for Severity $sev Alert on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.Severity -eq $sev }).jobname -ne $null | Should -be $true -Because "Should notify by SQL Agent Job" # } # } # if ($AgentAlertNotification) { # It "Severity $sev Alert should have a notification on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.Severity -eq $sev }).HasNotification -in 1, 2, 3, 4, 5, 6, 7 | Should -be $true -Because "Should notify by Agent notifications" # } # } # } # } # Context "Testing Agent Alerts MessageID exists on $psitem" { # ForEach ($mid in $messageid) { # It "Message_ID $mid Alert should exist on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.messageid -eq $mid }) | Should -be $true -Because "Recommended Agent Alerts to exists http://blog.extreme-advice.com/2013/01/29/list-of-errors-and-severity-level-in-sql-server-with-catalog-view-sysmessages/" # } # It "Message_ID $mid Alert should be enabled on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.messageid -eq $mid }) | Should -be $true -Because "Configured alerts should be enabled" # } # if ($AgentAlertJob) { # It "A Job name for Message_ID $mid Alert should be on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.messageid -eq $mid }).jobname -ne $null | Should -be $true -Because "Should notify by SQL Agent Job" # } # } # if ($AgentAlertNotification) { # It "Message_ID $mid Alert should have a notification on $psitem" -Skip:$skip { # ($alerts.Where{ $psitem.messageid -eq $mid }).HasNotification -in 1, 2, 3, 4, 5, 6, 7 | Should -be $true -Because "Should notify by Agent notifications" # } # } # } # } # } # } # Describe "Job History Configuration" -Tags JobHistory, $filename { # if ($NotContactable -contains $psitem) { # Context "Testing job history configuration on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing job history configuration on $psitem" { # [int]$minimumJobHistoryRows = Get-DbcConfigValue agent.history.maximumhistoryrows # [int]$minimumJobHistoryRowsPerJob = Get-DbcConfigValue agent.history.maximumjobhistoryrows # $AgentServer = Get-DbaAgentServer -SqlInstance $psitem -EnableException:$false # if ($minimumJobHistoryRows -eq -1) { # It "The maximum job history configuration should be set to disabled on $psitem" { # Assert-JobHistoryRowsDisabled -AgentServer $AgentServer -minimumJobHistoryRows $minimumJobHistoryRows # } # } # else { # It "The maximum job history number of rows configuration should be greater or equal to $minimumJobHistoryRows on $psitem" { # Assert-JobHistoryRows -AgentServer $AgentServer -minimumJobHistoryRows $minimumJobHistoryRows # } # It "The maximum job history rows per job configuration should be greater or equal to $minimumJobHistoryRowsPerJob on $psitem" { # Assert-JobHistoryRowsPerJob -AgentServer $AgentServer -minimumJobHistoryRowsPerJob $minimumJobHistoryRowsPerJob # } # } # } # } # } # Describe "Long Running Agent Jobs" -Tags LongRunningJob, $filename { # $skip = Get-DbcConfigValue skip.agent.longrunningjobs # $runningjobpercentage = Get-DbcConfigValue agent.longrunningjob.percentage # if (-not $skip) { # $query = "SELECT # JobName, # AvgSec, # start_execution_date as StartDate, # RunningSeconds, # RunningSeconds - AvgSec AS Diff # FROM # ( # SELECT # j.name AS JobName, # start_execution_date, # AVG(DATEDIFF(SECOND, 0, STUFF(STUFF(RIGHT('000000' # + CONVERT(VARCHAR(6),jh.run_duration),6),5,0,':'),3,0,':'))) AS AvgSec, # ja.start_execution_date as startdate, # DATEDIFF(second, ja.start_execution_date, GetDate()) AS RunningSeconds # FROM msdb.dbo.sysjobactivity ja # JOIN msdb.dbo.sysjobs j # ON ja.job_id = j.job_id # JOIN msdb.dbo.sysjobhistory jh # ON jh.job_id = j.job_id # WHERE start_execution_date is not null # AND stop_execution_date is null # AND run_duration < 235959 # AND run_duration >= 0 # AND ja.start_execution_date > DATEADD(day,-1,GETDATE()) # GROUP BY j.name,j.job_id,start_execution_date,stop_execution_date,ja.job_id # ) AS t # ORDER BY JobName;" # $runningjobs = Invoke-DbaQuery -SqlInstance $PSItem -Database msdb -Query $query # } # if ($NotContactable -contains $psitem) { # Context "Testing long running jobs on $psitem" { # It "Can't Connect to $Psitem" { # $false | Should -BeTrue -Because "The instance should be available to be connected to!" # } # } # } # else { # Context "Testing long running jobs on $psitem" { # if ($runningjobs) { # foreach ($runningjob in $runningjobs | Where-Object { $_.AvgSec -ne 0 }) { # It "Running job $($runningjob.JobName) duration should not be more than $runningjobpercentage % extra of the average run time on $psitem" -Skip:$skip { # Assert-LongRunningJobs -runningjob $runningjob -runningjobpercentage $runningjobpercentage # } # } # } # else { # It "There are no running jobs currently on $psitem" -Skip:$skip { # $True | SHould -BeTrue # } # } # } # } # } # Describe "Last Agent Job Run" -Tags LastJobRunTime, $filename { # $skip = Get-DbcConfigValue skip.agent.lastjobruntime # $runningjobpercentage = Get-DbcConfigValue agent.lastjobruntime.percentage # $maxdays = Get-DbcConfigValue agent.failedjob.since # if (-not $skip) { # $query = "IF OBJECT_ID('tempdb..#dbachecksLastRunTime') IS NOT NULL DROP Table #dbachecksLastRunTime # SELECT * INTO #dbachecksLastRunTime # FROM # ( # SELECT # j.job_id, # j.name AS JobName, # DATEDIFF(SECOND, 0, STUFF(STUFF(RIGHT('000000' + CONVERT(VARCHAR(6),jh.run_duration),6),5,0,':'),3,0,':')) AS Duration # FROM msdb.dbo.sysjobs j # INNER JOIN # ( # SELECT job_id, instance_id = MAX(instance_id) # FROM msdb.dbo.sysjobhistory # GROUP BY job_id # ) AS h # ON j.job_id = h.job_id # INNER JOIN # msdb.dbo.sysjobhistory AS jh # ON jh.job_id = h.job_id # AND jh.instance_id = h.instance_id # WHERE msdb.dbo.agent_datetime(jh.run_date, jh.run_time) > DATEADD(DAY,- $maxdays,GETDATE()) # AND jh.step_id = 0 # ) AS lrt # IF OBJECT_ID('tempdb..#dbachecksAverageRunTime') IS NOT NULL DROP Table #dbachecksAverageRunTime # SELECT * INTO #dbachecksAverageRunTime # FROM # ( # SELECT # job_id, # AVG(DATEDIFF(SECOND, 0, STUFF(STUFF(RIGHT('000000' + CONVERT(VARCHAR(6),run_duration),6),5,0,':'),3,0,':'))) AS AvgSec # FROM msdb.dbo.sysjobhistory hist # WHERE msdb.dbo.agent_datetime(run_date, run_time) > DATEADD(DAY,- $maxdays,GETDATE()) # AND Step_id = 0 # AND run_duration >= 0 # GROUP BY job_id # ) as art # SELECT # JobName, # Duration, # AvgSec, # Duration - AvgSec AS Diff # FROM #dbachecksLastRunTime lastrun # JOIN #dbachecksAverageRunTime avgrun # ON lastrun.job_id = avgrun.job_id # DROP Table #dbachecksLastRunTime # DROP Table #dbachecksAverageRunTime" # $lastagentjobruns = Invoke-DbaQuery -SqlInstance $PSItem -Database msdb -Query $query # Context "Testing last job run time on $psitem" { # foreach ($lastagentjobrun in $lastagentjobruns | Where-Object { $_.AvgSec -ne 0 }) { # It "Job $($lastagentjobrun.JobName) last run duration should be not be greater than $runningjobpercentage % extra of the average run time on $psitem" -Skip:$skip { # Assert-LastJobRun -lastagentjobrun $lastagentjobrun -runningjobpercentage $runningjobpercentage # } # } # } # } # else { # Context "Testing last job run time on $psitem" { # It "Job average run time on $psitem" -Skip { # Assert-LastJobRun -lastagentjobrun $lastagentjobrun -runningjobpercentage $runningjobpercentage # } # } # } # } |