1089 lines
35 KiB
C#
1089 lines
35 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Runtime.Serialization;
|
|
using System.Text;
|
|
|
|
namespace Microsoft.PowerShell.ScheduledJob
|
|
{
|
|
/// <summary>
|
|
/// This class provides functionality for retrieving scheduled job run results
|
|
/// from the scheduled job store. An instance of this object will be registered
|
|
/// with the PowerShell JobManager so that GetJobs commands will retrieve schedule
|
|
/// job runs from the file based scheduled job store. This allows scheduled job
|
|
/// runs to be managed from PowerShell in the same way workflow jobs are managed.
|
|
/// </summary>
|
|
public sealed class ScheduledJobSourceAdapter : JobSourceAdapter
|
|
{
|
|
#region Private Members
|
|
|
|
private static FileSystemWatcher StoreWatcher;
|
|
private static object SyncObject = new object();
|
|
private static ScheduledJobRepository JobRepository = new ScheduledJobRepository();
|
|
internal const string AdapterTypeName = "PSScheduledJob";
|
|
|
|
#endregion
|
|
|
|
#region Public Strings
|
|
|
|
/// <summary>
|
|
/// BeforeFilter.
|
|
/// </summary>
|
|
public const string BeforeFilter = "Before";
|
|
|
|
/// <summary>
|
|
/// AfterFilter.
|
|
/// </summary>
|
|
public const string AfterFilter = "After";
|
|
|
|
/// <summary>
|
|
/// NewestFilter.
|
|
/// </summary>
|
|
public const string NewestFilter = "Newest";
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
public ScheduledJobSourceAdapter()
|
|
{
|
|
Name = AdapterTypeName;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region JobSourceAdapter Implementation
|
|
|
|
/// <summary>
|
|
/// Create a new Job2 results instance.
|
|
/// </summary>
|
|
/// <param name="specification">Job specification.</param>
|
|
/// <returns>Job2.</returns>
|
|
public override Job2 NewJob(JobInvocationInfo specification)
|
|
{
|
|
if (specification == null)
|
|
{
|
|
throw new PSArgumentNullException("specification");
|
|
}
|
|
|
|
ScheduledJobDefinition scheduledJobDef = new ScheduledJobDefinition(
|
|
specification, null, null, null);
|
|
|
|
return new ScheduledJob(
|
|
specification.Command,
|
|
specification.Name,
|
|
scheduledJobDef);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new Job2 object based on a definition name
|
|
/// that can be run manually. If the path parameter is
|
|
/// null then a default location will be used to find the
|
|
/// job definition by name.
|
|
/// </summary>
|
|
/// <param name="definitionName">ScheduledJob definition name.</param>
|
|
/// <param name="definitionPath">ScheduledJob definition file path.</param>
|
|
/// <returns>Job2 object.</returns>
|
|
public override Job2 NewJob(string definitionName, string definitionPath)
|
|
{
|
|
if (string.IsNullOrEmpty(definitionName))
|
|
{
|
|
throw new PSArgumentException("definitionName");
|
|
}
|
|
|
|
Job2 rtnJob = null;
|
|
try
|
|
{
|
|
ScheduledJobDefinition scheduledJobDef =
|
|
ScheduledJobDefinition.LoadFromStore(definitionName, definitionPath);
|
|
|
|
rtnJob = new ScheduledJob(
|
|
scheduledJobDef.Command,
|
|
scheduledJobDef.Name,
|
|
scheduledJobDef);
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
// Return null if no job definition exists.
|
|
}
|
|
|
|
return rtnJob;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the list of jobs that are currently available in this
|
|
/// store.
|
|
/// </summary>
|
|
/// <returns>Collection of job objects.</returns>
|
|
public override IList<Job2> GetJobs()
|
|
{
|
|
RefreshRepository();
|
|
|
|
List<Job2> rtnJobs = new List<Job2>();
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
rtnJobs.Add(job);
|
|
}
|
|
|
|
return rtnJobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of jobs that matches the specified names.
|
|
/// </summary>
|
|
/// <param name="name">names to match, can support
|
|
/// wildcard if the store supports</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Collection of jobs that match the specified
|
|
/// criteria.</returns>
|
|
public override IList<Job2> GetJobsByName(string name, bool recurse)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
throw new PSArgumentException("name");
|
|
}
|
|
|
|
RefreshRepository();
|
|
|
|
WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase);
|
|
List<Job2> rtnJobs = new List<Job2>();
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (namePattern.IsMatch(job.Name))
|
|
{
|
|
rtnJobs.Add(job);
|
|
}
|
|
}
|
|
|
|
return rtnJobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of jobs that run the specified command.
|
|
/// </summary>
|
|
/// <param name="command">Command to match.</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Collection of jobs that match the specified
|
|
/// criteria.</returns>
|
|
public override IList<Job2> GetJobsByCommand(string command, bool recurse)
|
|
{
|
|
if (string.IsNullOrEmpty(command))
|
|
{
|
|
throw new PSArgumentException("command");
|
|
}
|
|
|
|
RefreshRepository();
|
|
|
|
WildcardPattern commandPattern = new WildcardPattern(command, WildcardOptions.IgnoreCase);
|
|
List<Job2> rtnJobs = new List<Job2>();
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (commandPattern.IsMatch(job.Command))
|
|
{
|
|
rtnJobs.Add(job);
|
|
}
|
|
}
|
|
|
|
return rtnJobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get job that has the specified id.
|
|
/// </summary>
|
|
/// <param name="instanceId">Guid to match.</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Job with the specified guid.</returns>
|
|
public override Job2 GetJobByInstanceId(Guid instanceId, bool recurse)
|
|
{
|
|
RefreshRepository();
|
|
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (Guid.Equals(job.InstanceId, instanceId))
|
|
{
|
|
return job;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get job that has specific session id.
|
|
/// </summary>
|
|
/// <param name="id">Id to match.</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Job with the specified id.</returns>
|
|
public override Job2 GetJobBySessionId(int id, bool recurse)
|
|
{
|
|
RefreshRepository();
|
|
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (id == job.Id)
|
|
{
|
|
return job;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of jobs that are in the specified state.
|
|
/// </summary>
|
|
/// <param name="state">State to match.</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Collection of jobs with the specified
|
|
/// state.</returns>
|
|
public override IList<Job2> GetJobsByState(JobState state, bool recurse)
|
|
{
|
|
RefreshRepository();
|
|
|
|
List<Job2> rtnJobs = new List<Job2>();
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (state == job.JobStateInfo.State)
|
|
{
|
|
rtnJobs.Add(job);
|
|
}
|
|
}
|
|
|
|
return rtnJobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get list of jobs based on the adapter specific
|
|
/// filter parameters.
|
|
/// </summary>
|
|
/// <param name="filter">dictionary containing name value
|
|
/// pairs for adapter specific filters</param>
|
|
/// <param name="recurse"></param>
|
|
/// <returns>Collection of jobs that match the
|
|
/// specified criteria.</returns>
|
|
public override IList<Job2> GetJobsByFilter(Dictionary<string, object> filter, bool recurse)
|
|
{
|
|
if (filter == null)
|
|
{
|
|
throw new PSArgumentNullException("filter");
|
|
}
|
|
|
|
List<Job2> rtnJobs = new List<Job2>();
|
|
foreach (var filterItem in filter)
|
|
{
|
|
switch (filterItem.Key)
|
|
{
|
|
case BeforeFilter:
|
|
GetJobsBefore((DateTime)filterItem.Value, ref rtnJobs);
|
|
break;
|
|
|
|
case AfterFilter:
|
|
GetJobsAfter((DateTime)filterItem.Value, ref rtnJobs);
|
|
break;
|
|
|
|
case NewestFilter:
|
|
GetNewestJobs((int)filterItem.Value, ref rtnJobs);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rtnJobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a job from the store.
|
|
/// </summary>
|
|
/// <param name="job">Job object to remove.</param>
|
|
public override void RemoveJob(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
RefreshRepository();
|
|
|
|
try
|
|
{
|
|
JobRepository.Remove(job);
|
|
ScheduledJobStore.RemoveJobRun(
|
|
job.Name,
|
|
job.PSBeginTime ?? DateTime.MinValue);
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves job to scheduled job run store.
|
|
/// </summary>
|
|
/// <param name="job">ScheduledJob.</param>
|
|
public override void PersistJob(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
SaveJobToStore(job as ScheduledJob);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Save Job
|
|
|
|
/// <summary>
|
|
/// Serializes a ScheduledJob and saves it to store.
|
|
/// </summary>
|
|
/// <param name="job">ScheduledJob.</param>
|
|
internal static void SaveJobToStore(ScheduledJob job)
|
|
{
|
|
string outputPath = job.Definition.OutputPath;
|
|
if (string.IsNullOrEmpty(outputPath))
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.CantSaveJobNoFilePathSpecified,
|
|
job.Name);
|
|
throw new ScheduledJobException(msg);
|
|
}
|
|
|
|
FileStream fsStatus = null;
|
|
FileStream fsResults = null;
|
|
try
|
|
{
|
|
// Check the job store results and if maximum number of results exist
|
|
// remove the oldest results folder to make room for these new results.
|
|
CheckJobStoreResults(outputPath, job.Definition.ExecutionHistoryLength);
|
|
|
|
fsStatus = ScheduledJobStore.CreateFileForJobRunItem(
|
|
outputPath,
|
|
job.PSBeginTime ?? DateTime.MinValue,
|
|
ScheduledJobStore.JobRunItem.Status);
|
|
|
|
// Save status only in status file stream.
|
|
SaveStatusToFile(job, fsStatus);
|
|
|
|
fsResults = ScheduledJobStore.CreateFileForJobRunItem(
|
|
outputPath,
|
|
job.PSBeginTime ?? DateTime.MinValue,
|
|
ScheduledJobStore.JobRunItem.Results);
|
|
|
|
// Save entire job in results file stream.
|
|
SaveResultsToFile(job, fsResults);
|
|
}
|
|
finally
|
|
{
|
|
if (fsStatus != null)
|
|
{
|
|
fsStatus.Close();
|
|
}
|
|
|
|
if (fsResults != null)
|
|
{
|
|
fsResults.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the job status information to the provided
|
|
/// file stream.
|
|
/// </summary>
|
|
/// <param name="job">ScheduledJob job to save.</param>
|
|
/// <param name="fs">FileStream.</param>
|
|
private static void SaveStatusToFile(ScheduledJob job, FileStream fs)
|
|
{
|
|
StatusInfo statusInfo = new StatusInfo(
|
|
job.InstanceId,
|
|
job.Name,
|
|
job.Location,
|
|
job.Command,
|
|
job.StatusMessage,
|
|
job.JobStateInfo.State,
|
|
job.HasMoreData,
|
|
job.PSBeginTime,
|
|
job.PSEndTime,
|
|
job.Definition);
|
|
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
serializer.WriteObject(fs, statusInfo);
|
|
fs.Flush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the job (which implements ISerializable) to the provided
|
|
/// file stream.
|
|
/// </summary>
|
|
/// <param name="job">ScheduledJob job to save.</param>
|
|
/// <param name="fs">FileStream.</param>
|
|
private static void SaveResultsToFile(ScheduledJob job, FileStream fs)
|
|
{
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
serializer.WriteObject(fs, job);
|
|
fs.Flush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check the job store results and if maximum number of results exist
|
|
/// remove the oldest results folder to make room for these new results.
|
|
/// </summary>
|
|
/// <param name="outputPath">Output path.</param>
|
|
/// <param name="executionHistoryLength">Maximum size of stored job results.</param>
|
|
private static void CheckJobStoreResults(string outputPath, int executionHistoryLength)
|
|
{
|
|
// Get current results for this job definition.
|
|
Collection<DateTime> jobRuns = ScheduledJobStore.GetJobRunsForDefinitionPath(outputPath);
|
|
if (jobRuns.Count <= executionHistoryLength)
|
|
{
|
|
// There is room for another job run in the store.
|
|
return;
|
|
}
|
|
|
|
// Remove the oldest job run from the store.
|
|
DateTime jobRunToRemove = DateTime.MaxValue;
|
|
foreach (DateTime jobRun in jobRuns)
|
|
{
|
|
jobRunToRemove = (jobRun < jobRunToRemove) ? jobRun : jobRunToRemove;
|
|
}
|
|
|
|
try
|
|
{
|
|
ScheduledJobStore.RemoveJobRunFromOutputPath(outputPath, jobRunToRemove);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Retrieve Job
|
|
|
|
/// <summary>
|
|
/// Finds and load the Job associated with this ScheduledJobDefinition object
|
|
/// having the job run date time provided.
|
|
/// </summary>
|
|
/// <param name="jobRun">DateTime of job run to load.</param>
|
|
/// <param name="definitionName">ScheduledJobDefinition name.</param>
|
|
/// <returns>Job2 job loaded from store.</returns>
|
|
internal static Job2 LoadJobFromStore(string definitionName, DateTime jobRun)
|
|
{
|
|
FileStream fsResults = null;
|
|
Exception ex = null;
|
|
bool corruptedFile = false;
|
|
Job2 job = null;
|
|
|
|
try
|
|
{
|
|
// Results
|
|
fsResults = ScheduledJobStore.GetFileForJobRunItem(
|
|
definitionName,
|
|
jobRun,
|
|
ScheduledJobStore.JobRunItem.Results,
|
|
FileMode.Open,
|
|
FileAccess.Read,
|
|
FileShare.Read);
|
|
|
|
job = LoadResultsFromFile(fsResults);
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.Serialization.SerializationException)
|
|
{
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.Runtime.Serialization.InvalidDataContractException)
|
|
{
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.Xml.XmlException)
|
|
{
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.TypeInitializationException)
|
|
{
|
|
corruptedFile = true;
|
|
}
|
|
finally
|
|
{
|
|
if (fsResults != null)
|
|
{
|
|
fsResults.Close();
|
|
}
|
|
}
|
|
|
|
if (corruptedFile)
|
|
{
|
|
// Remove the corrupted job results file.
|
|
ScheduledJobStore.RemoveJobRun(definitionName, jobRun);
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.CantLoadJobRunFromStore, definitionName, jobRun);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the Job2 object from provided files stream.
|
|
/// </summary>
|
|
/// <param name="fs">FileStream from which to read job object.</param>
|
|
/// <returns>Created Job2 from file stream.</returns>
|
|
private static Job2 LoadResultsFromFile(FileStream fs)
|
|
{
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
return (Job2)serializer.ReadObject(fs);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Static Methods
|
|
|
|
/// <summary>
|
|
/// Adds a Job2 object to the repository.
|
|
/// </summary>
|
|
/// <param name="job">Job2.</param>
|
|
internal static void AddToRepository(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
JobRepository.AddOrReplace(job);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all items in the repository.
|
|
/// </summary>
|
|
internal static void ClearRepository()
|
|
{
|
|
JobRepository.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all items for given job definition name in the
|
|
/// repository.
|
|
/// </summary>
|
|
/// <param name="definitionName">Scheduled job definition name.</param>
|
|
internal static void ClearRepositoryForDefinition(string definitionName)
|
|
{
|
|
if (string.IsNullOrEmpty(definitionName))
|
|
{
|
|
throw new PSArgumentException("definitionName");
|
|
}
|
|
|
|
// This returns a new list object of repository jobs.
|
|
List<Job2> jobList = JobRepository.Jobs;
|
|
foreach (var job in jobList)
|
|
{
|
|
if (string.Compare(definitionName, job.Name,
|
|
StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
JobRepository.Remove(job);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private void RefreshRepository()
|
|
{
|
|
ScheduledJobStore.CreateDirectoryIfNotExists();
|
|
CreateFileSystemWatcher();
|
|
|
|
IEnumerable<string> jobDefinitions = ScheduledJobStore.GetJobDefinitions();
|
|
foreach (string definitionName in jobDefinitions)
|
|
{
|
|
// Create Job2 objects for each job run in store.
|
|
Collection<DateTime> jobRuns = GetJobRuns(definitionName);
|
|
if (jobRuns == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ScheduledJobDefinition definition = null;
|
|
foreach (DateTime jobRun in jobRuns)
|
|
{
|
|
if (jobRun > JobRepository.GetLatestJobRun(definitionName))
|
|
{
|
|
Job2 job;
|
|
try
|
|
{
|
|
if (definition == null)
|
|
{
|
|
definition = ScheduledJobDefinition.LoadFromStore(definitionName, null);
|
|
}
|
|
|
|
job = LoadJobFromStore(definition.Name, jobRun);
|
|
}
|
|
catch (ScheduledJobException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
JobRepository.AddOrReplace(job);
|
|
JobRepository.SetLatestJobRun(definitionName, jobRun);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateFileSystemWatcher()
|
|
{
|
|
// Lazily create the static file system watcher
|
|
// on first use.
|
|
if (StoreWatcher == null)
|
|
{
|
|
lock (SyncObject)
|
|
{
|
|
if (StoreWatcher == null)
|
|
{
|
|
StoreWatcher = new FileSystemWatcher(ScheduledJobStore.GetJobDefinitionLocation());
|
|
StoreWatcher.IncludeSubdirectories = true;
|
|
StoreWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
|
StoreWatcher.Filter = "Results.xml";
|
|
StoreWatcher.EnableRaisingEvents = true;
|
|
StoreWatcher.Changed += (object sender, FileSystemEventArgs e) =>
|
|
{
|
|
UpdateRepositoryObjects(e);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void UpdateRepositoryObjects(FileSystemEventArgs e)
|
|
{
|
|
// Extract job run information from change file path.
|
|
string updateDefinitionName;
|
|
DateTime updateJobRun;
|
|
if (!GetJobRunInfo(e.Name, out updateDefinitionName, out updateJobRun))
|
|
{
|
|
System.Diagnostics.Debug.Assert(false, "All job run updates should have valid directory names.");
|
|
return;
|
|
}
|
|
|
|
// Find corresponding job in repository.
|
|
ScheduledJob updateJob = JobRepository.GetJob(updateDefinitionName, updateJobRun);
|
|
if (updateJob == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Load updated job information from store.
|
|
Job2 job = null;
|
|
try
|
|
{
|
|
job = LoadJobFromStore(updateDefinitionName, updateJobRun);
|
|
}
|
|
catch (ScheduledJobException)
|
|
{ }
|
|
catch (DirectoryNotFoundException)
|
|
{ }
|
|
catch (FileNotFoundException)
|
|
{ }
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
catch (IOException)
|
|
{ }
|
|
|
|
// Update job in repository based on new job store data.
|
|
if (job != null)
|
|
{
|
|
updateJob.Update(job as ScheduledJob);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses job definition name and job run DateTime from provided path string.
|
|
/// Example:
|
|
/// path = "ScheduledJob1\\Output\\20111219-200921-369\\Results.xml"
|
|
/// 'ScheduledJob1' is the definition name.
|
|
/// '20111219-200921-369' is the jobRun DateTime.
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <param name="definitionName"></param>
|
|
/// <param name="jobRunReturn"></param>
|
|
/// <returns></returns>
|
|
private static bool GetJobRunInfo(
|
|
string path,
|
|
out string definitionName,
|
|
out DateTime jobRunReturn)
|
|
{
|
|
// Parse definition name from path.
|
|
string[] pathItems = path.Split(System.IO.Path.DirectorySeparatorChar);
|
|
if (pathItems.Length == 4)
|
|
{
|
|
definitionName = pathItems[0];
|
|
return ScheduledJobStore.ConvertJobRunNameToDateTime(pathItems[2], out jobRunReturn);
|
|
}
|
|
|
|
definitionName = null;
|
|
jobRunReturn = DateTime.MinValue;
|
|
return false;
|
|
}
|
|
|
|
internal static Collection<DateTime> GetJobRuns(string definitionName)
|
|
{
|
|
Collection<DateTime> jobRuns = null;
|
|
try
|
|
{
|
|
jobRuns = ScheduledJobStore.GetJobRunsForDefinition(definitionName);
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{ }
|
|
catch (FileNotFoundException)
|
|
{ }
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
catch (IOException)
|
|
{ }
|
|
|
|
return jobRuns;
|
|
}
|
|
|
|
private void GetJobsBefore(
|
|
DateTime dateTime,
|
|
ref List<Job2> jobList)
|
|
{
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (job.PSEndTime < dateTime &&
|
|
!jobList.Contains(job))
|
|
{
|
|
jobList.Add(job);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetJobsAfter(
|
|
DateTime dateTime,
|
|
ref List<Job2> jobList)
|
|
{
|
|
foreach (var job in JobRepository.Jobs)
|
|
{
|
|
if (job.PSEndTime > dateTime &&
|
|
!jobList.Contains(job))
|
|
{
|
|
jobList.Add(job);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetNewestJobs(
|
|
int maxNumber,
|
|
ref List<Job2> jobList)
|
|
{
|
|
List<Job2> allJobs = JobRepository.Jobs;
|
|
|
|
// Sort descending.
|
|
allJobs.Sort((firstJob, secondJob) =>
|
|
{
|
|
if (firstJob.PSEndTime > secondJob.PSEndTime)
|
|
{
|
|
return -1;
|
|
}
|
|
else if (firstJob.PSEndTime < secondJob.PSEndTime)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
int count = 0;
|
|
foreach (var job in allJobs)
|
|
{
|
|
if (++count > maxNumber)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!jobList.Contains(job))
|
|
{
|
|
jobList.Add(job);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Repository Class
|
|
|
|
/// <summary>
|
|
/// Collection of Job2 objects.
|
|
/// </summary>
|
|
internal class ScheduledJobRepository
|
|
{
|
|
#region Private Members
|
|
|
|
private object _syncObject = new object();
|
|
private Dictionary<Guid, Job2> _jobs = new Dictionary<Guid, Job2>();
|
|
private Dictionary<string, DateTime> _latestJobRuns = new Dictionary<string, DateTime>();
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Returns all job objects in the repository as a List.
|
|
/// </summary>
|
|
public List<Job2> Jobs
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
return new List<Job2>(_jobs.Values);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns count of jobs in repository.
|
|
/// </summary>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
return _jobs.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Add Job2 to repository.
|
|
/// </summary>
|
|
/// <param name="job">Job2 to add.</param>
|
|
public void Add(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_jobs.ContainsKey(job.InstanceId))
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ScheduledJobAlreadyExistsInLocal, job.Name, job.InstanceId);
|
|
throw new ScheduledJobException(msg);
|
|
}
|
|
|
|
_jobs.Add(job.InstanceId, job);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add or replace passed in Job2 object to repository.
|
|
/// </summary>
|
|
/// <param name="job">Job2 to add.</param>
|
|
public void AddOrReplace(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_jobs.ContainsKey(job.InstanceId))
|
|
{
|
|
_jobs.Remove(job.InstanceId);
|
|
}
|
|
|
|
_jobs.Add(job.InstanceId, job);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove Job2 from repository.
|
|
/// </summary>
|
|
/// <param name="job"></param>
|
|
public void Remove(Job2 job)
|
|
{
|
|
if (job == null)
|
|
{
|
|
throw new PSArgumentNullException("job");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_jobs.ContainsKey(job.InstanceId) == false)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ScheduledJobNotInRepository, job.Name);
|
|
throw new ScheduledJobException(msg);
|
|
}
|
|
|
|
_jobs.Remove(job.InstanceId);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all Job2 items from the repository.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
_jobs.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the latest job run Date/Time for the given definition name.
|
|
/// </summary>
|
|
/// <param name="definitionName">ScheduledJobDefinition name.</param>
|
|
/// <returns>Job Run DateTime.</returns>
|
|
public DateTime GetLatestJobRun(string definitionName)
|
|
{
|
|
if (string.IsNullOrEmpty(definitionName))
|
|
{
|
|
throw new PSArgumentException("definitionName");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_latestJobRuns.ContainsKey(definitionName))
|
|
{
|
|
return _latestJobRuns[definitionName];
|
|
}
|
|
else
|
|
{
|
|
DateTime startJobRun = DateTime.MinValue;
|
|
_latestJobRuns.Add(definitionName, startJobRun);
|
|
return startJobRun;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the latest job run Date/Time for the given definition name.
|
|
/// </summary>
|
|
/// <param name="definitionName"></param>
|
|
/// <param name="jobRun"></param>
|
|
public void SetLatestJobRun(string definitionName, DateTime jobRun)
|
|
{
|
|
if (string.IsNullOrEmpty(definitionName))
|
|
{
|
|
throw new PSArgumentException("definitionName");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_latestJobRuns.ContainsKey(definitionName))
|
|
{
|
|
_latestJobRuns.Remove(definitionName);
|
|
_latestJobRuns.Add(definitionName, jobRun);
|
|
}
|
|
else
|
|
{
|
|
_latestJobRuns.Add(definitionName, jobRun);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search repository for specific job run.
|
|
/// </summary>
|
|
/// <param name="definitionName">Definition name.</param>
|
|
/// <param name="jobRun">Job run DateTime.</param>
|
|
/// <returns>Scheduled job if found.</returns>
|
|
public ScheduledJob GetJob(string definitionName, DateTime jobRun)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
foreach (ScheduledJob job in _jobs.Values)
|
|
{
|
|
if (job.PSBeginTime == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DateTime PSBeginTime = job.PSBeginTime ?? DateTime.MinValue;
|
|
if (definitionName.Equals(job.Definition.Name, StringComparison.OrdinalIgnoreCase) &&
|
|
jobRun.Year == PSBeginTime.Year &&
|
|
jobRun.Month == PSBeginTime.Month &&
|
|
jobRun.Day == PSBeginTime.Day &&
|
|
jobRun.Hour == PSBeginTime.Hour &&
|
|
jobRun.Minute == PSBeginTime.Minute &&
|
|
jobRun.Second == PSBeginTime.Second &&
|
|
jobRun.Millisecond == PSBeginTime.Millisecond)
|
|
{
|
|
return job;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|