SQL Server Agent Job Monitoring with PowerShell

问题

若要继续该 技巧系列,此PowerShell脚本将收集信息以为已为此监视解决方案注册的所有服务器构建SQL Server代理作业清单 

该模块将从所有指定的实例中收集有关SQL Server代理作业的信息。它将连接到inventory.MasterServerList表中的每个服务器实例,并捕获每个实例的数据。

与每个SQL Server实例的连接将基于清单.MasterServerList表中的设置。

 

 

先决条件

为了使用此模块,您需要 创建在此处找到的核心对象

数据库对象

对于此特定模块,将仅创建一个表,该表用于存储每个实例中SQL Server代理作业的信息。

这是表的结构,因此您可以了解将存储哪些数据。(如果要向该表中添加更多字段,请确保在PowerShell脚本中调整结构,并调整将用于处理其他列的相应逻辑。)

表格

    • inventory.Jobs- this will store information about SQL Agent Jobs for all servers
      • serverId - serverid ties back to inventory.MasterServerList
      • job_name - name of SQL Agent job
      • is_enabled - if job is enabled
      • owner - owner of the job
      • date_created - when job was created
      • date_modified - when job was last changed
      • frequency- how often job is run
      • days - how often job is run
      • execution_time- last execution time of the job
      • data_collection_timestamp - when data was last collected

PowerShell脚本

创建上述对象并将数据插入清单的PowerShell脚本称为:Jobs表:

  • Get-MSSQL-Instance-Jobs.ps1

该脚本包括验证,可帮助您检查是否缺少某些关键元素才能使脚本成功运行。例如,它将确认stocking.MasterServerList表存在并且至少已注册1个活动实例以便可以使用某些东西。

PowerShell脚本应存储在集中位置。在本系列中,我们一直使用“ C:\ Temp”。

 

 

如果要更改工作目录并使用“ C:\ temp”以外的其他名称,则需要编辑PowerShell脚本并修改第一行,如下所示。将来的所有模块也将是这种情况。

powershell脚本参数

如何使用

导航到创建文件的文件夹,然后可以运行PowerShell脚本,如下所示:

选项1

  • 右键单击Get-MSSQL-Instance-Jobs.ps1,然后选择“使用PowerShell运行”。

选项2

  • 打开命令窗口,然后导航到保存上述文件的文件夹并运行:
powershell“ C:\ temp \ Get-MSSQL-Instance-Jobs.ps1”

选项3

  • 将其安排为SQL Agent作业,以在设置的基础上运行PowerShell脚本。

选项4

  • 将其调度为Windows Task Scheduler作业,以在设置的基础上运行PowerShell脚本。

检查数据库和对象的创建

运行PowerShell脚本后,我们可以看到新对象创建的 ventory.Jobs

数据库对象

If we query the inventory.Jobs table, we can see the data that has been collected.

query results

Important note: the PowerShell script will store only the information from the very last execution. If you’d like to keep information from previous executions, you will have to modify the script and adapt it to your particular use case.

Checking for Errors

To check for errors query the monitoring.ErrorLog table using the following query:

 
 
SELECT *
FROM monitoring.ErrorLog
WHERE script = 'Get-MSSQL-Instance-Jobs'

If you’d like to include the SQL Server instance that had errors, issue the query like this:

SELECT 
   CASE WHEN msl.instance = 'MSSQLSERVER' THEN msl.server_name ELSE 
   CONCAT(msl.server_name,'\',msl.instance) 
   END AS instance,  
   e.script,
   e.message,
   e.error_timestamp
FROM monitoring.ErrorLog e
JOIN inventory.MasterServerList msl ON msl.serverId = e.serverId
WHERE e.script = 'Get-MSSQL-Instance-Jobs'

Useful Queries

By collecting all the data related to the jobs across all your instances, you might answer things like below.

Find jobs that include Backup

What’s the schedule backups across all the instances. Here we are looking for the keyword "Backup" in the job name.

SELECT
   CASE WHEN msl.instance = 'MSSQLSERVER' 
   THEN msl.server_name 
   ELSE CONCAT(msl.server_name,'\',msl.instance) 
   END AS instance, 
   j.job_name,
   j.frequency,
   j.days,
   j.execution_time
FROM inventory.Jobs j
JOIN inventory.MasterServerList msl ON j.serverId = msl.serverId
WHERE j.job_name LIKE '%Backup%'

Find jobs where owner is not sa

I would like to know which jobs have owners other than sa. Here we are looking at the owner column.

SELECT
   CASE WHEN msl.instance = 'MSSQLSERVER' 
   THEN msl.server_name ELSE         
   CONCAT(msl.server_name,'\',msl.instance) 
   END AS instance,
   j.job_name,
   j.owner
FROM inventory.Jobs j
JOIN inventory.MasterServerList msl ON j.serverId = msl.serverId
WHERE j.owner <> 'sa'


script block



Get-Content "C:\temp\Settings.ini" | foreach-object -begin {$h=@{}} -process { $k = [regex]::split($_,'='); if(($k[0].CompareTo("") -ne 0) -and ($k[0].StartsWith("[") -ne $True)) { $h.Add($k[0], $k[1]) } }
$server        = $h.Get_Item("centralServer")
$inventoryDB   = $h.Get_Item("inventoryDB")

if($server.length -eq 0){
    Write-Host "You must provide a value for the 'centralServer' in your Settings.ini file!!!" -BackgroundColor Red
    exit
}
if($inventoryDB.length -eq 0){
    Write-Host "You must provide a value for the 'inventoryDB' in your Settings.ini file!!!" -BackgroundColor Red
    exit
}

$mslExistenceQuery = "
SELECT Count(*) FROM dbo.sysobjects where id = object_id(N'[inventory].[MasterServerList]') and OBJECTPROPERTY(id, N'IsTable') = 1
"
$result = Invoke-Sqlcmd -Query $mslExistenceQuery -Database $inventoryDB -ServerInstance $server -ErrorAction Stop 

if($result[0] -eq 0){
    Write-Host "The table [inventory].[MasterServerList] wasn't found!!!" -BackgroundColor Red 
    exit
}

$enoughInstancesInMSLQuery = "
SELECT COUNT(*) FROM inventory.MasterServerList WHERE is_active = 1
"
$result = Invoke-Sqlcmd -Query $enoughInstancesInMSLQuery -Database $inventoryDB -ServerInstance $server -ErrorAction Stop 

if($result[0] -eq 0){
    Write-Host "There are no active instances registered to work with!!!" -BackgroundColor Red 
    exit
}

if ($h.Get_Item("username").length -gt 0 -and $h.Get_Item("password").length -gt 0) {
    $username   = $h.Get_Item("username")
    $password   = $h.Get_Item("password")
}

#Function to execute queries (depending on if the user will be using specific credentials or not)
function Execute-Query([string]$query,[string]$database,[string]$instance,[int]$trusted){
    if($trusted -eq 1){ 
        try{
            Invoke-Sqlcmd -Query $query -Database $database -ServerInstance $instance -ErrorAction Stop
        }
        catch{
            [string]$message = $_
            $errorQuery = "INSERT INTO monitoring.ErrorLog VALUES((SELECT serverId FROM inventory.MasterServerList WHERE CASE instance WHEN 'MSSQLSERVER' THEN server_name ELSE CONCAT(server_name,'\',instance) END = '$($instance)'),'Get-MSSQL-Instance-Jobs','"+$message.replace("'","''")+"',GETDATE())"
            Invoke-Sqlcmd -Query $errorQuery -Database $inventoryDB -ServerInstance $server -ErrorAction Stop
        }
    }
    else{
        try{
            Invoke-Sqlcmd -Query $query -Database $database -ServerInstance $instance -Username $username -Password $password -ErrorAction Stop
        }
        catch{
            [string]$message = $_
            $errorQuery = "INSERT INTO monitoring.ErrorLog VALUES((SELECT serverId FROM inventory.MasterServerList WHERE CASE instance WHEN 'MSSQLSERVER' THEN server_name ELSE CONCAT(server_name,'\',instance) END = '$($instance)'),'Get-MSSQL-Instance-Jobs','"+$message.replace("'","''")+"',GETDATE())"
            Invoke-Sqlcmd -Query $errorQuery -Database $inventoryDB -ServerInstance $server -ErrorAction Stop
        }
    }
}

###############################
#Jobs inventory table creation#
###############################
$jobsInventoryTableQuery = "
IF NOT EXISTS (SELECT * FROM dbo.sysobjects where id = object_id(N'[inventory].[Jobs]') and OBJECTPROPERTY(id, N'IsTable') = 1)
BEGIN
CREATE TABLE [inventory].[Jobs](
    [serverId]                  [INT]NOT NULL,
    [job_name]                  [VARCHAR](128) NOT NULL,
    [is_enabled]                [TINYINT] NULL,
    [owner]                     [VARCHAR](32) NULL,
    [date_created]              [DATETIME] NULL,
    [date_modified]             [DATETIME] NULL,
    [frequency]                 [VARCHAR](32) NULL,
    [days]                      [VARCHAR](64) NULL,
    [execution_time]            [VARCHAR](64) NULL,
    [data_collection_timestamp] [DATETIME] NOT NULL

    CONSTRAINT PK_JobsInventory PRIMARY KEY CLUSTERED (serverId,job_name),

    CONSTRAINT FK_JobsInventory_MasterServerList FOREIGN KEY (serverId) REFERENCES inventory.MasterServerList(serverId) ON DELETE NO ACTION ON UPDATE NO ACTION,

) ON [PRIMARY]
END
"
Execute-Query $jobsInventoryTableQuery $inventoryDB $server 1

#TRUNCATE the inventory.Jobs table to always store a fresh copy of the information from all the instances
Execute-Query "TRUNCATE TABLE inventory.Jobs" $inventoryDB $server 1

#Select the instances from the Master Server List that will be traversed
$instanceLookupQuery = "
SELECT
        serverId,
        trusted,
		CASE instance 
			WHEN 'MSSQLSERVER' THEN server_name                                   
			ELSE CONCAT(server_name,'\',instance)
		END AS 'instance',
		CASE instance 
			WHEN 'MSSQLSERVER' THEN ip                                   
			ELSE CONCAT(ip,'\',instance)
		END AS 'ip',
        CONCAT(ip,',',port) AS 'port'
FROM inventory.MasterServerList
WHERE is_active = 1
"
$instances = Execute-Query $instanceLookupQuery $inventoryDB $server 1

#For each instance, fetch the desired information
$jobsInformationQuery = "
SELECT
	SERVERPROPERTY('SERVERNAME') AS 'instance',
	sysjobs.name AS 'name',
	sysjobs.enabled AS 'enabled',
	SUSER_SNAME(sysjobs.owner_sid) AS 'owner',
	sysjobs.date_created AS 'date_created',
	sysjobs.date_modified AS 'date_modified',
	CASE
		WHEN freq_type = 4 THEN 'Daily'
	END AS 'frequency',
	'Every ' + CAST (freq_interval AS VARCHAR(3)) + ' day(s)' AS 'days',
	CASE
		WHEN freq_subday_type = 2 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' seconds ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		WHEN freq_subday_type = 4 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' minutes ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		WHEN freq_subday_type = 8 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' hours '   + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		ELSE 'Starting at ' 
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
	END AS 'execution_time'
FROM msdb.dbo.sysjobs
JOIN msdb.dbo.sysjobschedules ON sysjobs.job_id = sysjobschedules.job_id
JOIN msdb.dbo.sysschedules ON sysjobschedules.schedule_id = sysschedules.schedule_id
WHERE freq_type = 4

UNION

-- jobs with a weekly schedule
SELECT
	SERVERPROPERTY('SERVERNAME') AS 'instance',
	sysjobs.name AS 'name',
	sysjobs.enabled AS 'enabled',
	SUSER_SNAME(sysjobs.owner_sid) AS 'owner',
	sysjobs.date_created AS 'date_created',
	sysjobs.date_modified AS 'date_modified',
	CASE	
		WHEN freq_type = 8 THEN 'Weekly'
	END AS 'frequency',
	CASE WHEN freq_interval&2 = 2 THEN 'Monday, ' ELSE '' END
	+CASE WHEN freq_interval&4 = 4 THEN 'Tuesday, ' ELSE '' END
	+CASE WHEN freq_interval&8 = 8 THEN 'Wednesday, ' ELSE '' END
	+CASE WHEN freq_interval&16 = 16 THEN 'Thursday, ' ELSE '' END
	+CASE WHEN freq_interval&32 = 32 THEN 'Friday, ' ELSE '' END
	+CASE WHEN freq_interval&64 = 64 THEN 'Saturday, ' ELSE '' END
	+CASE WHEN freq_interval&1 = 1 THEN 'Sunday' ELSE '' END
	AS 'Days',
	CASE
		WHEN freq_subday_type = 2 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' seconds ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':') 
		WHEN freq_subday_type = 4 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' minutes ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		WHEN freq_subday_type = 8 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' hours '   + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		ELSE 'Starting at ' 
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
	END AS 'execution_time'
	FROM msdb.dbo.sysjobs
JOIN msdb.dbo.sysjobschedules ON sysjobs.job_id = sysjobschedules.job_id
JOIN msdb.dbo.sysschedules ON sysjobschedules.schedule_id = sysschedules.schedule_id
WHERE freq_type = 8
UNION

-- jobs with a monthly schedule
SELECT
	SERVERPROPERTY('SERVERNAME') AS 'instance',
	sysjobs.name AS 'name',
	sysjobs.enabled AS 'enabled',
	SUSER_SNAME(sysjobs.owner_sid) AS 'owner',
	sysjobs.date_created AS 'date_created',
	sysjobs.date_modified AS 'date_modified',
	CASE	
		WHEN freq_type = 16 THEN 'Monthly'
	END AS 'frequency',
	CASE WHEN freq_interval&2 = 2 THEN 'Monday, ' ELSE '' END
	+CASE WHEN freq_interval&4 = 4 THEN 'Tuesday, ' ELSE '' END
	+CASE WHEN freq_interval&8 = 8 THEN 'Wednesday, ' ELSE '' END
	+CASE WHEN freq_interval&16 = 16 THEN 'Thursday, ' ELSE '' END
	+CASE WHEN freq_interval&32 = 32 THEN 'Friday, ' ELSE '' END
	+CASE WHEN freq_interval&64 = 64 THEN 'Saturday, ' ELSE '' END
	+CASE WHEN freq_interval&1 = 1 THEN 'Sunday' ELSE '' END
	AS 'Days',
	CASE
		WHEN freq_subday_type = 2 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' seconds ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':') 
		WHEN freq_subday_type = 4 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' minutes ' + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		WHEN freq_subday_type = 8 THEN 'Every ' + CAST(freq_subday_interval AS VARCHAR(7)) 
		+ ' hours '   + 'starting at '
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
		ELSE 'Starting at ' 
		+ STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(active_start_time AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':')
	END AS 'execution_time'
FROM msdb.dbo.sysjobs
JOIN msdb.dbo.sysjobschedules ON sysjobs.job_id = sysjobschedules.job_id
JOIN msdb.dbo.sysschedules ON sysjobschedules.schedule_id = sysschedules.schedule_id
WHERE freq_type = 16
ORDER BY name
"

foreach ($instance in $instances){
   if($instance.trusted -eq 'True'){$trusted = 1}else{$trusted = 0}
   $sqlInstance = $instance.instance

   #Go grab the complementary information for the instance
   Write-Host "Fetching jobs information from instance" $instance.instance
   
   #Special logic for cases where the instance isn't reachable by name
   try{
        $results = Execute-Query $jobsInformationQuery "master" $sqlInstance $trusted
   }
   catch{
        $sqlInstance = $instance.ip
        [string]$message = $_
        $query = "INSERT INTO monitoring.ErrorLog VALUES("+$instance.serverId+",'Get-MSSQL-Instance-Jobs','"+$message.replace("'","''")+"',GETDATE())"
        Execute-Query $query $inventoryDB $server 1

        try{  
            $results = Execute-Query $jobsInformationQuery "master" $sqlInstance $trusted
        }
        catch{
            $sqlInstance = $instance.port
            [string]$message = $_
            $query = "INSERT INTO monitoring.ErrorLog VALUES("+$instance.serverId+",'Get-MSSQL-Instance-Jobs','"+$message.replace("'","''")+"',GETDATE())"
            Execute-Query $query $inventoryDB $server 1

            try{
                $results = Execute-Query $jobsInformationQuery "master" $sqlInstance $trusted
            }
            catch{
                [string]$message = $_
                $query = "INSERT INTO monitoring.ErrorLog VALUES("+$instance.serverId+",'Get-MSSQL-Instance-Jobs','"+$message.replace("'","''")+"',GETDATE())"
                Execute-Query $query $inventoryDB $server 1
            }
        }
   }
   
   #Perform the INSERT in the inventory.Jobs only if it returns information
   if($results.Length -ne 0){

      #Build the insert statement
      $insert = "INSERT INTO inventory.Jobs VALUES"
      foreach($result in $results){   
         $insert += "
         (
          '"+$instance.serverId+"',
          '"+$result['name']+"',
           "+$result['enabled']+",
          '"+$result['owner']+"',
          '"+$result['date_created']+"',
          '"+$result['date_modified']+"',
          '"+$result['frequency']+"',
          '"+$result['days']+"',
          '"+$result['execution_time']+"',
          GETDATE()
         ),
         "
       }

       $insert = $insert -replace "''",'NULL'
       $insert = $insert -replace "NULLNULL",'NULL'
       Execute-Query $insert.Substring(0,$insert.LastIndexOf(',')) $inventoryDB $server 1
   }
}

Write-Host "Done!"

  

posted @ 2020-07-02 09:45  Javi  阅读(262)  评论(0编辑  收藏  举报