SQL Agent configuration for Operators, Alerts and Proxies (WIP) (#621)

* Initial non-refactored SQL Agent alert classes (WIP)

* Move agent classes into subdirectories

* Refactor the agent config code a bit more

* Add more implementation for handlers

* Add more code to the create alert handler

* Clean up agent alert class

* Clean up alert methods a bit

* Initial Operator contracts

* Additonal SQL Agent config changes

* More Proxy config cleanup

* Cleanup AgentProxy class

* Additional cleanups

* Run SRGen

* Add security service to create credential objects
This commit is contained in:
Karl Burtram
2018-05-30 08:58:02 -07:00
committed by GitHub
parent f5efe18e1b
commit 4f214f43e9
49 changed files with 8152 additions and 257 deletions

View File

@@ -0,0 +1,166 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Data;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Diagnostics;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Smo.Agent;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
/// <summary>
/// AgentAlert class
/// </summary>
internal class AgentAlert : ManagementActionBase
{
/// <summary>
/// Agent alert info instance
/// </summary>
private AgentAlertInfo alertInfo = null;
/// <summary>
/// Default constructor that will be used to create dialog
/// </summary>
/// <param name="dataContainer"></param>
public AgentAlert(CDataContainer dataContainer, AgentAlertInfo alertInfo)
{
this.alertInfo = alertInfo;
this.DataContainer = dataContainer;
}
private static string GetAlertName(CDataContainer container)
{
string alertName = null;
STParameters parameters = new STParameters();
parameters.SetDocument(container.Document);
if (parameters.GetParam("alert", ref alertName) == false || string.IsNullOrWhiteSpace(alertName))
{
throw new Exception("SRError.AlertNameCannotBeBlank");
}
return alertName.Trim();
}
public bool Drop()
{
// fail if the user is not in the sysadmin role
if (!this.DataContainer.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin))
{
return false;
}
string alertName = GetAlertName(this.DataContainer);
if (this.DataContainer.Server.JobServer.Alerts.Contains(alertName))
{
this.DataContainer.Server.JobServer.Alerts.Refresh();
if (this.DataContainer.Server.JobServer.Alerts.Contains(alertName))
{
Alert alert = this.DataContainer.Server.JobServer.Alerts[alertName];
if (alert != null)
{
alert.DropIfExists();
}
}
}
return true;
}
public bool CreateOrUpdate()
{
Alert alert = null;
string alertName = GetAlertName(this.DataContainer);
bool createNewAlert = true;
try
{
// check if alert already exists
if (this.DataContainer.Server.JobServer.Alerts.Contains(alertName))
{
this.DataContainer.Server.JobServer.Alerts.Refresh(); // Try to recover
if (this.DataContainer.Server.JobServer.Alerts.Contains(alertName)) // If still no luck
{
// use the existing alert
alert = this.DataContainer.Server.JobServer.Alerts[alertName];
createNewAlert = false;
}
}
// create a new alert
if (createNewAlert)
{
alert = new Alert(this.DataContainer.Server.JobServer, alertName);
}
// apply changes from input parameter to SMO alert object
UpdateAlertProperties(alert);
if (createNewAlert)
{
alert.Create();
}
else
{
// don't bother trying to update the alert unless they are sysadmin.
if (!this.DataContainer.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin))
{
return false;
}
alert.Alter();
}
return true;
}
catch (Exception e)
{
ApplicationException applicationException;
if (createNewAlert)
{
applicationException = new ApplicationException("AgentAlertSR.CannotCreateNewAlert", e);
}
else
{
applicationException = new ApplicationException("AgentAlertSR.CannotAlterAlert", e);
}
throw applicationException;
}
}
private void UpdateAlertProperties(Alert alert)
{
if (alert == null)
{
throw new ArgumentNullException("alert");
}
if (!string.IsNullOrWhiteSpace(this.DataContainer.ConnectionInfo.DatabaseName))
{
alert.DatabaseName = this.DataContainer.ConnectionInfo.DatabaseName;
}
if (!string.IsNullOrWhiteSpace(this.alertInfo.CategoryName))
{
alert.CategoryName = this.alertInfo.CategoryName;
}
alert.IsEnabled = this.alertInfo.IsEnabled;
if (alertInfo.AlertType == Contracts.AlertType.SqlServerEvent)
{
alert.Severity = this.alertInfo.Severity;
alert.MessageID = this.alertInfo.MessageId;
if (!string.IsNullOrWhiteSpace(this.alertInfo.EventDescriptionKeyword))
{
alert.EventDescriptionKeyword = this.alertInfo.EventDescriptionKeyword;
}
}
}
}
}

View File

@@ -0,0 +1,947 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Diagnostics;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Smo.Agent;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
/// <summary>
/// Agent Operators management class
/// </summary>
internal class AgentOperator : ManagementActionBase
{
private AgentOperatorInfo operatorInfo;
AgentOperatorsData operatorsData = null;
/// <summary>
/// Constructor
/// </summary>
public AgentOperator(CDataContainer dataContainer, AgentOperatorInfo operatorInfo)
{
try
{
if (dataContainer == null)
{
throw new ArgumentNullException("dataContainer");
}
if (operatorInfo == null)
{
throw new ArgumentNullException("operatorInfo");
}
this.operatorInfo = operatorInfo;
this.DataContainer = dataContainer;
STParameters parameters = new STParameters();
parameters.SetDocument(dataContainer.Document);
string agentOperatorName = null;
if (parameters.GetParam("operator", ref agentOperatorName))
{
this.operatorsData = new AgentOperatorsData(dataContainer, agentOperatorName);
}
else
{
throw new ArgumentNullException("agentOperatorName");
}
}
catch(Exception e)
{
throw new ApplicationException("AgentOperatorsSR.FailedToCreateInitializeAgentOperatorDialog", e);
}
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if(disposing)
{
}
base.Dispose(disposing);
}
public bool CreateOrUpdate()
{
this.operatorsData.ApplyChanges(this.operatorInfo);
return true;
}
}
#region internal structures
/// <summary>
/// Provides data to be consumed in the job notification grid
/// </summary>
internal struct AgentJobNotificationHelper
{
/// <summary>
/// constructor
/// </summary>
/// <param name="name">job name</param>
/// <param name="notifyEmail"></param>
/// <param name="notifyPager"></param>
public AgentJobNotificationHelper(string name, CompletionAction notifyEmail, CompletionAction notifyPager)
{
this.Name = name;
this.NotifyEmail = notifyEmail;
this.NotifyPager = notifyPager;
}
/// <summary>
/// Name of the job
/// </summary>
public string Name;
/// <summary>
/// job email notification action
/// </summary>
public CompletionAction NotifyEmail;
/// <summary>
/// job pager notification action
/// </summary>
public CompletionAction NotifyPager;
}
/// <summary>
/// Provides data to be consumed in the alert notification grid
/// </summary>
internal struct AgentAlertNotificationHelper
{
/// <summary>
/// constructor
/// </summary>
/// <param name="name">Name of the alert</param>
/// <param name="notifyEmail"></param>
/// <param name="notifyPager"></param>
/// <param name="alert">Alert object</param>
public AgentAlertNotificationHelper(string name, bool notifyEmail, bool notifyPager, Alert alert)
{
this.Name = name;
this.NotifyEmail = notifyEmail;
this.NotifyPager = notifyPager;
this.Alert = alert;
}
/// <summary>
/// Alert name
/// </summary>
public string Name;
/// <summary>
/// Indicates whether the alert will notify the operator through email
/// </summary>
public bool NotifyEmail;
/// <summary>
/// Indicates whether the alert will notify the operator through pager
/// </summary>
public bool NotifyPager;
/// <summary>
/// Alert object. optimisation to stop us having to lookup the alert object when needed
/// </summary>
public Alert Alert;
}
#endregion
/// <summary>
/// Proxy class for the AgentOperators dialog and property pages.
/// Performs lazy instantiation of groups of data based around the operators dialog property pages
/// </summary>
internal class AgentOperatorsData
{
#region members
/// <summary>
/// Data container
/// </summary>
CDataContainer dataContainer;
/// <summary>
/// Original operator name. Empty if we are creating a new operator
/// </summary>
string originalOperatorName = String.Empty;
/// <summary>
/// Indicates whether we are creating an operator or not
/// </summary>
bool createMode;
/// <summary>
/// Has then data for the general page been initialised
/// </summary>
bool generalInitialized = false;
/// <summary>
/// has the data for the history page been initialised
/// </summary>
bool historyInitialized = false;
/// <summary>
/// True if this operator cannot be modified
/// </summary>
bool readOnly = false;
#region general items
string name;
bool enabled;
string emailAddress;
string pagerAddress;
SqlServer.Management.Smo.Agent.WeekDays pagerDays;
DateTime weekdayStartTime;
DateTime weekdayEndTime;
DateTime saturdayStartTime;
DateTime saturdayEndTime;
DateTime sundayStartTime;
DateTime sundayEndTime;
#endregion
#region notification items
/// <summary>
/// will be null if the alert notifications have not been initialised
/// </summary>
IList<AgentAlertNotificationHelper> alertNotifications;
/// <summary>
/// will be null if the job notifications have not been initialised
/// </summary>
IList<AgentJobNotificationHelper> jobNotifications;
#endregion
#region history items
DateTime lastEmailDate;
DateTime lastPagerDate;
#endregion
#endregion
#region properties
/// <summary>
/// indicates if the data is in create mode
/// </summary>
public bool Creating
{
get
{
return this.createMode;
}
}
/// <summary>
/// name of the object
/// </summary>
public string Name
{
get
{
LoadGeneralData();
return name;
}
set
{
LoadGeneralData();
name = value;
}
}
/// <summary>
/// Indicates if the dataobject is readonly
/// </summary>
public bool ReadOnly
{
get
{
return this.readOnly;
}
}
#region general items
/// <summary>
/// indicates whether or not the operator is enabled
/// </summary>
public bool Enabled
{
get
{
LoadGeneralData();
return enabled;
}
set
{
LoadGeneralData();
enabled = value;
}
}
/// <summary>
/// email address of this operator
/// </summary>
public string EmailAddress
{
get
{
LoadGeneralData();
return this.emailAddress;
}
set
{
LoadGeneralData();
this.emailAddress = value;
}
}
/// <summary>
/// pager address of this operator
/// </summary>
public string PagerAddress
{
get
{
LoadGeneralData();
return this.pagerAddress;
}
set
{
LoadGeneralData();
this.pagerAddress = value;
}
}
/// <summary>
/// the days of the week the operator is active
/// </summary>
public SqlServer.Management.Smo.Agent.WeekDays PagerDays
{
get
{
LoadGeneralData();
return this.pagerDays;
}
set
{
LoadGeneralData();
this.pagerDays = value;
}
}
/// <summary>
/// Weekday start time for this operator to be active
/// </summary>
public DateTime WeekdayStartTime
{
get
{
LoadGeneralData();
return this.weekdayStartTime;
}
set
{
LoadGeneralData();
this.weekdayStartTime = value;
}
}
/// <summary>
/// Weekday end time for this operator to be active
/// </summary>
public DateTime WeekdayEndTime
{
get
{
LoadGeneralData();
return this.weekdayEndTime;
}
set
{
LoadGeneralData();
this.weekdayEndTime = value;
}
}
/// <summary>
/// Saturday start time for this operator to be active
/// </summary>
public DateTime SaturdayStartTime
{
get
{
LoadGeneralData();
return this.saturdayStartTime;
}
set
{
LoadGeneralData();
this.saturdayStartTime = value;
}
}
/// <summary>
/// Saturday end time for this operator to be active
/// </summary>
public DateTime SaturdayEndTime
{
get
{
LoadGeneralData();
return this.saturdayEndTime;
}
set
{
LoadGeneralData();
this.saturdayEndTime = value;
}
}
/// <summary>
/// Sunday start time for this operator to be active
/// </summary>
public DateTime SundayStartTime
{
get
{
LoadGeneralData();
return this.sundayStartTime;
}
set
{
LoadGeneralData();
this.sundayStartTime = value;
}
}
/// <summary>
/// Saturday end time for this operator to be active
/// </summary>
public DateTime SundayEndTime
{
get
{
LoadGeneralData();
return this.sundayEndTime;
}
set
{
LoadGeneralData();
this.sundayEndTime = value;
}
}
#endregion
#region notification items
/// <summary>
/// Alerts that notify this operator
/// </summary>
public IList<AgentAlertNotificationHelper> AlertNotifications
{
get
{
LoadAlertNotificationData();
return this.alertNotifications;
}
set
{
this.alertNotifications = value;
}
}
/// <summary>
/// Jobs that notify this operator. This has to be set through the jobs dialog and is read only
/// </summary>
public IList<AgentJobNotificationHelper> JobNotifications
{
get
{
LoadJobNotificationData();
return this.jobNotifications;
}
}
#endregion
#region history items
/// <summary>
/// Date this operator was last emailed
/// </summary>
public DateTime LastEmailDate
{
get
{
LoadHistoryData();
return this.lastEmailDate;
}
}
/// <summary>
/// Date this operator was last paged
/// </summary>
public DateTime LastPagerDate
{
get
{
LoadHistoryData();
return this.lastPagerDate;
}
}
#endregion
#endregion
#region Constructors
public AgentOperatorsData(CDataContainer dataContainer, string operatorName)
{
if (dataContainer == null)
{
throw new ArgumentNullException("dataContainer");
}
if (operatorName == null)
{
throw new ArgumentNullException("operatorName");
}
this.dataContainer = dataContainer;
this.readOnly = !this.dataContainer.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin);
this.originalOperatorName = operatorName;
this.name = operatorName;
this.createMode = operatorName.Length == 0;
}
#endregion
#region data loading
/// <summary>
/// load data for the general tab. This can be called multiple times but will only load the data once intially
/// or after a reset
/// </summary>
private void LoadGeneralData()
{
if(this.generalInitialized)
return;
// load defaults if we're creating
if(createMode)
{
LoadGeneralDefaults();
return;
}
// lookup the operator this will throw if it has been deleted.
Microsoft.SqlServer.Management.Smo.Agent.Operator currentOperator = GetCurrentOperator();
// setup the members
this.name = currentOperator.Name;
this.enabled = currentOperator.Enabled;
this.emailAddress = currentOperator.EmailAddress;
this.pagerAddress = currentOperator.PagerAddress;
this.pagerDays = currentOperator.PagerDays;
this.weekdayStartTime = ConvertAgentTime(currentOperator.WeekdayPagerStartTime);
this.weekdayEndTime = ConvertAgentTime(currentOperator.WeekdayPagerEndTime);
this.saturdayStartTime = ConvertAgentTime(currentOperator.SaturdayPagerStartTime);
this.saturdayEndTime = ConvertAgentTime(currentOperator.SaturdayPagerEndTime);
this.sundayStartTime = ConvertAgentTime(currentOperator.SundayPagerStartTime);
this.sundayEndTime = ConvertAgentTime(currentOperator.SundayPagerEndTime);
this.generalInitialized = true;
}
/// <summary>
/// Load the data for the jobs that notify this operator. Can be called multiple times and will
/// only load the data initially, or after a reset.
/// </summary>
private void LoadJobNotificationData()
{
if(this.jobNotifications != null)
return;
// just set defaults if we're creating as no jobs will point to this operator yet.
if(createMode)
{
LoadJobNotificationDefaults();
return;
}
JobServer jobServer = GetJobServer();
this.jobNotifications = new List<AgentJobNotificationHelper>();
// we have to loop through each job and see if it notifies us.
foreach(Job job in jobServer.Jobs)
{
bool emailOperator = (job.OperatorToEmail == this.originalOperatorName);
bool pageOperator = (job.OperatorToPage == this.originalOperatorName);
if(emailOperator || pageOperator )
{
// only return jobs that notify this operator
AgentJobNotificationHelper notification = new AgentJobNotificationHelper(job.Name
, job.EmailLevel
, job.PageLevel
);
this.jobNotifications.Add(notification);
}
}
}
/// <summary>
/// Load alerts that notify this operator
/// </summary>
private void LoadAlertNotificationData()
{
if (this.alertNotifications != null)
{
return;
}
// defaults in create ode
if (createMode)
{
LoadAlertNotificationDefaults();
return;
}
this.alertNotifications = new List<AgentAlertNotificationHelper>();
Microsoft.SqlServer.Management.Smo.Agent.Operator agentOperator = GetCurrentOperator();
JobServer jobServer = GetJobServer();
// see all alerts that notifuy this operator
DataTable notifications = agentOperator.EnumNotifications();
DataRow alertRow;
bool notifyEmail;
bool notifyPager;
AgentAlertNotificationHelper alertNotification;
// Add every alert to the structure
foreach (Alert alert in jobServer.Alerts)
{
alertRow = null;
// see if the alert notifies us already
foreach (DataRow row in notifications.Rows)
{
if((string)row["AlertName"] == alert.Name)
{
alertRow = row;
break;
}
}
// set if the current alert notifies this operator
// if so so how
if (alertRow != null)
{
notifyEmail = (bool)alertRow["UseEmail"];
notifyPager = (bool)alertRow["UsePager"];
}
else
{ notifyEmail = false;
notifyPager = false;
}
alertNotification = new AgentAlertNotificationHelper(alert.Name
,notifyEmail
,notifyPager
,alert);
this.alertNotifications.Add(alertNotification);
}
}
/// <summary>
/// load the notifiaction history for the operator
/// </summary>
private void LoadHistoryData()
{
if (this.historyInitialized)
{
return;
}
if (this.createMode)
{
LoadHistoryDefaults();
return;
}
Microsoft.SqlServer.Management.Smo.Agent.Operator currentOperator = GetCurrentOperator();
this.lastEmailDate = currentOperator.LastEmailDate;
this.lastPagerDate = currentOperator.LastPagerDate;
}
#endregion
#region saving
/// <summary>
/// apply any changes to the operator. If the operator does not exist create it.
/// </summary>
public void ApplyChanges(AgentOperatorInfo operatorInfo)
{
// do nothing if we are read only
if (this.readOnly)
{
return;
}
JobServer jobServer = GetJobServer();
// get the operator. This will create a new one if it does not already exist
Microsoft.SqlServer.Management.Smo.Agent.Operator currentOperator = GetCurrentOperator();
// general tab
currentOperator.Enabled = operatorInfo.Enabled;
if (!string.IsNullOrWhiteSpace(operatorInfo.EmailAddress))
{
currentOperator.EmailAddress = operatorInfo.EmailAddress;
}
if (!string.IsNullOrWhiteSpace(operatorInfo.PagerAddress))
{
currentOperator.PagerAddress = operatorInfo.PagerAddress;
}
currentOperator.PagerDays = this.pagerDays;
if ((operatorInfo.PagerDays & Contracts.WeekDays.WeekDays) > 0)
{
currentOperator.WeekdayPagerStartTime = operatorInfo.WeekdayPagerStartTime;
currentOperator.WeekdayPagerEndTime = operatorInfo.WeekdayPagerEndTime;
}
if ((operatorInfo.PagerDays & Contracts.WeekDays.Saturday) > 0)
{
currentOperator.SaturdayPagerStartTime = operatorInfo.SaturdayPagerStartTime;
currentOperator.SaturdayPagerEndTime = operatorInfo.SaturdayPagerEndTime;
}
if ((operatorInfo.PagerDays & Contracts.WeekDays.Sunday) > 0)
{
currentOperator.SundayPagerStartTime = operatorInfo.SundayPagerStartTime;
currentOperator.SundayPagerEndTime = operatorInfo.SundayPagerEndTime;
}
if (this.createMode)
{
// create the object
currentOperator.Create();
this.originalOperatorName = this.name;
}
else
{
// alter the object
currentOperator.Alter();
}
// only set this up if the notifications has been set
if (this.alertNotifications != null)
{
SqlServer.Management.Smo.Agent.NotifyMethods notifyMethods;
for (int i = 0; i < alertNotifications.Count; ++i)
{
notifyMethods = 0;
if (alertNotifications[i].NotifyEmail)
{
notifyMethods |= SqlServer.Management.Smo.Agent.NotifyMethods.NotifyEmail;
}
if (alertNotifications[i].NotifyPager)
{
notifyMethods |= SqlServer.Management.Smo.Agent.NotifyMethods.Pager;
}
bool alertAlreadyNotifiesOperator = false;
// if we're not creating see if the current alert already notifies this operator
if (!createMode)
{
DataTable notifications = alertNotifications[i].Alert.EnumNotifications(this.originalOperatorName);
if (notifications.Rows.Count > 0)
{
alertAlreadyNotifiesOperator = true;
}
}
// either update or clear existing notifications
if (alertAlreadyNotifiesOperator)
{
if(notifyMethods != SqlServer.Management.Smo.Agent.NotifyMethods.None)
{
alertNotifications[i].Alert.UpdateNotification(this.originalOperatorName, notifyMethods);
}
else
{
alertNotifications[i].Alert.RemoveNotification(this.originalOperatorName);
}
}
else if(notifyMethods != SqlServer.Management.Smo.Agent.NotifyMethods.None)
{
// add a new notification
alertNotifications[i].Alert.AddNotification(this.originalOperatorName, notifyMethods);
}
}
}
// see if we need to rename. This has to be done last otherwise any scripts generated will be incorrect.
if (!this.createMode && currentOperator.Name != this.originalOperatorName)
{
currentOperator.Rename(this.name);
if(this.dataContainer.Server.ConnectionContext.SqlExecutionModes != SqlExecutionModes.CaptureSql)
{
this.originalOperatorName = this.name;
}
}
// update state if we aren't scripting
if (this.createMode && this.dataContainer.Server.ConnectionContext.SqlExecutionModes != SqlExecutionModes.CaptureSql)
{
this.createMode = false;
}
}
#endregion
#region reset
/// <summary>
/// Reset the object to it's original state / reload any data from the erver
/// </summary>
public void Reset()
{
JobServer jobServer = GetJobServer();
this.generalInitialized = false;
if (this.jobNotifications != null)
{
// ensure the individual jobs are reset also
jobServer.Jobs.Refresh(true);
this.jobNotifications = null;
}
if (this.alertNotifications != null)
{
// ensure the individual jobs are reset also
jobServer.Alerts.Refresh(true);
this.alertNotifications = null;
}
this.historyInitialized = false;
}
#endregion
#region defaults
/// <summary>
/// set general tab defaults
/// </summary>
private void LoadGeneralDefaults()
{
name = String.Empty;
this.emailAddress = String.Empty;
this.pagerAddress = String.Empty;
enabled = true;
pagerDays = 0;
weekdayStartTime = saturdayStartTime = sundayStartTime = new DateTime(2000, 1, 1, 8, 0, 0);
weekdayEndTime = saturdayEndTime = sundayEndTime = new DateTime(2000, 1, 1, 18, 0, 0);
this.generalInitialized = true;
}
/// <summary>
/// Set job notification defaults. This is just an empty list
/// </summary>
private void LoadJobNotificationDefaults()
{
this.jobNotifications = new List<AgentJobNotificationHelper>();
}
/// <summary>
/// set the alert notification defaults. This list will contain all of the alerts
/// </summary>
private void LoadAlertNotificationDefaults()
{
this.alertNotifications = new List<AgentAlertNotificationHelper>();
JobServer jobServer = GetJobServer();
AgentAlertNotificationHelper alertNotification;
foreach(Alert alert in jobServer.Alerts)
{
alertNotification = new AgentAlertNotificationHelper(alert.Name, notifyEmail:false, notifyPager:false, alert: alert);
this.alertNotifications.Add(alertNotification);
}
}
/// <summary>
/// load defaults for the history page
/// </summary>
private void LoadHistoryDefaults()
{
this.lastEmailDate = DateTime.MinValue;
this.lastPagerDate = DateTime.MinValue;
this.historyInitialized = true;
}
#endregion
#region helpers
/// <summary>
/// Get the job server. Will throw if it is not available
/// </summary>
/// <returns>Job server object</returns>
private JobServer GetJobServer()
{
JobServer jobServer = this.dataContainer.Server.JobServer;
if(jobServer == null)
{
throw new ApplicationException("AgentOperatorsSR.JobServerIsNotAvailable");
}
return jobServer;
}
/// <summary>
/// Get the current operator. If we are creating this will be a new operator. If we are modifying
/// an existing operator it will be the existing operator, and will throw if the operator has been
/// deleted.
/// </summary>
/// <returns>Operator object</returns>
private Operator GetCurrentOperator()
{
JobServer jobServer = GetJobServer();
Operator currentOperator = jobServer.Operators[this.originalOperatorName];
this.createMode = (currentOperator == null);
if (this.createMode)
{
currentOperator = new Microsoft.SqlServer.Management.Smo.Agent.Operator(jobServer, this.name);
}
return currentOperator;
}
/// <summary>
///
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
static public TimeSpan ConvertAgentTime(DateTime dateTime)
{
return new TimeSpan(dateTime.Hour, dateTime.Minute, dateTime.Second);
}
/// <summary>
///
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
static public DateTime ConvertAgentTime(TimeSpan dateTime)
{
return new DateTime(2000, 1, 1, dateTime.Hours, dateTime.Minutes, dateTime.Seconds);
}
/// <summary>
///
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
static public int ConvertAgentTimeToInt(DateTime dateTime)
{
return dateTime.Hour * 10000 + dateTime.Minute * 100 + dateTime.Second;
}
/// <summary>
///
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
static public DateTime ConvertAgentTime(int dateTime)
{
return new DateTime(2000, 1, 1, (int)(dateTime / 10000), (int)((dateTime - (dateTime / 10000) * 10000) / 100), (int)(dateTime - (dateTime / 100) * 100));
}
#endregion
}
}

View File

@@ -0,0 +1,467 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.ComponentModel;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Diagnostics;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Smo.Agent;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
internal class AgentProxyAccount : ManagementActionBase
{
#region Constants
internal const string ProxyAccountPropertyName = "proxyaccount";
internal const string ProxyAccountSubsystem = "SubSystem";
internal const string ProxyAccountMode = "Mode";
internal const string ProxyAccountDuplicateMode = "Duplicate";
internal const int SysnameLength = 256;
internal const int DescriptionLength = 512;
#endregion
internal enum ProxyPrincipalType
{
SqlLogin,
MsdbRole,
ServerRole
}
// Collections of principals for logins/server roles/msdb roles
private ArrayList[] principals;
// Name of the proxy account we work with
private string proxyAccountName;
private AgentProxyInfo proxyInfo;
// Flag indicating that proxy account should be duplicated
private bool duplicate;
public static string SysadminAccount
{
get { return "AgentProxyAccountSR.SysadminAccount"; }
}
/// <summary>
/// Main constructor. Creates all pages and adds them
/// to the tree control.
/// </summary>
public AgentProxyAccount(CDataContainer dataContainer, AgentProxyInfo proxyInfo)
{
this.DataContainer = dataContainer;
this.proxyInfo = proxyInfo;
// Find out if we are creating a new proxy account or
// modifying an existing one.
GetProxyAccountName(dataContainer, ref this.proxyAccountName, ref this.duplicate);
}
/// <summary>
/// It creates a new ProxyAccount or gets an existing
/// one from JobServer and updates all properties.
/// </summary>
private bool CreateOrUpdateProxyAccount(
AgentProxyInfo proxyInfo,
bool isUpdate)
{
ProxyAccount proxyAccount = null;
if (!isUpdate)
{
proxyAccount = new ProxyAccount(this.DataContainer.Server.JobServer,
proxyInfo.AccountName,
proxyInfo.CredentialName,
proxyInfo.IsEnabled,
proxyInfo.Description);
UpdateProxyAccount(proxyAccount);
proxyAccount.Create();
}
else if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
{
// Try refresh and check again
this.DataContainer.Server.JobServer.ProxyAccounts.Refresh();
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
{
// fail since account exists and asked to create a new one
if (!isUpdate)
{
return false;
}
proxyAccount = AgentProxyAccount.GetProxyAccount(this.proxyAccountName, this.DataContainer.Server.JobServer);
// Set the other properties
proxyAccount.CredentialName = proxyInfo.CredentialName;
proxyAccount.Description = proxyInfo.Description;
UpdateProxyAccount(proxyAccount);
proxyAccount.Alter();
// Rename the proxy if needed
// This has to be done after Alter, in order to
// work correcly when scripting this action.
if (this.proxyAccountName != proxyInfo.AccountName)
{
proxyAccount.Rename(proxyInfo.AccountName);
}
}
}
else
{
return false;
}
return true;
#if false // @TODO - reenable subsystem code below
// Update the subsystems
foreach (AgentSubSystem subsystem in this.addSubSystems)
{
proxyAccount.AddSubSystem(subsystem);
}
foreach (AgentSubSystem subsystem in this.removeSubSystems)
{
proxyAccount.RemoveSubSystem(subsystem);
// Update jobsteps that use this proxy accunt
// when some subsystems are removed from it
string reassignToProxyName = this.reassignToProxyNames[(int)subsystem];
if (reassignToProxyName != null)
{
// if version is sql 11 and above call SMO API to reassign proxy account
if (Utils.IsSql11OrLater(this.DataContainer.Server.ServerVersion))
{
proxyAccount.Reassign(reassignToProxyName);
}
else
{
// legacy code
// Get a list of all job step objects that use this proxy and this subsystem
Request req = new Request();
req.Urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/JobServer/Job/Step[@ProxyName=\'{0}\' and @SubSystem={1}]",
Urn.EscapeString(proxyAccount.Name),
(int)subsystem);
req.Fields = new string[] { "Name" };
req.ParentPropertiesRequests = new PropertiesRequest[1] { new PropertiesRequest() };
req.ParentPropertiesRequests[0].Fields = new string[] { "Name" };
Enumerator en = new Enumerator();
DataTable table = en.Process(this.DataContainer.ServerConnection, req);
foreach (DataRow row in table.Rows)
{
// Get the actual job step object using urn
string urnString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "Server/JobServer/Job[@Name=\"{0}\"/Step[@Name=\"{1}\"", row["Job_Name"], row["Name"]);
Urn urn = new Urn(urnString);
JobStep jobStep = (JobStep)this.DataContainer.Server.GetSmoObject(urn);
jobStep.ProxyName = reassignToProxyName;
jobStep.Alter();
}
}
}
}
#endif
}
public bool Create()
{
CreateOrUpdateProxyAccount(this.proxyInfo, false);
return true;
}
public bool Update()
{
CreateOrUpdateProxyAccount(this.proxyInfo, true);
return true;
}
public bool Drop()
{
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
{
// Try refresh and check again
this.DataContainer.Server.JobServer.ProxyAccounts.Refresh();
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
{
ProxyAccount proxyAccount = AgentProxyAccount.GetProxyAccount(this.proxyAccountName, this.DataContainer.Server.JobServer);
proxyAccount.DropIfExists();
}
}
return false;
}
/// <summary>
/// Called to update the proxy object with properties
/// from this page.
/// </summary>
public void UpdateProxyAccount(ProxyAccount proxyAccount)
{
if (proxyAccount == null)
{
throw new ArgumentNullException("proxyAccount");
}
ArrayList principalsToAdd = new ArrayList();
ArrayList principalsToRemove = new ArrayList();
// Process Sql Logins
if (ExtractPermissionsToAddAndRemove(this.proxyAccountName != null? proxyAccount.EnumLogins() : null, this.principals[(int) ProxyPrincipalType.SqlLogin], principalsToAdd, principalsToRemove))
{
foreach (string principal in principalsToRemove)
{
proxyAccount.RemoveLogin(principal);
}
foreach (string principal in principalsToAdd)
{
proxyAccount.AddLogin(principal);
}
}
// Process Server Roles
if (ExtractPermissionsToAddAndRemove(this.proxyAccountName != null? proxyAccount.EnumServerRoles() : null, this.principals[(int) ProxyPrincipalType.ServerRole], principalsToAdd, principalsToRemove))
{
foreach (string principal in principalsToRemove)
{
proxyAccount.RemoveServerRole(principal);
}
foreach (string principal in principalsToAdd)
{
proxyAccount.AddServerRole(principal);
}
}
// Process Msdb Roles
if (ExtractPermissionsToAddAndRemove(this.proxyAccountName != null? proxyAccount.EnumMsdbRoles() : null, this.principals[(int) ProxyPrincipalType.MsdbRole], principalsToAdd, principalsToRemove))
{
foreach (string principal in principalsToRemove)
{
proxyAccount.RemoveMsdbRole(principal);
}
foreach (string principal in principalsToAdd)
{
proxyAccount.AddMsdbRole(principal);
}
}
}
/// <summary>
/// This method scans two list of principals - an existing one extracted from ProxyAccount object
/// and a new one obtained from this panel and then it creates a two differential lists: one of
/// principals to add and the other of principals to remove.
/// </summary>
/// <returns>true if there are any changes between existingPermissions and newPermissions lists</returns>
private bool ExtractPermissionsToAddAndRemove(DataTable existingPermissions, ArrayList newPermissions, ArrayList principalsToAdd, ArrayList principalsToRemove)
{
// Reset both output lists
principalsToAdd.Clear();
principalsToRemove.Clear();
// Sort both input lists
DataRow[] existingRows = existingPermissions != null? existingPermissions.Select(string.Empty, "Name DESC") : new DataRow[] {};
newPermissions.Sort();
// Go through both lists at the same time and find differences
int existingPos = 0;
int newPos = 0;
while (newPos < newPermissions.Count && existingPos < existingRows.Length)
{
int result = string.Compare(existingRows[existingPos]["Name"] as string, newPermissions[newPos] as string,StringComparison.Ordinal);
if (result < 0)
{
// element in existingRows is lower then element in newPermissions
// mark element in existingRows for removal
principalsToRemove.Add(existingRows[existingPos]["Name"]);
++existingPos;
}
else if (result > 0)
{
// element in existingRows is greater then element in newPermissions
// mark element in newPermissions for adding
principalsToAdd.Add(newPermissions[newPos]);
++newPos;
}
else
{
// Both elements are equal.
// Advance to the next element
++existingPos;
++newPos;
}
}
while (newPos < newPermissions.Count)
{
// Some elements are still left
// Copy them all to Add collection
principalsToAdd.Add(newPermissions[newPos++]);
}
while (existingPos < existingRows.Length)
{
// Some elements are still left
// Copy them all to Remove collection
principalsToRemove.Add(existingRows[existingPos++]["Name"]);
}
return(principalsToAdd.Count > 0 || principalsToRemove.Count > 0);
}
private void RefreshData()
{
// List all the jobsteps that use current
// proxy account
Request req = new Request();
req.Urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/JobServer/Job/Step[@ProxyName=\'{0}\']",
Urn.EscapeString(this.proxyAccountName));
req.ResultType = ResultType.IDataReader;
req.Fields = new string[] {"Name", "SubSystem"};
req.ParentPropertiesRequests = new PropertiesRequest[1];
req.ParentPropertiesRequests[0] = new PropertiesRequest(new string[] {"Name"});
Enumerator en = new Enumerator();
using (IDataReader reader = en.Process(this.DataContainer.ServerConnection, req).Data as IDataReader)
{
while (reader.Read())
{
//JobStepSubSystems.
// @TODO - write to output collection
// new GridCell(reader.GetString(0)), // Job Name (parent property is first)
// new GridCell(reader.GetString(1)), // JobStep Name
// new GridCell(JobStepSubSystems.LookupFriendlyName((AgentSubSystem) reader.GetInt32(2))) // JobStep SubSystem
}
}
}
#region Static methods
/// <summary>
/// Retrieves an instance of ProxyAccount from job server using name provided.
/// If proxy does not exist it throws an exception.
/// </summary>
/// <param name="proxyAccountName">Name of the proxy to get</param>
/// <param name="jobServer">Job server to get the proxy from</param>
/// <returns>A valid proxy account.</returns>
internal static ProxyAccount GetProxyAccount(string proxyAccountName, JobServer jobServer)
{
if (proxyAccountName == null || proxyAccountName.Length == 0)
{
throw new ArgumentException("proxyAccountName");
}
if (jobServer == null)
{
throw new ArgumentNullException("jobServer");
}
ProxyAccount proxyAccount = jobServer.ProxyAccounts[proxyAccountName];
if (proxyAccount == null)
{
// proxy not found. Try refreshing the collection
jobServer.ProxyAccounts.Refresh();
proxyAccount = jobServer.ProxyAccounts[proxyAccountName];
// if still cannot get the proxy throw an exception
if (proxyAccount == null)
{
throw new ApplicationException("SRError.ProxyAccountNotFound(proxyAccountName)");
}
}
return proxyAccount;
}
/// <summary>
/// Retrieves a proxy account name from shared data containter.
/// </summary>
internal static void GetProxyAccountName(CDataContainer dataContainer, ref string proxyAccountName, ref bool duplicate)
{
STParameters parameters = new STParameters();
parameters.SetDocument(dataContainer.Document);
// Get proxy name
parameters.GetParam(AgentProxyAccount.ProxyAccountPropertyName, ref proxyAccountName);
if (proxyAccountName != null && proxyAccountName.Length == 0)
{
// Reset empty name back to null
proxyAccountName = null;
}
// Get duplicate flag
string mode = string.Empty;
if (parameters.GetParam(AgentProxyAccount.ProxyAccountMode, ref mode) &&
0 == string.Compare(mode, AgentProxyAccount.ProxyAccountDuplicateMode, StringComparison.Ordinal))
{
duplicate = true;
}
}
/// <summary>
/// Uses enumerator to list names of all proxy accounts that use specified Agent SubSystem.
/// </summary>
/// <param name="serverConnection">Connection to use.</param>
/// <param name="subsystemName">Requested SubSystem name</param>
/// <param name="includeSysadmin">If set to true, 'sysadmin' account is added as a first entry in
/// the list of proxy accounts.</param>
/// <returns>An array containing names of proxy accounts</returns>
internal static string[] ListProxyAccountsForSubsystem(
ServerConnection serverConnection,
string subsystemName,
bool includeSysadmin)
{
ArrayList proxyAccounts = new ArrayList();
// This method is valid only on Yukon and later
if (serverConnection.ServerVersion.Major >= 9)
{
if (includeSysadmin)
{
proxyAccounts.Add(AgentProxyAccount.SysadminAccount);
}
// Get the list of proxy accounts
Request req = new Request();
req.Urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/JobServer/ProxyAccount/AgentSubSystem[@Name = \"{0}\"]",
Urn.EscapeString(subsystemName));
req.ResultType = ResultType.IDataReader;
req.Fields = new string[] { "Name" };
req.ParentPropertiesRequests = new PropertiesRequest[1] { new PropertiesRequest() };
req.ParentPropertiesRequests[0].Fields = new string[] { "Name" };
Enumerator en = new Enumerator();
using (IDataReader reader = en.Process(serverConnection, req).Data as IDataReader)
{
while (reader.Read())
{
proxyAccounts.Add(reader.GetString(0));
}
}
}
return (string[]) proxyAccounts.ToArray(typeof(string));
}
#endregion
}
}

View File

@@ -0,0 +1,406 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Text;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo.Agent;
using SMO = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
internal class JobFetcher
{
private Enumerator enumerator = null;
private ServerConnection connection = null;
private SMO.Server server = null;
public JobFetcher(ServerConnection connection)
{
System.Diagnostics.Debug.Assert(connection != null, "ServerConnection is null");
this.enumerator = new Enumerator();
this.connection = connection;
this.server = new SMO.Server(connection);
}
//
// ServerConnection object should be passed from caller,
// who gets it from CDataContainer.ServerConnection
//
public Dictionary<Guid, JobProperties> FetchJobs(JobActivityFilter filter)
{
string urn = server.JobServer.Urn.Value + "/Job";
if (filter != null)
{
urn += filter.GetXPathClause();
return FilterJobs(FetchJobs(urn), filter);
}
return FetchJobs(urn);
}
/// <summary>
/// Filter Jobs that matches criteria specified in JobActivityFilter
/// here we filter jobs by properties that enumerator doesn't
/// support filtering on.
/// $ISSUE - - DevNote: Filtering Dictionaries can be easily done with Linq and System.Expressions in .NET 3.5
/// This requires re-design of current code and might impact functionality / performance due to newer dependencies
/// We need to consider this change in future enhancements for Job Activity monitor
/// </summary>
/// <param name="unfilteredJobs"></param>
/// <param name="filter"></param>
/// <returns></returns>
private Dictionary<Guid, JobProperties> FilterJobs(Dictionary<Guid, JobProperties> unfilteredJobs,
JobActivityFilter filter)
{
if (unfilteredJobs == null)
{
return null;
}
if (filter == null ||
(filter is IFilterDefinition &&
((filter as IFilterDefinition).Enabled == false ||
(filter as IFilterDefinition).IsDefault())))
{
return unfilteredJobs;
}
Dictionary<Guid, JobProperties> filteredJobs = new Dictionary<Guid, JobProperties>();
// Apply Filter
foreach (JobProperties jobProperties in unfilteredJobs.Values)
{
// If this job passed all filter criteria then include in filteredJobs Dictionary
if (this.CheckIfNameMatchesJob(filter, jobProperties) &&
this.CheckIfCategoryMatchesJob(filter, jobProperties) &&
this.CheckIfEnabledStatusMatchesJob(filter, jobProperties) &&
this.CheckIfScheduledStatusMatchesJob(filter, jobProperties) &&
this.CheckIfJobStatusMatchesJob(filter, jobProperties) &&
this.CheckIfLastRunOutcomeMatchesJob(filter, jobProperties) &&
this.CheckIfLastRunDateIsGreater(filter, jobProperties) &&
this.CheckifNextRunDateIsGreater(filter, jobProperties) &&
this.CheckJobRunnableStatusMatchesJob(filter, jobProperties))
{
filteredJobs.Add(jobProperties.JobID, jobProperties);
}
}
return filteredJobs;
}
/// <summary>
/// check if job runnable status in filter matches given job property
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckJobRunnableStatusMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isRunnableMatched = false;
// filter based on job runnable
switch (filter.Runnable)
{
// if All was selected, include in match
case EnumThreeState.All:
isRunnableMatched = true;
break;
// if Yes was selected, include only if job that is runnable
case EnumThreeState.Yes:
if (jobProperties.Runnable)
{
isRunnableMatched = true;
}
break;
// if Yes was selected, include only if job is not runnable
case EnumThreeState.No:
if (!jobProperties.Runnable)
{
isRunnableMatched = true;
}
break;
}
return isRunnableMatched;
}
/// <summary>
/// Check if next run date for given job property is greater than the one specified in the filter
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckifNextRunDateIsGreater(JobActivityFilter filter, JobProperties jobProperties)
{
bool isNextRunOutDateMatched = false;
// filter next run date
if (filter.NextRunDate.Ticks == 0 ||
jobProperties.NextRun >= filter.NextRunDate)
{
isNextRunOutDateMatched = true;
}
return isNextRunOutDateMatched;
}
/// <summary>
/// Check if last run date for given job property is greater than the one specified in the filter
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfLastRunDateIsGreater(JobActivityFilter filter, JobProperties jobProperties)
{
bool isLastRunOutDateMatched = false;
// filter last run date
if (filter.LastRunDate.Ticks == 0 ||
jobProperties.LastRun >= filter.LastRunDate)
{
isLastRunOutDateMatched = true;
}
return isLastRunOutDateMatched;
}
/// <summary>
/// check if last run status filter matches given job property
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfLastRunOutcomeMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isLastRunOutcomeMatched = false;
// filter - last run outcome
if (filter.LastRunOutcome == EnumCompletionResult.All ||
jobProperties.LastRunOutcome == (int)filter.LastRunOutcome)
{
isLastRunOutcomeMatched = true;
}
return isLastRunOutcomeMatched;
}
/// <summary>
/// Check if job status filter matches given jobproperty
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfJobStatusMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isStatusMatched = false;
// filter - job run status
if (filter.Status == EnumStatus.All ||
jobProperties.CurrentExecutionStatus == (int)filter.Status)
{
isStatusMatched = true;
}
return isStatusMatched;
}
/// <summary>
/// Check if job scheduled status filter matches job
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfScheduledStatusMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isScheduledMatched = false;
// apply filter - if job has schedules or not
switch (filter.Scheduled)
{
// if All was selected, include in match
case EnumThreeState.All:
isScheduledMatched = true;
break;
// if Yes was selected, include only if job has schedule
case EnumThreeState.Yes:
if (jobProperties.HasSchedule)
{
isScheduledMatched = true;
}
break;
// if Yes was selected, include only if job does not have schedule
case EnumThreeState.No:
if (!jobProperties.HasSchedule)
{
isScheduledMatched = true;
}
break;
}
return isScheduledMatched;
}
/// <summary>
/// Check if job enabled status matches job
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfEnabledStatusMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isEnabledMatched = false;
// apply filter - if job was enabled or not
switch (filter.Enabled)
{
// if All was selected, include in match
case EnumThreeState.All:
isEnabledMatched = true;
break;
// if Yes was selected, include only if job has schedule
case EnumThreeState.Yes:
if (jobProperties.Enabled)
{
isEnabledMatched = true;
}
break;
// if Yes was selected, include only if job does not have schedule
case EnumThreeState.No:
if (!jobProperties.Enabled)
{
isEnabledMatched = true;
}
break;
}
return isEnabledMatched;
}
/// <summary>
/// Check if a category matches given jobproperty
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfCategoryMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isCategoryMatched = false;
// Apply category filter if specified
if (filter.Category.Length > 0)
{
//
// we count it as a match if the job category contains
// a case-insensitive match for the filter string.
//
string jobCategory = jobProperties.Category.ToLower(CultureInfo.CurrentCulture);
if (String.Compare(jobCategory, filter.Category.Trim().ToLower(CultureInfo.CurrentCulture), StringComparison.Ordinal) == 0)
{
isCategoryMatched = true;
}
}
else
{
// No category filter was specified
isCategoryMatched = true;
}
return isCategoryMatched;
}
/// <summary>
/// Check if name filter specified matches given jobproperty
/// </summary>
/// <param name="filter"></param>
/// <param name="jobProperties"></param>
/// <returns></returns>
private bool CheckIfNameMatchesJob(JobActivityFilter filter, JobProperties jobProperties)
{
bool isNameMatched = false;
//
// job name (can be comma-separated list)
// we count it as a match if the job name contains
// a case-insensitive match for any of the filter strings.
//
if (filter.Name.Length > 0)
{
string jobname = jobProperties.Name.ToLower(CultureInfo.CurrentCulture);
string[] jobNames = filter.Name.ToLower(CultureInfo.CurrentCulture).Split(',');
int length = jobNames.Length;
for (int j = 0; j < length; ++j)
{
if (jobname.IndexOf(jobNames[j].Trim(), StringComparison.Ordinal) > -1)
{
isNameMatched = true;
break;
}
}
}
else
{
// No name filter was specified
isNameMatched = true;
}
return isNameMatched;
}
/// <summary>
/// Fetch jobs for a given Urn
/// </summary>
/// <param name="urn"></param>
/// <returns></returns>
public Dictionary<Guid, JobProperties> FetchJobs(string urn)
{
if(String.IsNullOrEmpty(urn))
{
throw new ArgumentNullException("urn");
}
Request request = new Request();
request.Urn = urn;
request.Fields = new string[]
{
"Name",
"IsEnabled",
"Category",
"CategoryID",
"CategoryType",
"CurrentRunStatus",
"CurrentRunStep",
"HasSchedule",
"HasStep",
"HasServer",
"LastRunDate",
"NextRunDate",
"LastRunOutcome",
"JobID"
};
DataTable dt = enumerator.Process(connection, request);
int numJobs = dt.Rows.Count;
if (numJobs == 0)
{
return null;
}
Dictionary<Guid, JobProperties> foundJobs = new Dictionary<Guid, JobProperties>(numJobs);
for (int i = 0; i < numJobs; ++i)
{
JobProperties jobProperties = new JobProperties(dt.Rows[i]);
foundJobs.Add(jobProperties.JobID, jobProperties);
}
return foundJobs;
}
}
}

View File

@@ -0,0 +1,100 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo.Agent;
using SMO = Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
internal class JobHelper
{
private JobHelper()
{
}
private ServerConnection connection = null;
private string jobName = string.Empty;
private SMO.Server server = null;
private Job job = null;
//
// ServerConnection object should be passed from caller,
// who gets it from CDataContainer.ServerConnection
//
public JobHelper(ServerConnection connection)
{
this.connection = connection;
server = new SMO.Server(connection);
}
public string JobName
{
get
{
return jobName;
}
set
{
if (server != null)
{
Job j = server.JobServer.Jobs[value];
if (j != null)
{
job = j;
jobName = value;
}
else
{
throw new InvalidOperationException("Job not found");
}
}
}
}
public void Stop()
{
if (job != null)
{
job.Stop();
}
}
public void Start()
{
if (job != null)
{
job.Start();
}
}
public void Delete()
{
if (job != null)
{
job.Drop();
//
// you can't do anything with
// a job after you drop it!
//
job = null;
}
}
public void Enable(bool enable)
{
if (job != null && job.IsEnabled != enable)
{
job.IsEnabled = enable;
job.Alter();
}
}
}
}

View File

@@ -0,0 +1,763 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Text;
using System.Xml;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo.Agent;
using SMO = Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
/// <summary>
/// severity associated with a log entry (ILogEntry)
// these should be ordered least severe to most severe where possible.
/// </summary>
public enum SeverityClass
{
Unknown = -1,
Success,
Information,
SuccessAudit,
InProgress,
Retry,
Warning,
FailureAudit,
Cancelled,
Error
}
public interface ILogEntry
{
string OriginalSourceTypeName {get;}
string OriginalSourceName {get;}
SeverityClass Severity {get;}
DateTime PointInTime {get;}
string this[string fieldName] {get;}
bool CanLoadSubEntries {get;}
List<ILogEntry> SubEntries {get;}
}
internal class LogSourceJobHistory : ILogSource, IDisposable //, ITypedColumns, ILogCommandTarget
{
#region Variables
private string m_jobName = null;
//assigning invalid jobCategoryId
public int m_jobCategoryId = -1;
private Guid m_jobId = Guid.Empty;
private SqlConnectionInfo m_sqlConnectionInfo = null;
private List<ILogEntry> m_logEntries = null;
private string m_logName = null;
private bool m_logInitialized = false;
private string[] m_fieldNames = null;
private ILogEntry m_currentEntry = null;
private int m_index = 0;
private bool m_isClosed = false;
private TypedColumnCollection columnTypes;
private IServiceProvider serviceProvider = null;
private static string historyTableDeclaration = "declare @tmp_sp_help_jobhistory table";
private static string historyTableDeclaration80 = "create table #tmp_sp_help_jobhistory";
private static string historyTableName = "@tmp_sp_help_jobhistory";
private static string historyTableName80 = "#tmp_sp_help_jobhistory";
private static string jobHistoryQuery =
@"{0}
(
instance_id int null,
job_id uniqueidentifier null,
job_name sysname null,
step_id int null,
step_name sysname null,
sql_message_id int null,
sql_severity int null,
message nvarchar(4000) null,
run_status int null,
run_date int null,
run_time int null,
run_duration int null,
operator_emailed sysname null,
operator_netsent sysname null,
operator_paged sysname null,
retries_attempted int null,
server sysname null
)
insert into {1}
exec msdb.dbo.sp_help_jobhistory
@job_id = '{2}',
@mode='FULL'
SELECT
tshj.instance_id AS [InstanceID],
tshj.sql_message_id AS [SqlMessageID],
tshj.message AS [Message],
tshj.step_id AS [StepID],
tshj.step_name AS [StepName],
tshj.sql_severity AS [SqlSeverity],
tshj.job_id AS [JobID],
tshj.job_name AS [JobName],
tshj.run_status AS [RunStatus],
CASE tshj.run_date WHEN 0 THEN NULL ELSE
convert(datetime,
stuff(stuff(cast(tshj.run_date as nchar(8)), 7, 0, '-'), 5, 0, '-') + N' ' +
stuff(stuff(substring(cast(1000000 + tshj.run_time as nchar(7)), 2, 6), 5, 0, ':'), 3, 0, ':'),
120) END AS [RunDate],
tshj.run_duration AS [RunDuration],
tshj.operator_emailed AS [OperatorEmailed],
tshj.operator_netsent AS [OperatorNetsent],
tshj.operator_paged AS [OperatorPaged],
tshj.retries_attempted AS [RetriesAttempted],
tshj.server AS [Server],
getdate() as [CurrentDate]
FROM {1} as tshj
ORDER BY [InstanceID] ASC";
#endregion
#region Public Property - used by Delete handler
public string JobName
{
get
{
return m_jobName;
}
}
public List<ILogEntry> LogEntries
{
get
{
return m_logEntries;
}
}
#endregion
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
// dispose child ILogSources...
ILogSource me = this as ILogSource;
if (me.SubSources != null)
{
for (int i = 0; i < me.SubSources.Length; ++i)
{
IDisposable d = me.SubSources[i] as IDisposable;
if (d != null)
{
d.Dispose();
}
}
}
if (m_currentEntry != null)
{
m_currentEntry = null;
}
}
}
#region Constructor
public LogSourceJobHistory(string jobName, SqlConnectionInfo sqlCi, object customCommandHandler, int jobCategoryId, Guid JobId, IServiceProvider serviceProvider)
{
m_logName = jobName;
m_jobCategoryId = jobCategoryId;
m_jobId = JobId;
m_logEntries = new List<ILogEntry>();
m_jobName = jobName;
m_sqlConnectionInfo = sqlCi;
m_fieldNames = new string[]
{
"LogViewerSR.Field_StepID",
"LogViewerSR.Field_Server",
"LogViewerSR.Field_JobName",
"LogViewerSR.Field_StepName",
"LogViewerSR.Field_Notifications",
"LogViewerSR.Field_Message",
"LogViewerSR.Field_Duration",
"LogViewerSR.Field_SqlSeverity",
"LogViewerSR.Field_SqlMessageID",
"LogViewerSR.Field_OperatorEmailed",
"LogViewerSR.Field_OperatorNetsent",
"LogViewerSR.Field_OperatorPaged",
"LogViewerSR.Field_RetriesAttempted"
};
columnTypes = new TypedColumnCollection();
for (int i = 0; i < m_fieldNames.Length; i++)
{
columnTypes.AddColumnType(m_fieldNames[i], SourceColumnTypes[i]);
}
//m_customCommandHandler = customCommandHandler;
this.serviceProvider = serviceProvider;
}
#endregion
private static int[] SourceColumnTypes
{
get
{
return new int[]
{
GridColumnType.Hyperlink,
GridColumnType.Text,
GridColumnType.Hyperlink,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text,
GridColumnType.Text
};
}
}
public TypedColumnCollection ColumnTypes
{
get
{
return columnTypes;
}
}
#region ILogSource interface implementation
bool ILogSource.OrderedByDateDescending
{
get { return false; }
}
ILogEntry ILogSource.CurrentEntry
{
get
{
return m_currentEntry;
}
}
bool ILogSource.ReadEntry()
{
if (!m_isClosed && m_index >= 0)
{
m_currentEntry = m_logEntries[m_index--];
return true;
}
else
{
return false;
}
}
void ILogSource.CloseReader()
{
m_index = m_logEntries.Count -1;
m_isClosed = true;
m_currentEntry = null;
return;
}
string ILogSource.Name
{
get
{
return m_logName;
}
}
void ILogSource.Initialize()
{
if (m_logInitialized == true)
{
return;
}
// do the actual initialization, retrieveing the ILogEntry-s
InitializeInternal();
m_logInitialized = true;
}
ILogSource[] ILogSource.SubSources
{
get { return null; }
}
string[] ILogSource.FieldNames
{
get
{
return m_fieldNames;
}
}
ILogSource ILogSource.GetRefreshedClone()
{
return new LogSourceJobHistory(m_jobName, m_sqlConnectionInfo, null, m_jobCategoryId, m_jobId, this.serviceProvider);
}
#endregion
#region Implementation
/// <summary>
/// does the actual initialization by retrieving Server/ErrorLog/Text via enumerator
/// </summary>
private void InitializeInternal()
{
m_logEntries.Clear();
IDbConnection connection = null;
try
{
connection = m_sqlConnectionInfo.CreateConnectionObject();
connection.Open();
IDbCommand command = connection.CreateCommand();
string jobId = this.m_jobId.ToString();
string query =
(this.m_sqlConnectionInfo.ServerVersion == null
|| this.m_sqlConnectionInfo.ServerVersion.Major >= 9) ?
String.Format(jobHistoryQuery,
historyTableDeclaration,
historyTableName,
jobId) :
String.Format(jobHistoryQuery,
historyTableDeclaration80,
historyTableName80,
jobId);
command.CommandType = CommandType.Text;
command.CommandText = query;
DataTable dtJobHistory = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter((SqlCommand)command);
adapter.Fill(dtJobHistory);
int n = dtJobHistory.Rows.Count;
// populate m_logEntries with ILogEntry-s that have (ref to us, rowno)
for (int rowno = 0; rowno < n; rowno++)
{
// we will create here only the job outcomes (0) - entries, and skip non 0
// job outcome (step 0) it will extract the sub-entries (steps 1...n) itself
ILogEntry jobOutcome = new LogEntryJobHistory(m_jobName, dtJobHistory, rowno);
int skippedSubentries = (jobOutcome.SubEntries == null) ? 0 : jobOutcome.SubEntries.Count;
rowno += skippedSubentries; // skip subentries
m_logEntries.Add(jobOutcome);
}
m_index = m_logEntries.Count - 1;
}
finally
{
if (connection != null)
{
connection.Close();
}
}
}
#endregion
#region internal class - LogEntryJobHistory
/// <summary>
/// LogEntryJobHistory - represents a SqlServer log entry
/// </summary>
internal class LogEntryJobHistory : ILogEntry
{
internal const string cUrnInstanceID = "InstanceID"; // internally used by Agent to mark order in which steps were executed
#region Variables
string m_originalSourceName = null;
DateTime m_pointInTime = DateTime.MinValue;
SeverityClass m_severity = SeverityClass.Unknown;
string m_fieldJobName = null;
string m_fieldStepID = null;
string m_fieldStepName = null;
string m_fieldMessage = null;
string m_fieldDuration = null;
string m_fieldSqlSeverity = null;
string m_fieldSqlMessageID = null;
string m_fieldOperatorEmailed = null;
string m_fieldOperatorNetsent = null;
string m_fieldOperatorPaged = null;
string m_fieldRetriesAttempted = null;
private string m_serverName = null;
List<ILogEntry> m_subEntries = null;
#endregion
#region Constructor
/// <summary>
/// constructor used by log source to create 'job outcome' entries
/// </summary>
/// <param name="sourceName"></param>
/// <param name="dt">data table containing all history info for a job</param>
/// <param name="rowno">index for row that describes 'job outcome', rowno+1..n will describe 'job steps'</param>
public LogEntryJobHistory(string sourceName, DataTable dt, int rowno)
{
InitializeJobHistoryStepSubEntries(sourceName, dt, rowno); // create subentries, until we hit job outcome or end of history
// initialize job outcome
if ((m_subEntries != null) && (m_subEntries.Count > 0))
{
// are we at the end of history data set?
if ((rowno + m_subEntries.Count) < dt.Rows.Count)
{
// row following list of subentries coresponds to an outcome job that already finished
InitializeJobHistoryFromDataRow(sourceName, dt.Rows[rowno + m_subEntries.Count]);
}
else
{
// there is no row with stepID=0 that coresponds to a job job outcome for a job that is running
// since agent will write the outcome only after it finishes the job therefore we will build ourselves
// an entry describing the running job, to which we will host the subentries for job steps already executed
InitializeJobHistoryForRunningJob(sourceName, dt.Rows[rowno]);
}
}
else
{
InitializeJobHistoryFromDataRow(sourceName, dt.Rows[rowno]);
}
}
/// <summary>
/// constructor used by a parent log entry to create child 'job step' sub-entries
/// </summary>
/// <param name="sourceName"></param>
/// <param name="dr">row describing subentry</param>
public LogEntryJobHistory(string sourceName, DataRow dr)
{
InitializeJobHistoryFromDataRow(sourceName, dr); // initialize intself
}
#endregion
#region Implementation
/// <summary>
/// builds an entry based on a row returned by enurerator - that can be either a job step or a job outcome
/// </summary>
/// <param name="dr"></param>
private void InitializeJobHistoryFromDataRow(string sourceName, DataRow dr)
{
try
{
m_originalSourceName = sourceName;
m_pointInTime = Convert.ToDateTime(dr[JobUtilities.UrnRunDate], System.Globalization.CultureInfo.InvariantCulture);
m_serverName = Convert.ToString(dr[JobUtilities.UrnServer], System.Globalization.CultureInfo.InvariantCulture);
m_fieldJobName = Convert.ToString(dr[JobUtilities.UrnJobName], System.Globalization.CultureInfo.InvariantCulture);
switch ((Microsoft.SqlServer.Management.Smo.Agent.CompletionResult)Convert.ToInt32(dr[JobUtilities.UrnRunStatus], System.Globalization.CultureInfo.InvariantCulture))
{
case CompletionResult.Cancelled:
m_severity = SeverityClass.Cancelled;
break;
case CompletionResult.Failed:
m_severity = SeverityClass.Error;
break;
case CompletionResult.InProgress:
m_severity = SeverityClass.InProgress;
break;
case CompletionResult.Retry:
m_severity = SeverityClass.Retry;
break;
case CompletionResult.Succeeded:
m_severity = SeverityClass.Success;
break;
case CompletionResult.Unknown:
m_severity = SeverityClass.Unknown;
break;
default:
m_severity = SeverityClass.Unknown;
break;
}
//
// check our subentries, see if any of them have a worse status than we do.
// if so, then we should set ourselves to SeverityClass.Warning
//
if (this.m_subEntries != null)
{
for (int i = 0; i < this.m_subEntries.Count; i++)
{
if (this.m_subEntries[i].Severity > this.m_severity &&
(this.m_subEntries[i].Severity == SeverityClass.Retry ||
this.m_subEntries[i].Severity == SeverityClass.Warning ||
this.m_subEntries[i].Severity == SeverityClass.FailureAudit ||
this.m_subEntries[i].Severity == SeverityClass.Error))
{
this.m_severity = SeverityClass.Warning;
break;
}
}
}
// if stepId is zero then dont show stepID and step name in log viewer
// Valid step Ids starts from index 1
int currentStepId = (int)dr[JobUtilities.UrnStepID];
if (currentStepId == 0)
{
m_fieldStepID = String.Empty;
m_fieldStepName = String.Empty;
}
else
{
m_fieldStepID = Convert.ToString(currentStepId, System.Globalization.CultureInfo.CurrentCulture);
m_fieldStepName = Convert.ToString(dr[JobUtilities.UrnStepName], System.Globalization.CultureInfo.CurrentCulture);
}
m_fieldMessage = Convert.ToString(dr[JobUtilities.UrnMessage], System.Globalization.CultureInfo.CurrentCulture);
m_fieldSqlSeverity = Convert.ToString(dr[JobUtilities.UrnSqlSeverity], System.Globalization.CultureInfo.CurrentCulture);
m_fieldSqlMessageID = Convert.ToString(dr[JobUtilities.UrnSqlMessageID], System.Globalization.CultureInfo.CurrentCulture);
m_fieldOperatorEmailed = Convert.ToString(dr[JobUtilities.UrnOperatorEmailed], System.Globalization.CultureInfo.CurrentCulture);
m_fieldOperatorNetsent = Convert.ToString(dr[JobUtilities.UrnOperatorNetsent], System.Globalization.CultureInfo.CurrentCulture);
m_fieldOperatorPaged = Convert.ToString(dr[JobUtilities.UrnOperatorPaged], System.Globalization.CultureInfo.CurrentCulture);
m_fieldRetriesAttempted = Convert.ToString(dr[JobUtilities.UrnRetriesAttempted], System.Globalization.CultureInfo.CurrentCulture);
Int64 hhmmss = Convert.ToInt64(dr[JobUtilities.UrnRunDuration], System.Globalization.CultureInfo.InvariantCulture); // HHMMSS
int hh = Convert.ToInt32(hhmmss / 10000, System.Globalization.CultureInfo.InvariantCulture);
int mm = Convert.ToInt32((hhmmss / 100) % 100, System.Globalization.CultureInfo.InvariantCulture);
int ss = Convert.ToInt32(hhmmss % 100, System.Globalization.CultureInfo.InvariantCulture);
m_fieldDuration = Convert.ToString(new TimeSpan(hh, mm, ss), System.Globalization.CultureInfo.CurrentCulture);
}
catch (InvalidCastException)
{
// keep null data if we hit some invalid info
}
}
/// <summary>
/// builds sub-entries (steps 1...n), until we find a 'job outcome' (step0) or end of history (meaning job is in progress)
/// </summary>
/// <param name="dt"></param>
/// <param name="rowno">points to 1st subentry => points to 1st 'job step'</param>
private void InitializeJobHistoryStepSubEntries(string sourceName, DataTable dt, int rowno)
{
if (m_subEntries == null)
{
m_subEntries = new List<ILogEntry>();
}
else
{
m_subEntries.Clear();
}
int i = rowno;
while (i < dt.Rows.Count)
{
DataRow dr = dt.Rows[i];
object o = dr[JobUtilities.UrnStepID];
try
{
int stepID = Convert.ToInt32(o, System.Globalization.CultureInfo.InvariantCulture);
if (stepID == 0)
{
// we found the 'job outcome' for our set of steps
break;
}
//
// we want to have the subentries ordered newest to oldest,
// which is the same time order as the parent nodes themselves.
// that's why we add each one to the head of the list
//
m_subEntries.Insert(0, new LogEntryJobHistory(sourceName, dr));
}
catch (InvalidCastException)
{
}
++i;
}
}
/// <summary>
/// builds an entry for a running job - in this case there is no row available since agent logs outcomes only after job finishes
/// </summary>
/// <param name="sourceName"></param>
/// <param name="dr">points to last entry - which should corespond to first step - so we can compute job name and duration</param>
private void InitializeJobHistoryForRunningJob(string sourceName, DataRow dr)
{
try
{
m_originalSourceName = sourceName;
m_pointInTime = Convert.ToDateTime(dr[JobUtilities.UrnRunDate], System.Globalization.CultureInfo.InvariantCulture);
m_fieldJobName = Convert.ToString(dr[JobUtilities.UrnJobName], System.Globalization.CultureInfo.InvariantCulture);
m_severity = SeverityClass.InProgress;
m_fieldStepID = null;
m_fieldStepName = null;
m_fieldMessage = "DropObjectsSR.InProgressStatus"; // $FUTURE - assign its own string when string resources got un-freezed
m_fieldSqlSeverity = null;
m_fieldSqlMessageID = null;
m_fieldOperatorEmailed = null;
m_fieldOperatorNetsent = null;
m_fieldOperatorPaged = null;
m_fieldRetriesAttempted = null;
m_serverName = null;
m_fieldDuration = Convert.ToString(Convert.ToDateTime(dr[JobUtilities.UrnServerTime]) - Convert.ToDateTime(dr[JobUtilities.UrnRunDate], System.Globalization.CultureInfo.InvariantCulture), System.Globalization.CultureInfo.InvariantCulture);
}
catch (InvalidCastException)
{
// keep null data if we hit some invalid info
}
}
#endregion
#region ILogEntry interface implementation
string ILogEntry.OriginalSourceTypeName
{
get
{
return "LogViewerSR.LogSourceTypeJobHistory";
}
}
string ILogEntry.OriginalSourceName
{
get
{
return m_originalSourceName;
}
}
SeverityClass ILogEntry.Severity
{
get
{
return m_severity;
}
}
DateTime ILogEntry.PointInTime
{
get
{
return m_pointInTime;
}
}
string ILogEntry.this[string fieldName]
{
get
{
{
return null;
}
}
}
bool ILogEntry.CanLoadSubEntries
{
get { return ((m_subEntries != null) && (m_subEntries.Count > 0)); }
}
List<ILogEntry> ILogEntry.SubEntries
{
get { return m_subEntries; }
}
/* Public Properties */
internal string Duration
{
get { return m_fieldDuration; }
}
internal string JobName
{
get { return m_fieldJobName; }
}
internal string Message
{
get { return m_fieldMessage; }
}
internal string StepID
{
get { return m_fieldStepID; }
}
internal string OperatorEmailed
{
get { return m_fieldOperatorEmailed; }
}
internal string OperatorNetsent
{
get { return m_fieldOperatorNetsent; }
}
internal string OperatorPaged
{
get { return m_fieldOperatorPaged; }
}
internal string StepName
{
get { return m_fieldStepName; }
}
internal string RetriesAttempted
{
get { return m_fieldRetriesAttempted; }
}
internal string SqlMessageID
{
get { return m_fieldSqlMessageID; }
}
internal string SqlSeverity
{
get { return m_fieldSqlSeverity; }
}
internal string Server
{
get { return m_serverName; }
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,167 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Text;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo.Agent;
using SMO = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
/// <summary>
/// a class for storing various properties of agent jobs,
/// used by the Job Activity Monitor
/// </summary>
public class JobProperties
{
private string name;
private int currentExecutionStatus;
private int lastRunOutcome;
private string currentExecutionStep;
private bool enabled;
private bool hasTarget;
private bool hasSchedule;
private bool hasStep;
private bool runnable;
private string category;
private int categoryID;
private int categoryType;
private DateTime lastRun;
private DateTime nextRun;
private Guid jobId;
private JobProperties()
{
}
public JobProperties(DataRow row)
{
System.Diagnostics.Debug.Assert(row["Name"] != DBNull.Value, "Name is null!");
System.Diagnostics.Debug.Assert(row["IsEnabled"] != DBNull.Value, "IsEnabled is null!");
System.Diagnostics.Debug.Assert(row["Category"] != DBNull.Value, "Category is null!");
System.Diagnostics.Debug.Assert(row["CategoryID"] != DBNull.Value, "CategoryID is null!");
System.Diagnostics.Debug.Assert(row["CategoryType"] != DBNull.Value, "CategoryType is null!");
System.Diagnostics.Debug.Assert(row["CurrentRunStatus"] != DBNull.Value, "CurrentRunStatus is null!");
System.Diagnostics.Debug.Assert(row["CurrentRunStep"] != DBNull.Value, "CurrentRunStep is null!");
System.Diagnostics.Debug.Assert(row["HasSchedule"] != DBNull.Value, "HasSchedule is null!");
System.Diagnostics.Debug.Assert(row["HasStep"] != DBNull.Value, "HasStep is null!");
System.Diagnostics.Debug.Assert(row["HasServer"] != DBNull.Value, "HasServer is null!");
System.Diagnostics.Debug.Assert(row["LastRunOutcome"] != DBNull.Value, "LastRunOutcome is null!");
System.Diagnostics.Debug.Assert(row["JobID"] != DBNull.Value, "JobID is null!");
this.name = row["Name"].ToString();
this.enabled = Convert.ToBoolean(row["IsEnabled"], CultureInfo.InvariantCulture);
this.category = row["Category"].ToString();
this.categoryID = Convert.ToInt32(row["CategoryID"], CultureInfo.InvariantCulture);
this.categoryType = Convert.ToInt32(row["CategoryType"], CultureInfo.InvariantCulture);
this.currentExecutionStatus = Convert.ToInt32(row["CurrentRunStatus"], CultureInfo.InvariantCulture);
this.currentExecutionStep = row["CurrentRunStep"].ToString();
this.hasSchedule = Convert.ToBoolean(row["HasSchedule"], CultureInfo.InvariantCulture);
this.hasStep = Convert.ToBoolean(row["HasStep"], CultureInfo.InvariantCulture);
this.hasTarget = Convert.ToBoolean(row["HasServer"], CultureInfo.InvariantCulture);
this.lastRunOutcome = Convert.ToInt32(row["LastRunOutcome"], CultureInfo.InvariantCulture);
this.jobId = Guid.Parse(row["JobID"].ToString()); ;
// for a job to be runnable, it must:
// 1. have a target server
// 2. have some steps
this.runnable = this.hasTarget && this.hasStep;
if (row["LastRunDate"] != DBNull.Value)
{
this.lastRun = Convert.ToDateTime(row["LastRunDate"], CultureInfo.InvariantCulture);
}
if (row["NextRunDate"] != DBNull.Value)
{
this.nextRun = Convert.ToDateTime(row["NextRunDate"], CultureInfo.InvariantCulture);
}
}
public bool Runnable
{
get{ return runnable;}
}
public string Name
{
get{ return name;}
}
public string Category
{
get{ return category;}
}
public int CategoryID
{
get{ return categoryID;}
}
public int CategoryType
{
get{ return categoryType;}
}
public int LastRunOutcome
{
get{ return lastRunOutcome;}
}
public int CurrentExecutionStatus
{
get{ return currentExecutionStatus;}
}
public string CurrentExecutionStep
{
get{ return currentExecutionStep;}
}
public bool Enabled
{
get{ return enabled;}
}
public bool HasTarget
{
get{ return hasTarget;}
}
public bool HasStep
{
get{ return hasStep;}
}
public bool HasSchedule
{
get{ return hasSchedule;}
}
public DateTime NextRun
{
get{ return nextRun;}
}
public DateTime LastRun
{
get{ return lastRun;}
}
public Guid JobID
{
get
{
return this.jobId;
}
}
}
}

View File

@@ -0,0 +1,118 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo.Agent;
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
using SMO = Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.Agent
{
public class JobUtilities
{
public const string UrnJobName = "JobName";
public const string UrnJobId = "JobId";
public const string UrnRunStatus = "RunStatus";
public const string UrnInstanceID = "InstanceId";
public const string UrnSqlMessageID = "SqlMessageId";
public const string UrnMessage = "Message";
public const string UrnStepID = "StepId";
public const string UrnStepName = "StepName";
public const string UrnSqlSeverity = "SqlSeverity";
public const string UrnRunDate = "RunDate";
public const string UrnRunDuration = "RunDuration";
public const string UrnOperatorEmailed = "OperatorEmailed";
public const string UrnOperatorNetsent = "OperatorNetsent";
public const string UrnOperatorPaged = "OperatorPaged";
public const string UrnRetriesAttempted = "RetriesAttempted";
public const string UrnServer = "Server";
internal const string UrnServerTime = "CurrentDate";
public static AgentJobInfo ConvertToAgentJobInfo(JobProperties job)
{
return new AgentJobInfo
{
Name = job.Name,
CurrentExecutionStatus = job.CurrentExecutionStatus,
LastRunOutcome = job.LastRunOutcome,
CurrentExecutionStep = job.CurrentExecutionStep,
Enabled = job.Enabled,
HasTarget = job.HasTarget,
HasSchedule = job.HasSchedule,
HasStep = job.HasStep,
Runnable = job.Runnable,
Category = job.Category,
CategoryId = job.CategoryID,
CategoryType = job.CategoryType,
LastRun = job.LastRun != null ? job.LastRun.ToString() : string.Empty,
NextRun = job.NextRun != null ? job.NextRun.ToString() : string.Empty,
JobId = job.JobID != null ? job.JobID.ToString() : null
};
}
public static AgentJobStep ConvertToAgentJobStep(ILogEntry logEntry, DataRow jobRow)
{
var entry = logEntry as LogSourceJobHistory.LogEntryJobHistory;
string stepId = entry.StepID;
string stepName = entry.StepName;
string message = entry.Message;
DateTime runDate = logEntry.PointInTime;
int runStatus = Convert.ToInt32(jobRow[UrnRunStatus], System.Globalization.CultureInfo.InvariantCulture);
AgentJobStep step = new AgentJobStep();
step.StepId = stepId;
step.StepName = stepName;
step.Message = message;
step.RunDate = runDate;
step.RunStatus = runStatus;
return step;
}
public static List<AgentJobHistoryInfo> ConvertToAgentJobHistoryInfo(List<ILogEntry> logEntries, DataRow jobRow)
{
List<AgentJobHistoryInfo> jobs = new List<AgentJobHistoryInfo>();
// get all the values for a job history
foreach (ILogEntry entry in logEntries)
{
// Make a new AgentJobHistoryInfo object
var jobHistoryInfo = new AgentJobHistoryInfo();
jobHistoryInfo.InstanceId = Convert.ToInt32(jobRow[UrnInstanceID], System.Globalization.CultureInfo.InvariantCulture);
jobHistoryInfo.RunStatus = Convert.ToInt32(jobRow[UrnRunStatus], System.Globalization.CultureInfo.InvariantCulture);
jobHistoryInfo.JobId = (Guid) jobRow[UrnJobId];
var logEntry = entry as LogSourceJobHistory.LogEntryJobHistory;
jobHistoryInfo.SqlMessageId = logEntry.SqlMessageID;
jobHistoryInfo.Message = logEntry.Message;
jobHistoryInfo.StepId = logEntry.StepID;
jobHistoryInfo.StepName = logEntry.StepName;
jobHistoryInfo.SqlSeverity = logEntry.SqlSeverity;
jobHistoryInfo.JobName = logEntry.JobName;
jobHistoryInfo.RunDate = entry.PointInTime;
jobHistoryInfo.RunDuration = logEntry.Duration;
jobHistoryInfo.OperatorEmailed = logEntry.OperatorEmailed;
jobHistoryInfo.OperatorNetsent = logEntry.OperatorNetsent;
jobHistoryInfo.OperatorPaged = logEntry.OperatorPaged;
jobHistoryInfo.RetriesAttempted = logEntry.RetriesAttempted;
jobHistoryInfo.Server = logEntry.Server;
// Add steps to the job if any
var jobSteps = new List<AgentJobStep>();
if (entry.CanLoadSubEntries)
{
foreach (ILogEntry step in entry.SubEntries)
{
jobSteps.Add(JobUtilities.ConvertToAgentJobStep(step, jobRow));
}
}
jobHistoryInfo.Steps = jobSteps.ToArray();
jobs.Add(jobHistoryInfo);
}
return jobs;
}
}
}