PowerShell/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs

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
}