/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v7.4.18
src/Microsoft.PowerShell.ScheduledJob/ScheduledJobSourceAdapter.cs
1 088 строк
35 KB
Steve Lee
Update copyright notice to latest guidance (#12190)
24 мар 2020, 21:08
Не верифицирован
24 мар 2020, 21:08
b7cb335
Код
Авторство
О чём код?
// 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 } }