2586 lines
83 KiB
C#
2586 lines
83 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.Management.Automation;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Tracing;
|
|
using System.Runtime.Serialization;
|
|
using System.Security.Permissions;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Microsoft.PowerShell.ScheduledJob
|
|
{
|
|
/// <summary>
|
|
/// This class contains all information needed to define a PowerShell job that
|
|
/// can be scheduled to run through either stand-alone or through the Windows
|
|
/// Task Scheduler.
|
|
/// </summary>
|
|
[Serializable]
|
|
public sealed class ScheduledJobDefinition : ISerializable, IDisposable
|
|
{
|
|
#region Private Members
|
|
|
|
private JobInvocationInfo _invocationInfo;
|
|
private ScheduledJobOptions _options;
|
|
private PSCredential _credential;
|
|
private Guid _globalId = Guid.NewGuid();
|
|
private string _name = string.Empty;
|
|
private int _id = GetCurrentId();
|
|
private int _executionHistoryLength = DefaultExecutionHistoryLength;
|
|
private bool _enabled = true;
|
|
private Dictionary<Int32, ScheduledJobTrigger> _triggers = new Dictionary<Int32, ScheduledJobTrigger>();
|
|
private Int32 _currentTriggerId;
|
|
|
|
private string _definitionFilePath;
|
|
private string _definitionOutputPath;
|
|
|
|
private bool _isDisposed;
|
|
|
|
// Task Action strings.
|
|
private const string TaskExecutionPath = @"pwsh.exe";
|
|
private const string TaskArguments = @"-NoLogo -NonInteractive -WindowStyle Hidden -Command ""Import-Module PSScheduledJob; $jobDef = [Microsoft.PowerShell.ScheduledJob.ScheduledJobDefinition]::LoadFromStore('{0}', '{1}'); $jobDef.Run()""";
|
|
private static object LockObject = new object();
|
|
private static int CurrentId = 0;
|
|
private static int DefaultExecutionHistoryLength = 32;
|
|
|
|
internal static ScheduledJobDefinitionRepository Repository = new ScheduledJobDefinitionRepository();
|
|
|
|
// Task Scheduler COM error codes.
|
|
private const int TSErrorDisabledTask = -2147216602;
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Contains information needed to run the job such as script parameters,
|
|
/// job definition, user credentials, etc.
|
|
/// </summary>
|
|
public JobInvocationInfo InvocationInfo
|
|
{
|
|
get { return _invocationInfo; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the script commands that define the job.
|
|
/// </summary>
|
|
public JobDefinition Definition
|
|
{
|
|
get { return _invocationInfo.Definition; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies Task Scheduler options for the scheduled job.
|
|
/// </summary>
|
|
public ScheduledJobOptions Options
|
|
{
|
|
get { return new ScheduledJobOptions(_options); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Credential.
|
|
/// </summary>
|
|
public PSCredential Credential
|
|
{
|
|
get { return _credential; }
|
|
|
|
internal set { _credential = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// An array of trigger objects that specify a time/condition
|
|
/// for when the job is run.
|
|
/// </summary>
|
|
public List<ScheduledJobTrigger> JobTriggers
|
|
{
|
|
get
|
|
{
|
|
List<Int32> notFoundIds;
|
|
return GetTriggers(null, out notFoundIds);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local instance Id for object instance.
|
|
/// </summary>
|
|
public int Id
|
|
{
|
|
get { return _id; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Global Id for scheduled job definition.
|
|
/// </summary>
|
|
public Guid GlobalId
|
|
{
|
|
get { return _globalId; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name of scheduled job definition.
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get { return _name; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Job command.
|
|
/// </summary>
|
|
public string Command
|
|
{
|
|
get { return _invocationInfo.Command; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the maximum number of job execution data
|
|
/// allowed in the job store.
|
|
/// </summary>
|
|
public int ExecutionHistoryLength
|
|
{
|
|
get { return _executionHistoryLength; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether this scheduled job definition is enabled
|
|
/// in Task Scheduler.
|
|
/// </summary>
|
|
public bool Enabled
|
|
{
|
|
get { return _enabled; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the PowerShell command line execution path.
|
|
/// </summary>
|
|
public string PSExecutionPath
|
|
{
|
|
get { return TaskExecutionPath; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell command line arguments to run
|
|
/// the scheduled job.
|
|
/// </summary>
|
|
public string PSExecutionArgs
|
|
{
|
|
get
|
|
{
|
|
// Escape single quotes in name. Double quotes are not allowed
|
|
// and are caught during name validation.
|
|
string nameEscapeQuotes = _invocationInfo.Name.Replace("'", "''");
|
|
|
|
return string.Format(CultureInfo.InvariantCulture, TaskArguments, nameEscapeQuotes, _definitionFilePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the job run output path for this job definition.
|
|
/// </summary>
|
|
internal string OutputPath
|
|
{
|
|
get { return _definitionOutputPath; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Default constructor is not accessible.
|
|
/// </summary>
|
|
private ScheduledJobDefinition()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="invocationInfo">Information to invoke Job.</param>
|
|
/// <param name="triggers">ScheduledJobTriggers.</param>
|
|
/// <param name="options">ScheduledJobOptions.</param>
|
|
/// <param name="credential">Credential.</param>
|
|
public ScheduledJobDefinition(
|
|
JobInvocationInfo invocationInfo,
|
|
IEnumerable<ScheduledJobTrigger> triggers,
|
|
ScheduledJobOptions options,
|
|
PSCredential credential)
|
|
{
|
|
if (invocationInfo == null)
|
|
{
|
|
throw new PSArgumentNullException("invocationInfo");
|
|
}
|
|
|
|
_name = invocationInfo.Name;
|
|
_invocationInfo = invocationInfo;
|
|
|
|
SetTriggers(triggers, false);
|
|
_options = (options != null) ? new ScheduledJobOptions(options) :
|
|
new ScheduledJobOptions();
|
|
_options.JobDefinition = this;
|
|
|
|
_credential = credential;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISerializable Implementation
|
|
|
|
/// <summary>
|
|
/// Serialization constructor.
|
|
/// </summary>
|
|
/// <param name="info">SerializationInfo.</param>
|
|
/// <param name="context">StreamingContext.</param>
|
|
private ScheduledJobDefinition(
|
|
SerializationInfo info,
|
|
StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
{
|
|
throw new PSArgumentNullException("info");
|
|
}
|
|
|
|
_options = (ScheduledJobOptions)info.GetValue("Options_Member", typeof(ScheduledJobOptions));
|
|
_globalId = Guid.Parse(info.GetString("GlobalId_Member"));
|
|
_name = info.GetString("Name_Member");
|
|
_executionHistoryLength = info.GetInt32("HistoryLength_Member");
|
|
_enabled = info.GetBoolean("Enabled_Member");
|
|
_triggers = (Dictionary<Int32, ScheduledJobTrigger>)info.GetValue("Triggers_Member", typeof(Dictionary<Int32, ScheduledJobTrigger>));
|
|
_currentTriggerId = info.GetInt32("CurrentTriggerId_Member");
|
|
_definitionFilePath = info.GetString("FilePath_Member");
|
|
_definitionOutputPath = info.GetString("OutputPath_Member");
|
|
|
|
object invocationObject = info.GetValue("InvocationInfo_Member", typeof(object));
|
|
_invocationInfo = invocationObject as JobInvocationInfo;
|
|
|
|
// Set the JobDefinition reference for the ScheduledJobTrigger and
|
|
// ScheduledJobOptions objects.
|
|
_options.JobDefinition = this;
|
|
foreach (ScheduledJobTrigger trigger in _triggers.Values)
|
|
{
|
|
trigger.JobDefinition = this;
|
|
}
|
|
|
|
// Instance information.
|
|
_isDisposed = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialization constructor.
|
|
/// </summary>
|
|
/// <param name="info">SerializationInfo.</param>
|
|
/// <param name="context">StreamingContext.</param>
|
|
public void GetObjectData(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
{
|
|
throw new PSArgumentNullException("info");
|
|
}
|
|
|
|
info.AddValue("Options_Member", _options);
|
|
info.AddValue("GlobalId_Member", _globalId.ToString());
|
|
info.AddValue("Name_Member", _name);
|
|
info.AddValue("HistoryLength_Member", _executionHistoryLength);
|
|
info.AddValue("Enabled_Member", _enabled);
|
|
info.AddValue("Triggers_Member", _triggers);
|
|
info.AddValue("CurrentTriggerId_Member", _currentTriggerId);
|
|
info.AddValue("FilePath_Member", _definitionFilePath);
|
|
info.AddValue("OutputPath_Member", _definitionOutputPath);
|
|
|
|
info.AddValue("InvocationInfo_Member", _invocationInfo);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Updates existing information if scheduled job already exists.
|
|
/// WTS entry includes command line, options, and trigger conditions.
|
|
/// </summary>
|
|
private void UpdateWTSFromDefinition()
|
|
{
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
taskScheduler.UpdateTask(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares the current ScheduledJobDefinition task scheduler information
|
|
/// with the corresponding information stored in Task Scheduler. If the
|
|
/// information is different then the task scheduler information in this
|
|
/// object is updated to match what is in Task Scheduler, since that information
|
|
/// takes precedence.
|
|
///
|
|
/// Task Scheduler information:
|
|
/// - Triggers
|
|
/// - Options
|
|
/// - Enabled state.
|
|
/// </summary>
|
|
/// <returns>Boolean if this object data is modified.</returns>
|
|
private bool UpdateDefinitionFromWTS()
|
|
{
|
|
bool dataModified = false;
|
|
|
|
// Get information from Task Scheduler.
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
bool wtsEnabled = taskScheduler.GetTaskEnabled(_name);
|
|
ScheduledJobOptions wtsOptions = taskScheduler.GetJobOptions(_name);
|
|
Collection<ScheduledJobTrigger> wtsTriggers = taskScheduler.GetJobTriggers(_name);
|
|
|
|
//
|
|
// Compare with existing object data and modify if necessary.
|
|
//
|
|
|
|
// Enabled.
|
|
if (wtsEnabled != _enabled)
|
|
{
|
|
_enabled = wtsEnabled;
|
|
dataModified = true;
|
|
}
|
|
|
|
// Options.
|
|
if (wtsOptions.DoNotAllowDemandStart != _options.DoNotAllowDemandStart ||
|
|
wtsOptions.IdleDuration != _options.IdleDuration ||
|
|
wtsOptions.IdleTimeout != _options.IdleTimeout ||
|
|
wtsOptions.MultipleInstancePolicy != _options.MultipleInstancePolicy ||
|
|
wtsOptions.RestartOnIdleResume != _options.RestartOnIdleResume ||
|
|
wtsOptions.RunElevated != _options.RunElevated ||
|
|
wtsOptions.RunWithoutNetwork != _options.RunWithoutNetwork ||
|
|
wtsOptions.ShowInTaskScheduler != _options.ShowInTaskScheduler ||
|
|
wtsOptions.StartIfNotIdle != _options.StartIfNotIdle ||
|
|
wtsOptions.StartIfOnBatteries != _options.StartIfOnBatteries ||
|
|
wtsOptions.StopIfGoingOffIdle != _options.StopIfGoingOffIdle ||
|
|
wtsOptions.StopIfGoingOnBatteries != _options.StopIfGoingOnBatteries ||
|
|
wtsOptions.WakeToRun != _options.WakeToRun)
|
|
{
|
|
// Keep the current scheduled job definition reference.
|
|
wtsOptions.JobDefinition = _options.JobDefinition;
|
|
_options = wtsOptions;
|
|
dataModified = true;
|
|
}
|
|
|
|
// Triggers.
|
|
if (_triggers.Count != wtsTriggers.Count)
|
|
{
|
|
SetTriggers(wtsTriggers, false);
|
|
dataModified = true;
|
|
}
|
|
else
|
|
{
|
|
bool foundTriggerDiff = false;
|
|
|
|
// Compare each trigger object.
|
|
foreach (var wtsTrigger in wtsTriggers)
|
|
{
|
|
if (_triggers.ContainsKey(wtsTrigger.Id) == false)
|
|
{
|
|
foundTriggerDiff = true;
|
|
break;
|
|
}
|
|
|
|
ScheduledJobTrigger trigger = _triggers[wtsTrigger.Id];
|
|
if (trigger.DaysOfWeek != wtsTrigger.DaysOfWeek ||
|
|
trigger.Enabled != wtsTrigger.Enabled ||
|
|
trigger.Frequency != wtsTrigger.Frequency ||
|
|
trigger.Interval != wtsTrigger.Interval ||
|
|
trigger.RandomDelay != wtsTrigger.RandomDelay ||
|
|
trigger.At != wtsTrigger.At ||
|
|
trigger.User != wtsTrigger.User)
|
|
{
|
|
foundTriggerDiff = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundTriggerDiff)
|
|
{
|
|
SetTriggers(wtsTriggers, false);
|
|
dataModified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return dataModified;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds this scheduled job definition to the Task Scheduler.
|
|
/// </summary>
|
|
private void AddToWTS()
|
|
{
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
taskScheduler.CreateTask(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes this scheduled job definition from the Task Scheduler.
|
|
/// This operation will fail if a current instance of this job definition
|
|
/// is running.
|
|
/// If force == true then all current instances will be stopped.
|
|
/// </summary>
|
|
/// <param name="force">Force removal and stop all running instances.</param>
|
|
private void RemoveFromWTS(bool force)
|
|
{
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
taskScheduler.RemoveTask(this, force);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds this scheduled job definition to the job definition store.
|
|
/// </summary>
|
|
private void AddToJobStore()
|
|
{
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
fs = ScheduledJobStore.CreateFileForJobDefinition(Name);
|
|
_definitionFilePath = ScheduledJobStore.GetJobDefinitionLocation();
|
|
_definitionOutputPath = ScheduledJobStore.GetJobRunOutputDirectory(Name);
|
|
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
serializer.WriteObject(fs, this);
|
|
fs.Flush();
|
|
}
|
|
finally
|
|
{
|
|
if (fs != null)
|
|
{
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
// If credentials are provided then update permissions.
|
|
if (Credential != null)
|
|
{
|
|
UpdateFilePermissions(Credential.UserName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates existing file with this definition information.
|
|
/// </summary>
|
|
private void UpdateJobStore()
|
|
{
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
// Overwrite the existing file.
|
|
fs = GetFileStream(
|
|
Name,
|
|
_definitionFilePath,
|
|
FileMode.Create,
|
|
FileAccess.Write,
|
|
FileShare.None);
|
|
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
serializer.WriteObject(fs, this);
|
|
fs.Flush();
|
|
}
|
|
finally
|
|
{
|
|
if (fs != null)
|
|
{
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
// If credentials are provided then update permissions.
|
|
if (Credential != null)
|
|
{
|
|
UpdateFilePermissions(Credential.UserName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates definition file permissions for provided user account.
|
|
/// </summary>
|
|
/// <param name="user">Account user name.</param>
|
|
private void UpdateFilePermissions(string user)
|
|
{
|
|
Exception ex = null;
|
|
try
|
|
{
|
|
// Add user for read access to the job definition file.
|
|
ScheduledJobStore.SetReadAccessOnDefinitionFile(Name, user);
|
|
|
|
// Add user for write access to the job run Output directory.
|
|
ScheduledJobStore.SetWriteAccessOnJobRunOutput(Name, user);
|
|
}
|
|
catch (System.Security.Principal.IdentityNotMappedException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (ArgumentNullException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorSettingAccessPermissions, this.Name, Credential.UserName);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes this scheduled job definition from the job definition store.
|
|
/// </summary>
|
|
private void RemoveFromJobStore()
|
|
{
|
|
ScheduledJobStore.RemoveJobDefinition(Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throws exception if object is disposed.
|
|
/// </summary>
|
|
private void IsDisposed()
|
|
{
|
|
if (_isDisposed == true)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionObjectDisposed, Name);
|
|
throw new RuntimeException(msg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If repository is empty try refreshing it from the store.
|
|
/// </summary>
|
|
private void LoadRepository()
|
|
{
|
|
ScheduledJobDefinition.RefreshRepositoryFromStore();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates all triggers in collection. An exception is thrown
|
|
/// for invalid triggers.
|
|
/// </summary>
|
|
/// <param name="triggers"></param>
|
|
private void ValidateTriggers(IEnumerable<ScheduledJobTrigger> triggers)
|
|
{
|
|
if (triggers != null)
|
|
{
|
|
foreach (var trigger in triggers)
|
|
{
|
|
trigger.Validate();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates the job definition name. Since the job definition
|
|
/// name is used in the job store as a directory name, make sure
|
|
/// it does not contain any invalid characters.
|
|
/// </summary>
|
|
private static void ValidateName(string name)
|
|
{
|
|
if (name.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) != -1)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidJobDefName, name);
|
|
throw new ScheduledJobException(msg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterates through all job run files, opens each job
|
|
/// run and renames it to the provided new name.
|
|
/// </summary>
|
|
/// <param name="newDefName">New job run name.</param>
|
|
private void UpdateJobRunNames(
|
|
string newDefName)
|
|
{
|
|
// Job run results will be under the new scheduled job definition name.
|
|
Collection<DateTime> jobRuns = ScheduledJobSourceAdapter.GetJobRuns(newDefName);
|
|
if (jobRuns == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Load and rename each job.
|
|
ScheduledJobDefinition definition = ScheduledJobDefinition.LoadFromStore(newDefName, null);
|
|
foreach (DateTime jobRun in jobRuns)
|
|
{
|
|
ScheduledJob job = null;
|
|
try
|
|
{
|
|
job = ScheduledJobSourceAdapter.LoadJobFromStore(definition.Name, jobRun) as ScheduledJob;
|
|
}
|
|
catch (ScheduledJobException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
continue;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (job != null)
|
|
{
|
|
job.Name = newDefName;
|
|
job.Definition = definition;
|
|
ScheduledJobSourceAdapter.SaveJobToStore(job);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles known Task Scheduler COM error codes.
|
|
/// </summary>
|
|
/// <param name="e">COMException.</param>
|
|
/// <returns>Error message.</returns>
|
|
private string ConvertCOMErrorCode(System.Runtime.InteropServices.COMException e)
|
|
{
|
|
string msg = null;
|
|
switch (e.ErrorCode)
|
|
{
|
|
case TSErrorDisabledTask:
|
|
msg = ScheduledJobErrorStrings.ReasonTaskDisabled;
|
|
break;
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Methods
|
|
|
|
/// <summary>
|
|
/// Save object to store.
|
|
/// </summary>
|
|
internal void SaveToStore()
|
|
{
|
|
IsDisposed();
|
|
|
|
UpdateJobStore();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares the task scheduler information in this object with
|
|
/// what is stored in Task Scheduler. If there is a difference
|
|
/// then this object is updated with the information from Task
|
|
/// Scheduler and saved to the job store.
|
|
/// </summary>
|
|
internal void SyncWithWTS()
|
|
{
|
|
Exception notFoundEx = null;
|
|
try
|
|
{
|
|
if (UpdateDefinitionFromWTS())
|
|
{
|
|
SaveToStore();
|
|
}
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
notFoundEx = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
notFoundEx = e;
|
|
}
|
|
|
|
if (notFoundEx != null)
|
|
{
|
|
// There is no corresponding Task Scheduler item for this
|
|
// scheduled job definition. Remove this definition from
|
|
// the job store for consistency.
|
|
Remove(true);
|
|
throw notFoundEx;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renames scheduled job definition, store directory and task scheduler task.
|
|
/// </summary>
|
|
/// <param name="newName">New name of job definition.</param>
|
|
internal void RenameAndSave(string newName)
|
|
{
|
|
if (InvocationInfo.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ValidateName(newName);
|
|
|
|
// Attempt to rename job store directory. Detect if new name
|
|
// is not unique.
|
|
string oldName = InvocationInfo.Name;
|
|
Exception ex = null;
|
|
try
|
|
{
|
|
ScheduledJobStore.RenameScheduledJobDefDir(oldName, newName);
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
string msg;
|
|
if (!string.IsNullOrEmpty(ex.Message))
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRenamingScheduledJobWithMessage, oldName, newName, ex.Message);
|
|
}
|
|
else
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRenamingScheduledJob, oldName, newName);
|
|
}
|
|
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Remove old named Task Scheduler task.
|
|
// This also stops any existing running job.
|
|
RemoveFromWTS(true);
|
|
|
|
// Update job definition names.
|
|
_name = newName;
|
|
InvocationInfo.Name = newName;
|
|
InvocationInfo.Definition.Name = newName;
|
|
_definitionOutputPath = ScheduledJobStore.GetJobRunOutputDirectory(Name);
|
|
|
|
// Update job definition in new job store location.
|
|
UpdateJobStore();
|
|
|
|
// Add new Task Scheduler task with new name.
|
|
// Jobs can start running again.
|
|
AddToWTS();
|
|
|
|
// Update any existing job run names.
|
|
UpdateJobRunNames(newName);
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
finally
|
|
{
|
|
// Clear job run cache since job runs now appear in new directory location.
|
|
ScheduledJobSourceAdapter.ClearRepository();
|
|
}
|
|
|
|
// If any part of renaming the various scheduled job components fail,
|
|
// aggressively remove scheduled job corrupted state and inform user.
|
|
if (ex != null)
|
|
{
|
|
try
|
|
{
|
|
Remove(true);
|
|
}
|
|
catch (ScheduledJobException e)
|
|
{
|
|
ex.Data.Add("SchedJobRemoveError", e);
|
|
}
|
|
|
|
string msg;
|
|
if (!string.IsNullOrEmpty(ex.Message))
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.BrokenRenamingScheduledJobWithMessage, oldName, newName, ex.Message);
|
|
}
|
|
else
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.BrokenRenamingScheduledJob, oldName, newName);
|
|
}
|
|
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Registers this scheduled job definition object by doing the
|
|
/// following:
|
|
/// a) Writing this object to the scheduled job object store.
|
|
/// b) Registering this job as a Windows Task Scheduler task.
|
|
/// c) Adding this object to the local repository.
|
|
/// </summary>
|
|
public void Register()
|
|
{
|
|
IsDisposed();
|
|
|
|
LoadRepository();
|
|
|
|
ValidateName(Name);
|
|
|
|
// First add to the job store. If an exception occurs here
|
|
// then this method fails with no clean up.
|
|
Exception ex = null;
|
|
bool corruptedFile = false;
|
|
try
|
|
{
|
|
AddToJobStore();
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.Serialization.SerializationException e)
|
|
{
|
|
corruptedFile = true;
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.Serialization.InvalidDataContractException e)
|
|
{
|
|
corruptedFile = true;
|
|
ex = e;
|
|
}
|
|
catch (ScheduledJobException e)
|
|
{
|
|
// Can be thrown for error setting file access permissions with supplied credentials.
|
|
// But file is not considered corrupted if it already exists.
|
|
corruptedFile = !(e.FQEID.Equals(ScheduledJobStore.ScheduledJobDefExistsFQEID, StringComparison.OrdinalIgnoreCase));
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
if (corruptedFile)
|
|
{
|
|
// Remove from store.
|
|
try
|
|
{
|
|
ScheduledJobStore.RemoveJobDefinition(Name);
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{ }
|
|
catch (FileNotFoundException)
|
|
{ }
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
catch (IOException)
|
|
{ }
|
|
}
|
|
|
|
if (ex is not ScheduledJobException)
|
|
{
|
|
// Wrap in ScheduledJobException type.
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRegisteringDefinitionStore, this.Name);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise just re-throw.
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
// Next register with the Task Scheduler.
|
|
ex = null;
|
|
try
|
|
{
|
|
AddToWTS();
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.InteropServices.COMException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
// Clean up job store.
|
|
RemoveFromJobStore();
|
|
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRegisteringDefinitionTask,
|
|
this.Name,
|
|
(string.IsNullOrEmpty(ex.Message) == false) ? ex.Message : string.Empty);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
|
|
// Finally add to the local repository.
|
|
Repository.AddOrReplace(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves this scheduled job definition object:
|
|
/// a) Rewrites this object to the scheduled job object store.
|
|
/// b) Updates the Windows Task Scheduler task.
|
|
/// </summary>
|
|
public void Save()
|
|
{
|
|
IsDisposed();
|
|
|
|
LoadRepository();
|
|
|
|
ValidateName(Name);
|
|
|
|
// First update the Task Scheduler. If an exception occurs here then
|
|
// we fail with no clean up.
|
|
Exception ex = null;
|
|
try
|
|
{
|
|
UpdateWTSFromDefinition();
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.InteropServices.COMException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
// We want this object to remain synchronized with what is in WTS.
|
|
SyncWithWTS();
|
|
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorUpdatingDefinitionTask, this.Name);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
|
|
// Next save to job store.
|
|
ex = null;
|
|
try
|
|
{
|
|
UpdateJobStore();
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
// Remove this from WTS for consistency.
|
|
RemoveFromWTS(true);
|
|
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorUpdatingDefinitionStore, this.Name);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
|
|
// Finally update this object in the local repository.
|
|
ScheduledJobDefinition.RefreshRepositoryFromStore();
|
|
Repository.AddOrReplace(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes this definition object:
|
|
/// a) Removes from the Task Scheduler
|
|
/// or fails if an instance is currently running.
|
|
/// or stops any running instances if force is true.
|
|
/// b) Removes from the scheduled job definition store.
|
|
/// c) Removes from the local repository.
|
|
/// d) Disposes this object.
|
|
/// </summary>
|
|
public void Remove(bool force)
|
|
{
|
|
IsDisposed();
|
|
|
|
// First remove from Task Scheduler. Catch not found
|
|
// exceptions and continue.
|
|
try
|
|
{
|
|
RemoveFromWTS(force);
|
|
}
|
|
catch (System.IO.DirectoryNotFoundException)
|
|
{
|
|
// Continue with removal.
|
|
}
|
|
catch (System.IO.FileNotFoundException)
|
|
{
|
|
// Continue with removal.
|
|
}
|
|
|
|
// Remove from the Job Store. Catch exceptions and continue
|
|
// with removal.
|
|
Exception ex = null;
|
|
try
|
|
{
|
|
RemoveFromJobStore();
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
finally
|
|
{
|
|
// Remove from the local repository.
|
|
Repository.Remove(this);
|
|
|
|
// Remove job runs for this definition from local repository.
|
|
ScheduledJobSourceAdapter.ClearRepositoryForDefinition(this.Name);
|
|
|
|
// Dispose this object.
|
|
Dispose();
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRemovingDefinitionStore, this.Name);
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the scheduled job immediately. A ScheduledJob object is
|
|
/// returned that represents the running command, and this returned
|
|
/// job is also added to the local job repository. Job results are
|
|
/// not written to the job store.
|
|
/// </summary>
|
|
/// <returns>ScheduledJob object for running job.</returns>
|
|
public ScheduledJob StartJob()
|
|
{
|
|
IsDisposed();
|
|
|
|
ScheduledJob job = new ScheduledJob(_invocationInfo.Command, _invocationInfo.Name, this);
|
|
job.StartJob();
|
|
|
|
return job;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts registered job definition running from the Task Scheduler.
|
|
/// </summary>
|
|
public void RunAsTask()
|
|
{
|
|
IsDisposed();
|
|
|
|
Exception ex = null;
|
|
string reason = null;
|
|
try
|
|
{
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
taskScheduler.RunTask(this);
|
|
}
|
|
}
|
|
catch (System.IO.DirectoryNotFoundException e)
|
|
{
|
|
reason = ScheduledJobErrorStrings.reasonJobNotFound;
|
|
ex = e;
|
|
}
|
|
catch (System.IO.FileNotFoundException e)
|
|
{
|
|
reason = ScheduledJobErrorStrings.reasonJobNotFound;
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.InteropServices.COMException e)
|
|
{
|
|
reason = ConvertCOMErrorCode(e);
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
string msg;
|
|
if (reason != null)
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRunningAsTaskWithReason, this.Name, reason);
|
|
}
|
|
else
|
|
{
|
|
msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRunningAsTask, this.Name);
|
|
}
|
|
|
|
throw new ScheduledJobException(msg, ex);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Trigger Methods
|
|
|
|
/// <summary>
|
|
/// Adds new ScheduledJobTriggers.
|
|
/// </summary>
|
|
/// <param name="triggers">Collection of ScheduledJobTrigger objects.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
public void AddTriggers(
|
|
IEnumerable<ScheduledJobTrigger> triggers,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
if (triggers == null)
|
|
{
|
|
throw new PSArgumentNullException("triggers");
|
|
}
|
|
|
|
// First validate all triggers.
|
|
ValidateTriggers(triggers);
|
|
|
|
Collection<int> newTriggerIds = new Collection<int>();
|
|
foreach (ScheduledJobTrigger trigger in triggers)
|
|
{
|
|
ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(trigger);
|
|
|
|
newTrigger.Id = ++_currentTriggerId;
|
|
newTriggerIds.Add(newTrigger.Id);
|
|
newTrigger.JobDefinition = this;
|
|
_triggers.Add(newTrigger.Id, newTrigger);
|
|
}
|
|
|
|
if (save)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes triggers matching passed in trigger Ids.
|
|
/// </summary>
|
|
/// <param name="triggerIds">Trigger Ids to remove.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
/// <returns>Trigger Ids not found.</returns>
|
|
public List<Int32> RemoveTriggers(
|
|
IEnumerable<Int32> triggerIds,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
List<Int32> idsNotFound = new List<Int32>();
|
|
bool triggerFound = false;
|
|
|
|
// triggerIds is null then remove all triggers.
|
|
if (triggerIds == null)
|
|
{
|
|
_currentTriggerId = 0;
|
|
if (_triggers.Count > 0)
|
|
{
|
|
triggerFound = true;
|
|
|
|
foreach (ScheduledJobTrigger trigger in _triggers.Values)
|
|
{
|
|
trigger.Id = 0;
|
|
trigger.JobDefinition = null;
|
|
}
|
|
|
|
// Create new empty trigger collection.
|
|
_triggers = new Dictionary<int, ScheduledJobTrigger>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (Int32 removeId in triggerIds)
|
|
{
|
|
if (_triggers.ContainsKey(removeId))
|
|
{
|
|
_triggers[removeId].JobDefinition = null;
|
|
_triggers[removeId].Id = 0;
|
|
_triggers.Remove(removeId);
|
|
triggerFound = true;
|
|
}
|
|
else
|
|
{
|
|
idsNotFound.Add(removeId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (save && triggerFound)
|
|
{
|
|
Save();
|
|
}
|
|
|
|
return idsNotFound;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates triggers with provided trigger objects, matching passed in
|
|
/// trigger Id with existing trigger Id.
|
|
/// </summary>
|
|
/// <param name="triggers">Collection of ScheduledJobTrigger objects to update.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
/// <returns>Trigger Ids not found.</returns>
|
|
public List<Int32> UpdateTriggers(
|
|
IEnumerable<ScheduledJobTrigger> triggers,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
if (triggers == null)
|
|
{
|
|
throw new PSArgumentNullException("triggers");
|
|
}
|
|
|
|
// First validate all triggers.
|
|
ValidateTriggers(triggers);
|
|
|
|
List<Int32> idsNotFound = new List<Int32>();
|
|
bool triggerFound = false;
|
|
foreach (ScheduledJobTrigger updateTrigger in triggers)
|
|
{
|
|
if (_triggers.ContainsKey(updateTrigger.Id))
|
|
{
|
|
// Disassociate old trigger from this definition.
|
|
_triggers[updateTrigger.Id].JobDefinition = null;
|
|
|
|
// Replace older trigger object with new updated one.
|
|
ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(updateTrigger);
|
|
newTrigger.Id = updateTrigger.Id;
|
|
newTrigger.JobDefinition = this;
|
|
_triggers[newTrigger.Id] = newTrigger;
|
|
triggerFound = true;
|
|
}
|
|
else
|
|
{
|
|
idsNotFound.Add(updateTrigger.Id);
|
|
}
|
|
}
|
|
|
|
if (save && triggerFound)
|
|
{
|
|
Save();
|
|
}
|
|
|
|
return idsNotFound;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new set of ScheduledJobTriggers for this object.
|
|
/// </summary>
|
|
/// <param name="newTriggers">Array of ScheduledJobTrigger objects to set.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
public void SetTriggers(
|
|
IEnumerable<ScheduledJobTrigger> newTriggers,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
// First validate all triggers.
|
|
ValidateTriggers(newTriggers);
|
|
|
|
// Disassociate any old trigger objects from this definition.
|
|
foreach (ScheduledJobTrigger trigger in _triggers.Values)
|
|
{
|
|
trigger.JobDefinition = null;
|
|
}
|
|
|
|
_currentTriggerId = 0;
|
|
_triggers = new Dictionary<Int32, ScheduledJobTrigger>();
|
|
if (newTriggers != null)
|
|
{
|
|
foreach (ScheduledJobTrigger trigger in newTriggers)
|
|
{
|
|
ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(trigger);
|
|
|
|
newTrigger.Id = ++_currentTriggerId;
|
|
newTrigger.JobDefinition = this;
|
|
_triggers.Add(newTrigger.Id, newTrigger);
|
|
}
|
|
}
|
|
|
|
if (save)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list of new ScheduledJobTrigger objects corresponding
|
|
/// to the passed in trigger Ids. Also returns an array of trigger Ids
|
|
/// that were not found in an out parameter.
|
|
/// </summary>
|
|
/// <param name="triggerIds">List of trigger Ids.</param>
|
|
/// <param name="notFoundIds">List of not found trigger Ids.</param>
|
|
/// <returns>List of ScheduledJobTrigger objects.</returns>
|
|
public List<ScheduledJobTrigger> GetTriggers(
|
|
IEnumerable<Int32> triggerIds,
|
|
out List<Int32> notFoundIds)
|
|
{
|
|
IsDisposed();
|
|
|
|
List<ScheduledJobTrigger> newTriggers;
|
|
List<Int32> notFoundList = new List<Int32>();
|
|
if (triggerIds == null)
|
|
{
|
|
// Return all triggers.
|
|
newTriggers = new List<ScheduledJobTrigger>();
|
|
foreach (ScheduledJobTrigger trigger in _triggers.Values)
|
|
{
|
|
newTriggers.Add(new ScheduledJobTrigger(trigger));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Filter returned triggers to match requested.
|
|
newTriggers = new List<ScheduledJobTrigger>();
|
|
foreach (Int32 triggerId in triggerIds)
|
|
{
|
|
if (_triggers.ContainsKey(triggerId))
|
|
{
|
|
newTriggers.Add(new ScheduledJobTrigger(_triggers[triggerId]));
|
|
}
|
|
else
|
|
{
|
|
notFoundList.Add(triggerId);
|
|
}
|
|
}
|
|
}
|
|
|
|
notFoundIds = notFoundList;
|
|
|
|
// Return array of ScheduledJobTrigger objects sorted by Id.
|
|
newTriggers.Sort((firstTrigger, secondTrigger) =>
|
|
{
|
|
return ((int)firstTrigger.Id - (int)secondTrigger.Id);
|
|
});
|
|
|
|
return newTriggers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds and returns a copy of the ScheduledJobTrigger corresponding to
|
|
/// the passed in trigger Id.
|
|
/// </summary>
|
|
/// <param name="triggerId">Trigger Id.</param>
|
|
/// <returns>ScheduledJobTrigger object.</returns>
|
|
public ScheduledJobTrigger GetTrigger(
|
|
Int32 triggerId)
|
|
{
|
|
IsDisposed();
|
|
|
|
if (_triggers.ContainsKey(triggerId))
|
|
{
|
|
return new ScheduledJobTrigger(_triggers[triggerId]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Update Methods
|
|
|
|
/// <summary>
|
|
/// Updates scheduled job options.
|
|
/// </summary>
|
|
/// <param name="options">ScheduledJobOptions or null for default.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
public void UpdateOptions(
|
|
ScheduledJobOptions options,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
// Disassociate current options object from this definition.
|
|
_options.JobDefinition = null;
|
|
|
|
// options == null is allowed and signals the use default
|
|
// Task Scheduler options.
|
|
_options = (options != null) ? new ScheduledJobOptions(options) :
|
|
new ScheduledJobOptions();
|
|
_options.JobDefinition = this;
|
|
|
|
if (save)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the execution history length property.
|
|
/// </summary>
|
|
/// <param name="executionHistoryLength">Execution history length.</param>
|
|
/// <param name="save">Save to store.</param>
|
|
public void SetExecutionHistoryLength(
|
|
int executionHistoryLength,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
_executionHistoryLength = executionHistoryLength;
|
|
|
|
if (save)
|
|
{
|
|
SaveToStore();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all execution results in the job store.
|
|
/// </summary>
|
|
public void ClearExecutionHistory()
|
|
{
|
|
IsDisposed();
|
|
|
|
ScheduledJobStore.RemoveAllJobRuns(Name);
|
|
ScheduledJobSourceAdapter.ClearRepositoryForDefinition(Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the JobInvocationInfo object.
|
|
/// </summary>
|
|
/// <param name="jobInvocationInfo">JobInvocationInfo.</param>
|
|
/// <param name="save">Save to store.</param>
|
|
public void UpdateJobInvocationInfo(
|
|
JobInvocationInfo jobInvocationInfo,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
if (jobInvocationInfo == null)
|
|
{
|
|
throw new PSArgumentNullException("jobInvocationInfo");
|
|
}
|
|
|
|
_invocationInfo = jobInvocationInfo;
|
|
_name = jobInvocationInfo.Name;
|
|
|
|
if (save)
|
|
{
|
|
SaveToStore();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the enabled state of this object.
|
|
/// </summary>
|
|
/// <param name="enabled">True if enabled.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
public void SetEnabled(
|
|
bool enabled,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
_enabled = enabled;
|
|
|
|
if (save)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the name of this scheduled job definition.
|
|
/// </summary>
|
|
/// <param name="name">Name.</param>
|
|
/// <param name="save">Update Windows Task Scheduler and save to store.</param>
|
|
public void SetName(
|
|
string name,
|
|
bool save)
|
|
{
|
|
IsDisposed();
|
|
|
|
_name = (name != null) ? name : string.Empty;
|
|
|
|
if (save)
|
|
{
|
|
Save();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
/// <summary>
|
|
/// Dispose.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
_isDisposed = true;
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Static Methods
|
|
|
|
/// <summary>
|
|
/// Synchronizes the local ScheduledJobDefinition repository with the
|
|
/// scheduled job definitions in the job store.
|
|
/// </summary>
|
|
/// <param name="itemFound">Callback delegate for each discovered item.</param>
|
|
/// <returns>Dictionary of errors.</returns>
|
|
internal static Dictionary<string, Exception> RefreshRepositoryFromStore(
|
|
Action<ScheduledJobDefinition> itemFound = null)
|
|
{
|
|
Dictionary<string, Exception> errors = new Dictionary<string, Exception>();
|
|
|
|
// Get current list of job definition files in store, and create hash
|
|
// table for quick look up.
|
|
IEnumerable<string> jobDefinitionPathNames = ScheduledJobStore.GetJobDefinitions();
|
|
HashSet<string> jobDefinitionNamesHash = new HashSet<string>();
|
|
foreach (string pathName in jobDefinitionPathNames)
|
|
{
|
|
// Remove path information and use job definition name only.
|
|
int indx = pathName.LastIndexOf('\\');
|
|
string jobDefName = (indx != -1) ? pathName.Substring(indx + 1) : pathName;
|
|
jobDefinitionNamesHash.Add(jobDefName);
|
|
}
|
|
|
|
// First remove definition objects not in store.
|
|
// Repository.Definitions returns a *copy* of current repository items.
|
|
foreach (ScheduledJobDefinition jobDef in Repository.Definitions)
|
|
{
|
|
if (jobDefinitionNamesHash.Contains(jobDef.Name) == false)
|
|
{
|
|
Repository.Remove(jobDef);
|
|
}
|
|
else
|
|
{
|
|
jobDefinitionNamesHash.Remove(jobDef.Name);
|
|
|
|
if (itemFound != null)
|
|
{
|
|
itemFound(jobDef);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next add definition items not in local repository.
|
|
foreach (string jobDefinitionName in jobDefinitionNamesHash)
|
|
{
|
|
try
|
|
{
|
|
// Read the job definition object from file and add to local repository.
|
|
ScheduledJobDefinition jobDefinition = ScheduledJobDefinition.LoadDefFromStore(jobDefinitionName, null);
|
|
Repository.AddOrReplace(jobDefinition);
|
|
|
|
if (itemFound != null)
|
|
{
|
|
itemFound(jobDefinition);
|
|
}
|
|
}
|
|
catch (System.IO.IOException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
catch (System.Xml.XmlException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
catch (System.TypeInitializationException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
catch (System.Runtime.Serialization.SerializationException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
catch (System.ArgumentNullException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
catch (System.UnauthorizedAccessException e)
|
|
{
|
|
errors.Add(jobDefinitionName, e);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a ScheduledJobDefinition object from file and
|
|
/// returns object.
|
|
/// </summary>
|
|
/// <param name="definitionName">Name of definition to load.</param>
|
|
/// <param name="definitionPath">Path to definition file.</param>
|
|
/// <returns>ScheduledJobDefinition object.</returns>
|
|
internal static ScheduledJobDefinition LoadDefFromStore(
|
|
string definitionName,
|
|
string definitionPath)
|
|
{
|
|
ScheduledJobDefinition definition = null;
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
fs = GetFileStream(
|
|
definitionName,
|
|
definitionPath,
|
|
FileMode.Open,
|
|
FileAccess.Read,
|
|
FileShare.Read);
|
|
|
|
XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer();
|
|
definition = serializer.ReadObject(fs) as ScheduledJobDefinition;
|
|
}
|
|
finally
|
|
{
|
|
if (fs != null)
|
|
{
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
return definition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new ScheduledJobDefinition object from a file.
|
|
/// </summary>
|
|
/// <param name="definitionName">Name of definition to load.</param>
|
|
/// <param name="definitionPath">Path to definition file.</param>
|
|
/// <returns>ScheduledJobDefinition object.</returns>
|
|
public static ScheduledJobDefinition LoadFromStore(
|
|
string definitionName,
|
|
string definitionPath)
|
|
{
|
|
if (string.IsNullOrEmpty(definitionName))
|
|
{
|
|
throw new PSArgumentNullException("definitionName");
|
|
}
|
|
|
|
ScheduledJobDefinition definition = null;
|
|
bool corruptedFile = false;
|
|
Exception ex = null;
|
|
|
|
try
|
|
{
|
|
definition = LoadDefFromStore(definitionName, definitionPath);
|
|
}
|
|
catch (DirectoryNotFoundException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Xml.XmlException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.TypeInitializationException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.ArgumentNullException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
catch (System.Runtime.Serialization.SerializationException e)
|
|
{
|
|
ex = e;
|
|
corruptedFile = true;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
//
|
|
// Remove definition if corrupted.
|
|
// But only if the corrupted file is in the default scheduled jobs
|
|
// path for the current user.
|
|
//
|
|
if (corruptedFile &&
|
|
(definitionPath == null ||
|
|
ScheduledJobStore.IsDefaultUserPath(definitionPath)))
|
|
{
|
|
// Remove corrupted scheduled job definition.
|
|
RemoveDefinition(definitionName);
|
|
|
|
// Throw exception for corrupted/removed job definition.
|
|
throw new ScheduledJobException(
|
|
StringUtil.Format(ScheduledJobErrorStrings.CantLoadDefinitionFromStore, definitionName),
|
|
ex);
|
|
}
|
|
|
|
// Throw exception for not found job definition.
|
|
throw new ScheduledJobException(
|
|
StringUtil.Format(ScheduledJobErrorStrings.CannotFindJobDefinition, definitionName),
|
|
ex);
|
|
}
|
|
|
|
// Make sure the deserialized ScheduledJobDefinition object contains the same
|
|
// Task Scheduler information that is stored in Task Scheduler.
|
|
definition.SyncWithWTS();
|
|
|
|
return definition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper method to remove a scheduled job definition
|
|
/// by name from job store and Task Scheduler.
|
|
/// </summary>
|
|
/// <param name="definitionName">Scheduled job definition name.</param>
|
|
internal static void RemoveDefinition(
|
|
string definitionName)
|
|
{
|
|
// Remove from store.
|
|
try
|
|
{
|
|
ScheduledJobStore.RemoveJobDefinition(definitionName);
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{ }
|
|
catch (FileNotFoundException)
|
|
{ }
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
catch (IOException)
|
|
{ }
|
|
|
|
// Check and remove from Task Scheduler.
|
|
using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS())
|
|
{
|
|
try
|
|
{
|
|
taskScheduler.RemoveTaskByName(definitionName, true, true);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{ }
|
|
catch (IOException)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
private static int GetCurrentId()
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
return ++CurrentId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts a scheduled job based on definition name and returns the
|
|
/// running job object. Returned job is also added to the local
|
|
/// job repository. Job results are not written to store.
|
|
/// </summary>
|
|
/// <param name="DefinitionName">ScheduledJobDefinition name.</param>
|
|
public static Job2 StartJob(
|
|
string DefinitionName)
|
|
{
|
|
// Load scheduled job definition.
|
|
ScheduledJobDefinition jobDefinition = ScheduledJobDefinition.LoadFromStore(DefinitionName, null);
|
|
|
|
// Start job.
|
|
return jobDefinition.StartJob();
|
|
}
|
|
|
|
private static FileStream GetFileStream(
|
|
string definitionName,
|
|
string definitionPath,
|
|
FileMode fileMode,
|
|
FileAccess fileAccess,
|
|
FileShare fileShare)
|
|
{
|
|
FileStream fs;
|
|
|
|
if (definitionPath == null)
|
|
{
|
|
// Look for definition in default current user location.
|
|
fs = ScheduledJobStore.GetFileForJobDefinition(
|
|
definitionName,
|
|
fileMode,
|
|
fileAccess,
|
|
fileShare);
|
|
}
|
|
else
|
|
{
|
|
// Look for definition in known path.
|
|
fs = ScheduledJobStore.GetFileForJobDefinition(
|
|
definitionName,
|
|
definitionPath,
|
|
fileMode,
|
|
fileAccess,
|
|
fileShare);
|
|
}
|
|
|
|
return fs;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Running Job
|
|
|
|
/// <summary>
|
|
/// Create a Job2 job, runs it and waits for it to complete.
|
|
/// Job status and results are written to the job store.
|
|
/// </summary>
|
|
/// <returns>Job2 job object that was run.</returns>
|
|
public Job2 Run()
|
|
{
|
|
Job2 job = null;
|
|
|
|
using (PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
Exception ex = null;
|
|
try
|
|
{
|
|
JobManager jobManager = Runspace.DefaultRunspace.JobManager;
|
|
|
|
job = jobManager.NewJob(InvocationInfo);
|
|
|
|
// If this is a scheduled job type then include this object so
|
|
// so that ScheduledJobSourceAdapter knows where the results are
|
|
// to be stored.
|
|
ScheduledJob schedJob = job as ScheduledJob;
|
|
if (schedJob != null)
|
|
{
|
|
schedJob.Definition = this;
|
|
schedJob.AllowSetShouldExit = true;
|
|
}
|
|
|
|
// Update job store data when job begins.
|
|
job.StateChanged += (object sender, JobStateEventArgs e) =>
|
|
{
|
|
if (e.JobStateInfo.State == JobState.Running)
|
|
{
|
|
// Write job to store with this running state.
|
|
jobManager.PersistJob(job, Definition);
|
|
}
|
|
};
|
|
|
|
job.StartJob();
|
|
|
|
// Log scheduled job start.
|
|
_tracer.WriteScheduledJobStartEvent(
|
|
job.Name,
|
|
job.PSBeginTime.ToString());
|
|
|
|
// Wait for job to finish.
|
|
job.Finished.WaitOne();
|
|
|
|
// Ensure that the job run results are persisted to store.
|
|
jobManager.PersistJob(job, Definition);
|
|
|
|
// Perform a Receive-Job on the job object. Output data will be dropped
|
|
// but we do this to execute any client method calls, in particular we
|
|
// want SetShouldExit to set the correct exit code on the process for
|
|
// use inside Task Scheduler.
|
|
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
|
|
{
|
|
// Run on the default runspace.
|
|
ps.AddCommand("Receive-Job").AddParameter("Job", job).AddParameter("Keep", true);
|
|
ps.Invoke();
|
|
}
|
|
|
|
// Log scheduled job finish.
|
|
_tracer.WriteScheduledJobCompleteEvent(
|
|
job.Name,
|
|
job.PSEndTime.ToString(),
|
|
job.JobStateInfo.State.ToString());
|
|
}
|
|
catch (RuntimeException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (InvalidOperationException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Security.SecurityException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (ScriptCallDepthException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.Serialization.SerializationException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Runtime.Serialization.InvalidDataContractException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (System.Xml.XmlException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
catch (Microsoft.PowerShell.ScheduledJob.ScheduledJobException e)
|
|
{
|
|
ex = e;
|
|
}
|
|
|
|
if (ex != null)
|
|
{
|
|
// Log error.
|
|
_tracer.WriteScheduledJobErrorEvent(
|
|
this.Name,
|
|
ex.Message,
|
|
ex.StackTrace.ToString(),
|
|
(ex.InnerException != null) ? ex.InnerException.Message : string.Empty);
|
|
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region ScheduledJobDefinition Repository
|
|
|
|
/// <summary>
|
|
/// Collection of ScheduledJobDefinition objects.
|
|
/// </summary>
|
|
internal class ScheduledJobDefinitionRepository
|
|
{
|
|
#region Private Members
|
|
|
|
private object _syncObject = new object();
|
|
private Dictionary<string, ScheduledJobDefinition> _definitions = new Dictionary<string, ScheduledJobDefinition>();
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Returns all definition objects in the repository as a List.
|
|
/// </summary>
|
|
public List<ScheduledJobDefinition> Definitions
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
// Sort returned list by Ids.
|
|
List<ScheduledJobDefinition> rtnList =
|
|
new List<ScheduledJobDefinition>(_definitions.Values);
|
|
|
|
rtnList.Sort((firstJob, secondJob) =>
|
|
{
|
|
if (firstJob.Id > secondJob.Id)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (firstJob.Id < secondJob.Id)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
return rtnList;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns count of object in repository.
|
|
/// </summary>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
return _definitions.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Add ScheduledJobDefinition to repository.
|
|
/// </summary>
|
|
/// <param name="jobDef"></param>
|
|
public void Add(ScheduledJobDefinition jobDef)
|
|
{
|
|
if (jobDef == null)
|
|
{
|
|
throw new PSArgumentNullException("jobDef");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_definitions.ContainsKey(jobDef.Name))
|
|
{
|
|
string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionAlreadyExistsInLocal, jobDef.Name, jobDef.GlobalId);
|
|
throw new ScheduledJobException(msg);
|
|
}
|
|
|
|
_definitions.Add(jobDef.Name, jobDef);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add or replace passed in ScheduledJobDefinition object to repository.
|
|
/// </summary>
|
|
/// <param name="jobDef"></param>
|
|
public void AddOrReplace(ScheduledJobDefinition jobDef)
|
|
{
|
|
if (jobDef == null)
|
|
{
|
|
throw new PSArgumentNullException("jobDef");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_definitions.ContainsKey(jobDef.Name))
|
|
{
|
|
_definitions.Remove(jobDef.Name);
|
|
}
|
|
|
|
_definitions.Add(jobDef.Name, jobDef);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove ScheduledJobDefinition from repository.
|
|
/// </summary>
|
|
/// <param name="jobDef"></param>
|
|
public void Remove(ScheduledJobDefinition jobDef)
|
|
{
|
|
if (jobDef == null)
|
|
{
|
|
throw new PSArgumentNullException("jobDef");
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_definitions.ContainsKey(jobDef.Name))
|
|
{
|
|
_definitions.Remove(jobDef.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to see if a ScheduledJobDefinition object exists with
|
|
/// the provided definition name.
|
|
/// </summary>
|
|
/// <param name="jobDefName">Definition name.</param>
|
|
/// <returns>True if definition exists.</returns>
|
|
public bool Contains(string jobDefName)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
return _definitions.ContainsKey(jobDefName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all ScheduledJobDefinition items from the repository.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
_definitions.Clear();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Exceptions
|
|
|
|
/// <summary>
|
|
/// Exception thrown for errors in Scheduled Jobs.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ScheduledJobException : SystemException
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of ScheduledJobException class.
|
|
/// </summary>
|
|
public ScheduledJobException()
|
|
: base
|
|
(
|
|
StringUtil.Format(ScheduledJobErrorStrings.GeneralWTSError)
|
|
)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of ScheduledJobException class.
|
|
/// </summary>
|
|
/// <param name="message">
|
|
/// The error message that explains the reason for the exception.
|
|
/// </param>
|
|
public ScheduledJobException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of ScheduledJobException class.
|
|
/// </summary>
|
|
/// <param name="message">
|
|
/// The error message that explains the reason for the exception.
|
|
/// </param>
|
|
/// <param name="innerException">
|
|
/// The exception that is the cause of the current exception.
|
|
/// </param>
|
|
public ScheduledJobException(string message, Exception innerException)
|
|
: base(message, innerException)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fully qualified error id for exception.
|
|
/// </summary>
|
|
internal string FQEID
|
|
{
|
|
get { return _fqeid; }
|
|
|
|
set { _fqeid = value ?? string.Empty; }
|
|
}
|
|
|
|
private string _fqeid = string.Empty;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
/// <summary>
|
|
/// Simple string formatting helper.
|
|
/// </summary>
|
|
internal class StringUtil
|
|
{
|
|
internal static string Format(string formatSpec, object o)
|
|
{
|
|
return string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, formatSpec, o);
|
|
}
|
|
|
|
internal static string Format(string formatSpec, params object[] o)
|
|
{
|
|
return string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, formatSpec, o);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScheduledJobInvocationInfo Class
|
|
|
|
/// <summary>
|
|
/// This class defines the JobInvocationInfo class for PowerShell jobs
|
|
/// for job scheduling. The following parameters are supported:
|
|
///
|
|
/// "ScriptBlock" -> ScriptBlock
|
|
/// "FilePath" -> String
|
|
/// "InitializationScript" -> ScriptBlock
|
|
/// "ArgumentList" -> object[]
|
|
/// "RunAs32" -> Boolean
|
|
/// "Authentication" -> AuthenticationMechanism.
|
|
/// </summary>
|
|
[Serializable]
|
|
public sealed class ScheduledJobInvocationInfo : JobInvocationInfo
|
|
{
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="definition">JobDefinition.</param>
|
|
/// <param name="parameters">Dictionary of parameters.</param>
|
|
public ScheduledJobInvocationInfo(JobDefinition definition, Dictionary<string, object> parameters)
|
|
: base(definition, parameters)
|
|
{
|
|
if (definition == null)
|
|
{
|
|
throw new PSArgumentNullException("definition");
|
|
}
|
|
|
|
Name = definition.Name;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Strings
|
|
|
|
/// <summary>
|
|
/// ScriptBlock parameter.
|
|
/// </summary>
|
|
public const string ScriptBlockParameter = "ScriptBlock";
|
|
|
|
/// <summary>
|
|
/// FilePath parameter.
|
|
/// </summary>
|
|
public const string FilePathParameter = "FilePath";
|
|
|
|
/// <summary>
|
|
/// RunAs32 parameter.
|
|
/// </summary>
|
|
public const string RunAs32Parameter = "RunAs32";
|
|
|
|
/// <summary>
|
|
/// Authentication parameter.
|
|
/// </summary>
|
|
public const string AuthenticationParameter = "Authentication";
|
|
|
|
/// <summary>
|
|
/// InitializationScript parameter.
|
|
/// </summary>
|
|
public const string InitializationScriptParameter = "InitializationScript";
|
|
|
|
/// <summary>
|
|
/// ArgumentList parameter.
|
|
/// </summary>
|
|
public const string ArgumentListParameter = "ArgumentList";
|
|
|
|
#endregion
|
|
|
|
#region ISerializable Implementation
|
|
|
|
/// <summary>
|
|
/// Serialization constructor.
|
|
/// </summary>
|
|
/// <param name="info">SerializationInfo.</param>
|
|
/// <param name="context">StreamingContext.</param>
|
|
internal ScheduledJobInvocationInfo(
|
|
SerializationInfo info,
|
|
StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
{
|
|
throw new PSArgumentNullException("info");
|
|
}
|
|
|
|
DeserializeInvocationInfo(info);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialization implementation.
|
|
/// </summary>
|
|
/// <param name="info">SerializationInfo.</param>
|
|
/// <param name="context">StreamingContext.</param>
|
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
{
|
|
throw new PSArgumentNullException("info");
|
|
}
|
|
|
|
SerializeInvocationInfo(info);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private void SerializeInvocationInfo(SerializationInfo info)
|
|
{
|
|
info.AddValue("InvocationInfo_Command", this.Command);
|
|
info.AddValue("InvocationInfo_Name", this.Name);
|
|
info.AddValue("InvocationInfo_AdapterType", this.Definition.JobSourceAdapterType);
|
|
info.AddValue("InvocationInfo_ModuleName", this.Definition.ModuleName);
|
|
info.AddValue("InvocationInfo_AdapterTypeName", this.Definition.JobSourceAdapterTypeName);
|
|
|
|
// Get the job parameters.
|
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
|
foreach (var commandParam in this.Parameters[0])
|
|
{
|
|
if (!parameters.ContainsKey(commandParam.Name))
|
|
{
|
|
parameters.Add(commandParam.Name, commandParam.Value);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Serialize only parameters that scheduled job knows about.
|
|
//
|
|
|
|
// ScriptBlock
|
|
if (parameters.ContainsKey(ScriptBlockParameter))
|
|
{
|
|
ScriptBlock scriptBlock = (ScriptBlock)parameters[ScriptBlockParameter];
|
|
info.AddValue("InvocationParam_ScriptBlock", scriptBlock.ToString());
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_ScriptBlock", null);
|
|
}
|
|
|
|
// FilePath
|
|
if (parameters.ContainsKey(FilePathParameter))
|
|
{
|
|
string filePath = (string)parameters[FilePathParameter];
|
|
info.AddValue("InvocationParam_FilePath", filePath);
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_FilePath", string.Empty);
|
|
}
|
|
|
|
// InitializationScript
|
|
if (parameters.ContainsKey(InitializationScriptParameter))
|
|
{
|
|
ScriptBlock scriptBlock = (ScriptBlock)parameters[InitializationScriptParameter];
|
|
info.AddValue("InvocationParam_InitScript", scriptBlock.ToString());
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_InitScript", string.Empty);
|
|
}
|
|
|
|
// RunAs32
|
|
if (parameters.ContainsKey(RunAs32Parameter))
|
|
{
|
|
bool runAs32 = (bool)parameters[RunAs32Parameter];
|
|
info.AddValue("InvocationParam_RunAs32", runAs32);
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_RunAs32", false);
|
|
}
|
|
|
|
// Authentication
|
|
if (parameters.ContainsKey(AuthenticationParameter))
|
|
{
|
|
AuthenticationMechanism authentication = (AuthenticationMechanism)parameters[AuthenticationParameter];
|
|
info.AddValue("InvocationParam_Authentication", authentication);
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_Authentication", AuthenticationMechanism.Default);
|
|
}
|
|
|
|
// ArgumentList
|
|
if (parameters.ContainsKey(ArgumentListParameter))
|
|
{
|
|
object[] argList = (object[])parameters[ArgumentListParameter];
|
|
info.AddValue("InvocationParam_ArgList", argList);
|
|
}
|
|
else
|
|
{
|
|
info.AddValue("InvocationParam_ArgList", null);
|
|
}
|
|
}
|
|
|
|
private void DeserializeInvocationInfo(SerializationInfo info)
|
|
{
|
|
string command = info.GetString("InvocationInfo_Command");
|
|
string name = info.GetString("InvocationInfo_Name");
|
|
string moduleName = info.GetString("InvocationInfo_ModuleName");
|
|
string adapterTypeName = info.GetString("InvocationInfo_AdapterTypeName");
|
|
|
|
//
|
|
// Parameters
|
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
|
|
|
// ScriptBlock
|
|
string script = info.GetString("InvocationParam_ScriptBlock");
|
|
if (script != null)
|
|
{
|
|
parameters.Add(ScriptBlockParameter, ScriptBlock.Create(script));
|
|
}
|
|
|
|
// FilePath
|
|
string filePath = info.GetString("InvocationParam_FilePath");
|
|
if (!string.IsNullOrEmpty(filePath))
|
|
{
|
|
parameters.Add(FilePathParameter, filePath);
|
|
}
|
|
|
|
// InitializationScript
|
|
script = info.GetString("InvocationParam_InitScript");
|
|
if (!string.IsNullOrEmpty(script))
|
|
{
|
|
parameters.Add(InitializationScriptParameter, ScriptBlock.Create(script));
|
|
}
|
|
|
|
// RunAs32
|
|
bool runAs32 = info.GetBoolean("InvocationParam_RunAs32");
|
|
parameters.Add(RunAs32Parameter, runAs32);
|
|
|
|
// Authentication
|
|
AuthenticationMechanism authentication = (AuthenticationMechanism)info.GetValue("InvocationParam_Authentication",
|
|
typeof(AuthenticationMechanism));
|
|
parameters.Add(AuthenticationParameter, authentication);
|
|
|
|
// ArgumentList
|
|
object[] argList = (object[])info.GetValue("InvocationParam_ArgList", typeof(object[]));
|
|
if (argList != null)
|
|
{
|
|
parameters.Add(ArgumentListParameter, argList);
|
|
}
|
|
|
|
JobDefinition jobDefinition = new JobDefinition(null, command, name);
|
|
jobDefinition.ModuleName = moduleName;
|
|
jobDefinition.JobSourceAdapterTypeName = adapterTypeName;
|
|
|
|
// Convert to JobInvocationParameter collection
|
|
CommandParameterCollection paramCollection = new CommandParameterCollection();
|
|
foreach (KeyValuePair<string, object> param in parameters)
|
|
{
|
|
CommandParameter paramItem = new CommandParameter(param.Key, param.Value);
|
|
paramCollection.Add(paramItem);
|
|
}
|
|
|
|
this.Definition = jobDefinition;
|
|
this.Name = name;
|
|
this.Command = command;
|
|
this.Parameters.Add(paramCollection);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
}
|