mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-07 17:25:04 -05:00
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:
166
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentAlert.cs
Normal file
166
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentAlert.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
947
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentOperator.cs
Normal file
947
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentOperator.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
406
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs
Normal file
406
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobHelper.cs
Normal file
100
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
763
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobHistoryItem.cs
Normal file
763
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobHistoryItem.cs
Normal 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
|
||||
}
|
||||
}
|
||||
167
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs
Normal file
167
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobUtilities.cs
Normal file
118
src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobUtilities.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user