/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v7.4.18
src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs
1 286 строк
39 KB
Aditya Patwardhan
Update to .NET 8 Preview 4 (#19696)
22 июн 2023, 01:17
Не верифицирован
22 июн 2023, 01:17
ebe0b54
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Runtime.Serialization; using System.Security.Permissions; using System.Text; namespace Microsoft.PowerShell.ScheduledJob { /// <summary> /// This is a Job2 derived class that contains a DefinitionJob for /// running job definition based jobs but can also save and load job /// results data from file. This class is used to load job result /// data from previously run jobs so that a user can view results of /// scheduled job runs. This class also contains the definition of /// the scheduled job and so can run an instance of the scheduled /// job and optionally save results to file. /// </summary> public sealed class ScheduledJob : Job2, ISerializable { #region Private Members private ScheduledJobDefinition _jobDefinition; private Runspace _runspace; private System.Management.Automation.PowerShell _powerShell; private Job _job = null; private bool _asyncJobStop; private bool _allowSetShouldExit; private PSHost _host; private const string AllowHostSetShouldExit = "AllowSetShouldExitFromRemote"; private StatusInfo _statusInfo; #endregion #region Public Properties /// <summary> /// ScheduledJobDefinition. /// </summary> public ScheduledJobDefinition Definition { get { return _jobDefinition; } internal set { _jobDefinition = value; } } /// <summary> /// Location of job being run. /// </summary> public override string Location { get { return Status.Location; } } /// <summary> /// Status Message associated with the Job. /// </summary> public override string StatusMessage { get { return Status.StatusMessage; } } /// <summary> /// Indicates whether more data is available from Job. /// </summary> public override bool HasMoreData { get { return (_job != null) ? _job.HasMoreData : (Output.Count > 0 || Error.Count > 0 || Warning.Count > 0 || Verbose.Count > 0 || Progress.Count > 0 || Debug.Count > 0 || Information.Count > 0 ); } } /// <summary> /// Job command string. /// </summary> public new string Command { get { return Status.Command; } } /// <summary> /// Internal property indicating whether a SetShouldExit is honored /// while running the scheduled job script. /// </summary> internal bool AllowSetShouldExit { get { return _allowSetShouldExit; } set { _allowSetShouldExit = value; } } #endregion #region Constructors /// <summary> /// Constructor. /// </summary> /// <param name="command">Job command string for display.</param> /// <param name="name">Name of job.</param> /// <param name="jobDefinition">ScheduledJobDefinition defining job to run.</param> public ScheduledJob( string command, string name, ScheduledJobDefinition jobDefinition) : base(command, name) { if (command == null) { throw new PSArgumentNullException("command"); } if (name == null) { throw new PSArgumentNullException("name"); } if (jobDefinition == null) { throw new PSArgumentNullException("jobDefinition"); } _jobDefinition = jobDefinition; PSJobTypeName = ScheduledJobSourceAdapter.AdapterTypeName; } #endregion #region Public Overrides /// <summary> /// Starts a job as defined by the contained ScheduledJobDefinition object. /// </summary> public override void StartJob() { lock (SyncRoot) { if (_job != null && !IsFinishedState(_job.JobStateInfo.State)) { string msg = StringUtil.Format(ScheduledJobErrorStrings.JobAlreadyRunning, _jobDefinition.Name); throw new PSInvalidOperationException(msg); } _statusInfo = null; _asyncJobStop = false; PSBeginTime = DateTime.Now; if (_powerShell == null) { InitialSessionState iss = InitialSessionState.CreateDefault2(); iss.Commands.Clear(); iss.Formats.Clear(); iss.Commands.Add( new SessionStateCmdletEntry("Start-Job", typeof(Microsoft.PowerShell.Commands.StartJobCommand), null)); // Get the default host from the default runspace. _host = GetDefaultHost(); _runspace = RunspaceFactory.CreateRunspace(_host, iss); _runspace.Open(); _powerShell = System.Management.Automation.PowerShell.Create(); _powerShell.Runspace = _runspace; // Indicate SetShouldExit to host. AddSetShouldExitToHost(); } else { _powerShell.Commands.Clear(); } _job = StartJobCommand(_powerShell); _job.StateChanged += new EventHandler<JobStateEventArgs>(HandleJobStateChanged); SetJobState(_job.JobStateInfo.State); // Add all child jobs to this object's list so that // the user and Receive-Job can retrieve results. foreach (Job childJob in _job.ChildJobs) { this.ChildJobs.Add(childJob); } // Add this job to the local repository. ScheduledJobSourceAdapter.AddToRepository(this); } } /// <summary> /// Start job asynchronously. /// </summary> public override void StartJobAsync() { // StartJob(); throw new PSNotSupportedException(); } /// <summary> /// Stop the job. /// </summary> public override void StopJob() { Job job; JobState state; lock (SyncRoot) { job = _job; state = Status.State; _asyncJobStop = false; } if (IsFinishedState(state)) { return; } if (job == null) { // Set job state to failed so that it can be removed from the // cache using Remove-Job. SetJobState(JobState.Failed); } else { job.StopJob(); } } /// <summary> /// Stop the job asynchronously. /// </summary> public override void StopJobAsync() { Job job; JobState state; lock (SyncRoot) { job = _job; state = Status.State; _asyncJobStop = true; } if (IsFinishedState(state)) { return; } if (job == null) { // Set job state to failed so that it can be removed from the // cache using Remove-Job. SetJobState(JobState.Failed); HandleJobStateChanged(this, new JobStateEventArgs( new JobStateInfo(JobState.Failed))); } else { job.StopJob(); } } /// <summary> /// SuspendJob. /// </summary> public override void SuspendJob() { throw new PSNotSupportedException(); } /// <summary> /// SuspendJobAsync. /// </summary> public override void SuspendJobAsync() { throw new PSNotSupportedException(); } /// <summary> /// ResumeJob. /// </summary> public override void ResumeJob() { throw new PSNotSupportedException(); } /// <summary> /// ResumeJobAsync. /// </summary> public override void ResumeJobAsync() { throw new PSNotSupportedException(); } /// <summary> /// UnblockJob. /// </summary> public override void UnblockJob() { throw new PSNotSupportedException(); } /// <summary> /// UnblockJobAsync. /// </summary> public override void UnblockJobAsync() { throw new PSNotSupportedException(); } /// <summary> /// StopJob. /// </summary> /// <param name="force"></param> /// <param name="reason"></param> public override void StopJob(bool force, string reason) { throw new PSNotSupportedException(); } /// <summary> /// StopJobAsync. /// </summary> /// <param name="force"></param> /// <param name="reason"></param> public override void StopJobAsync(bool force, string reason) { throw new PSNotSupportedException(); } /// <summary> /// SuspendJob. /// </summary> /// <param name="force"></param> /// <param name="reason"></param> public override void SuspendJob(bool force, string reason) { throw new PSNotSupportedException(); } /// <summary> /// SuspendJobAsync. /// </summary> /// <param name="force"></param> /// <param name="reason"></param> public override void SuspendJobAsync(bool force, string reason) { throw new PSNotSupportedException(); } #endregion #region Implementation of ISerializable /// <summary> /// Deserialize constructor. /// </summary> /// <param name="info">SerializationInfo.</param> /// <param name="context">StreamingContext.</param> private ScheduledJob( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentNullException("info"); } DeserializeStatusInfo(info); DeserializeResultsInfo(info); PSJobTypeName = ScheduledJobSourceAdapter.AdapterTypeName; } /// <summary> /// Serialize method. /// </summary> /// <param name="info">SerializationInfo.</param> /// <param name="context">StreamingContext.</param> public void GetObjectData( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentException("info"); } SerializeStatusInfo(info); SerializeResultsInfo(info); } private void SerializeStatusInfo(SerializationInfo info) { StatusInfo statusInfo = new StatusInfo( InstanceId, Name, Location, Command, StatusMessage, (_job != null) ? _job.JobStateInfo.State : JobStateInfo.State, HasMoreData, PSBeginTime, PSEndTime, _jobDefinition); info.AddValue("StatusInfo", statusInfo); } private void SerializeResultsInfo(SerializationInfo info) { // All other job information is in the child jobs. Collection<PSObject> output = new Collection<PSObject>(); Collection<ErrorRecord> error = new Collection<ErrorRecord>(); Collection<WarningRecord> warning = new Collection<WarningRecord>(); Collection<VerboseRecord> verbose = new Collection<VerboseRecord>(); Collection<ProgressRecord> progress = new Collection<ProgressRecord>(); Collection<DebugRecord> debug = new Collection<DebugRecord>(); Collection<InformationRecord> information = new Collection<InformationRecord>(); if (_job != null) { // Collect data from "live" job. if (JobStateInfo.Reason != null) { error.Add(new ErrorRecord(JobStateInfo.Reason, "ScheduledJobFailedState", ErrorCategory.InvalidResult, null)); } foreach (var item in _job.Error) { error.Add(item); } foreach (Job childJob in ChildJobs) { if (childJob.JobStateInfo.Reason != null) { error.Add(new ErrorRecord(childJob.JobStateInfo.Reason, "ScheduledJobFailedState", ErrorCategory.InvalidResult, null)); } foreach (var item in childJob.Output) { output.Add(item); } foreach (var item in childJob.Error) { error.Add(item); } foreach (var item in childJob.Warning) { warning.Add(item); } foreach (var item in childJob.Verbose) { verbose.Add(item); } foreach (var item in childJob.Progress) { progress.Add(item); } foreach (var item in childJob.Debug) { debug.Add(item); } foreach (var item in childJob.Information) { information.Add(item); } } } else { // Collect data from object collections. foreach (var item in Output) { // Wrap the base object in a new PSObject. This is necessary because the // source deserialized PSObject doesn't serialize again correctly and breaks // PS F&O. Not sure if this is a PSObject serialization bug or not. output.Add(new PSObject(item.BaseObject)); } foreach (var item in Error) { error.Add(item); } foreach (var item in Warning) { warning.Add(item); } foreach (var item in Verbose) { verbose.Add(item); } foreach (var item in Progress) { progress.Add(item); } foreach (var item in Debug) { debug.Add(item); } foreach (var item in Information) { information.Add(item); } } ResultsInfo resultsInfo = new ResultsInfo( output, error, warning, verbose, progress, debug, information); info.AddValue("ResultsInfo", resultsInfo); } private void DeserializeStatusInfo(SerializationInfo info) { StatusInfo statusInfo = (StatusInfo)info.GetValue("StatusInfo", typeof(StatusInfo)); Name = statusInfo.Name; PSBeginTime = statusInfo.StartTime; PSEndTime = statusInfo.StopTime; _jobDefinition = statusInfo.Definition; SetJobState(statusInfo.State, null); lock (SyncRoot) { _statusInfo = statusInfo; } } private void DeserializeResultsInfo(SerializationInfo info) { ResultsInfo resultsInfo = (ResultsInfo)info.GetValue("ResultsInfo", typeof(ResultsInfo)); // Output CopyOutput(resultsInfo.Output); // Error CopyError(resultsInfo.Error); // Warning CopyWarning(resultsInfo.Warning); // Verbose CopyVerbose(resultsInfo.Verbose); // Progress CopyProgress(resultsInfo.Progress); // Debug CopyDebug(resultsInfo.Debug); // Information CopyInformation(resultsInfo.Information); } #endregion #region Internal Methods /// <summary> /// Method to update a ScheduledJob based on new state and /// result data from a provided Job. /// </summary> /// <param name="fromJob">ScheduledJob to update from.</param> internal void Update(ScheduledJob fromJob) { // We do not update "live" jobs. if (_job != null || fromJob == null) { return; } // // Update status. // PSEndTime = fromJob.PSEndTime; JobState state = fromJob.JobStateInfo.State; if (Status.State != state) { SetJobState(state, null); } lock (SyncRoot) { _statusInfo = new StatusInfo( fromJob.InstanceId, fromJob.Name, fromJob.Location, fromJob.Command, fromJob.StatusMessage, state, fromJob.HasMoreData, fromJob.PSBeginTime, fromJob.PSEndTime, fromJob._jobDefinition); } // // Update results. // CopyOutput(fromJob.Output); CopyError(fromJob.Error); CopyWarning(fromJob.Warning); CopyVerbose(fromJob.Verbose); CopyProgress(fromJob.Progress); CopyDebug(fromJob.Debug); CopyInformation(fromJob.Information); } #endregion #region Private Methods private System.Management.Automation.Host.PSHost GetDefaultHost() { System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace).AddScript("$host"); Collection<System.Management.Automation.Host.PSHost> hosts = ps.Invoke<System.Management.Automation.Host.PSHost>(); if (hosts == null || hosts.Count == 0) { System.Diagnostics.Debug.Assert(false, "Current runspace should always return default host."); return null; } return hosts[0]; } private Job StartJobCommand(System.Management.Automation.PowerShell powerShell) { Job job = null; // Use PowerShell Start-Job cmdlet to run job. powerShell.AddCommand("Start-Job"); powerShell.AddParameter("Name", _jobDefinition.Name); // Add job parameters from the JobInvocationInfo object. CommandParameterCollection parameters = _jobDefinition.InvocationInfo.Parameters[0]; foreach (CommandParameter parameter in parameters) { switch (parameter.Name) { case "ScriptBlock": powerShell.AddParameter("ScriptBlock", parameter.Value as ScriptBlock); break; case "FilePath": powerShell.AddParameter("FilePath", parameter.Value as string); break; case "RunAs32": powerShell.AddParameter("RunAs32", (bool)parameter.Value); break; case "Authentication": powerShell.AddParameter("Authentication", (AuthenticationMechanism)parameter.Value); break; case "InitializationScript": powerShell.AddParameter("InitializationScript", parameter.Value as ScriptBlock); break; case "ArgumentList": powerShell.AddParameter("ArgumentList", parameter.Value as object[]); break; } } // Start the job. Collection<PSObject> rtn = powerShell.Invoke(); if (rtn != null && rtn.Count == 1) { job = rtn[0].BaseObject as Job; } return job; } private void HandleJobStateChanged(object sender, JobStateEventArgs e) { SetJobState(e.JobStateInfo.State); if (IsFinishedState(e.JobStateInfo.State)) { PSEndTime = DateTime.Now; // Dispose the PowerShell and Runspace objects. System.Management.Automation.PowerShell disposePowerShell = null; Runspace disposeRunspace = null; lock (SyncRoot) { if (_job != null && IsFinishedState(_job.JobStateInfo.State)) { disposePowerShell = _powerShell; _powerShell = null; disposeRunspace = _runspace; _runspace = null; } } if (disposePowerShell != null) { disposePowerShell.Dispose(); } if (disposeRunspace != null) { disposeRunspace.Dispose(); } // Raise async job stopped event, if needed. if (_asyncJobStop) { _asyncJobStop = false; OnStopJobCompleted(new AsyncCompletedEventArgs(null, false, null)); } // Remove AllowSetShouldExit from host. RemoveSetShouldExitFromHost(); } } internal bool IsFinishedState(JobState state) { return (state == JobState.Completed || state == JobState.Failed || state == JobState.Stopped); } private StatusInfo Status { get { StatusInfo statusInfo; lock (SyncRoot) { if (_statusInfo != null) { // Pass back static status. statusInfo = _statusInfo; } else if (_job != null) { // Create current job status. statusInfo = new StatusInfo( _job.InstanceId, _job.Name, _job.Location, _job.Command, _job.StatusMessage, _job.JobStateInfo.State, _job.HasMoreData, PSBeginTime, PSEndTime, _jobDefinition); } else { // Create default static empty status. _statusInfo = new StatusInfo( Guid.Empty, string.Empty, string.Empty, string.Empty, string.Empty, JobState.NotStarted, false, PSBeginTime, PSEndTime, _jobDefinition); statusInfo = _statusInfo; } } return statusInfo; } } private void CopyOutput(ICollection<PSObject> fromOutput) { PSDataCollection<PSObject> output = CopyResults<PSObject>(fromOutput); if (output != null) { try { Output = output; } catch (InvalidJobStateException) { } } } private void CopyError(ICollection<ErrorRecord> fromError) { PSDataCollection<ErrorRecord> error = CopyResults<ErrorRecord>(fromError); if (error != null) { try { Error = error; } catch (InvalidJobStateException) { } } } private void CopyWarning(ICollection<WarningRecord> fromWarning) { PSDataCollection<WarningRecord> warning = CopyResults<WarningRecord>(fromWarning); if (warning != null) { try { Warning = warning; } catch (InvalidJobStateException) { } } } private void CopyVerbose(ICollection<VerboseRecord> fromVerbose) { PSDataCollection<VerboseRecord> verbose = CopyResults<VerboseRecord>(fromVerbose); if (verbose != null) { try { Verbose = verbose; } catch (InvalidJobStateException) { } } } private void CopyProgress(ICollection<ProgressRecord> fromProgress) { PSDataCollection<ProgressRecord> progress = CopyResults<ProgressRecord>(fromProgress); if (progress != null) { try { Progress = progress; } catch (InvalidJobStateException) { } } } private void CopyDebug(ICollection<DebugRecord> fromDebug) { PSDataCollection<DebugRecord> debug = CopyResults<DebugRecord>(fromDebug); if (debug != null) { try { Debug = debug; } catch (InvalidJobStateException) { } } } private void CopyInformation(ICollection<InformationRecord> fromInformation) { PSDataCollection<InformationRecord> information = CopyResults<InformationRecord>(fromInformation); if (information != null) { try { Information = information; } catch (InvalidJobStateException) { } } } private PSDataCollection<T> CopyResults<T>(ICollection<T> fromResults) { if (fromResults != null && fromResults.Count > 0) { PSDataCollection<T> returnResults = new PSDataCollection<T>(); foreach (var item in fromResults) { returnResults.Add(item); } return returnResults; } return null; } private void AddSetShouldExitToHost() { if (!_allowSetShouldExit || _host == null) { return; } PSObject hostPrivateData = _host.PrivateData as PSObject; if (hostPrivateData != null) { // Adds or replaces. hostPrivateData.Properties.Add(new PSNoteProperty(AllowHostSetShouldExit, true)); } } private void RemoveSetShouldExitFromHost() { if (!_allowSetShouldExit || _host == null) { return; } PSObject hostPrivateData = _host.PrivateData as PSObject; if (hostPrivateData != null) { // Removes if exists. hostPrivateData.Properties.Remove(AllowHostSetShouldExit); } } #endregion #region Private ResultsInfo class private sealed class ResultsInfo : ISerializable { // Private Members private Collection<PSObject> _output; private Collection<ErrorRecord> _error; private Collection<WarningRecord> _warning; private Collection<VerboseRecord> _verbose; private Collection<ProgressRecord> _progress; private Collection<DebugRecord> _debug; private Collection<InformationRecord> _information; // Properties internal Collection<PSObject> Output { get { return _output; } } internal Collection<ErrorRecord> Error { get { return _error; } } internal Collection<WarningRecord> Warning { get { return _warning; } } internal Collection<VerboseRecord> Verbose { get { return _verbose; } } internal Collection<ProgressRecord> Progress { get { return _progress; } } internal Collection<DebugRecord> Debug { get { return _debug; } } internal Collection<InformationRecord> Information { get { return _information; } } // Constructors internal ResultsInfo( Collection<PSObject> output, Collection<ErrorRecord> error, Collection<WarningRecord> warning, Collection<VerboseRecord> verbose, Collection<ProgressRecord> progress, Collection<DebugRecord> debug, Collection<InformationRecord> information ) { if (output == null) { throw new PSArgumentNullException("output"); } if (error == null) { throw new PSArgumentNullException("error"); } if (warning == null) { throw new PSArgumentNullException("warning"); } if (verbose == null) { throw new PSArgumentNullException("verbose"); } if (progress == null) { throw new PSArgumentNullException("progress"); } if (debug == null) { throw new PSArgumentNullException("debug"); } if (information == null) { throw new PSArgumentNullException("information"); } _output = output; _error = error; _warning = warning; _verbose = verbose; _progress = progress; _debug = debug; _information = information; } // ISerializable private ResultsInfo( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentNullException("info"); } _output = (Collection<PSObject>)info.GetValue("Results_Output", typeof(Collection<PSObject>)); _error = (Collection<ErrorRecord>)info.GetValue("Results_Error", typeof(Collection<ErrorRecord>)); _warning = (Collection<WarningRecord>)info.GetValue("Results_Warning", typeof(Collection<WarningRecord>)); _verbose = (Collection<VerboseRecord>)info.GetValue("Results_Verbose", typeof(Collection<VerboseRecord>)); _progress = (Collection<ProgressRecord>)info.GetValue("Results_Progress", typeof(Collection<ProgressRecord>)); _debug = (Collection<DebugRecord>)info.GetValue("Results_Debug", typeof(Collection<DebugRecord>)); try { _information = (Collection<InformationRecord>)info.GetValue("Results_Information", typeof(Collection<InformationRecord>)); } catch(SerializationException) { // The job might not have the info stream. Ignore. _information = new Collection<InformationRecord>(); } } public void GetObjectData( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentException("info"); } info.AddValue("Results_Output", _output); info.AddValue("Results_Error", _error); info.AddValue("Results_Warning", _warning); info.AddValue("Results_Verbose", _verbose); info.AddValue("Results_Progress", _progress); info.AddValue("Results_Debug", _debug); info.AddValue("Results_Information", _information); } } #endregion } #region Internal StatusInfo Class internal class StatusInfo : ISerializable { // Private Members private Guid _instanceId; private string _name; private string _location; private string _command; private string _statusMessage; private JobState _jobState; private bool _hasMoreData; private DateTime? _startTime; private DateTime? _stopTime; private ScheduledJobDefinition _definition; // Properties internal Guid InstanceId { get { return _instanceId; } } internal string Name { get { return _name; } } internal string Location { get { return _location; } } internal string Command { get { return _command; } } internal string StatusMessage { get { return _statusMessage; } } internal JobState State { get { return _jobState; } } internal bool HasMoreData { get { return _hasMoreData; } } internal DateTime? StartTime { get { return _startTime; } } internal DateTime? StopTime { get { return _stopTime; } } internal ScheduledJobDefinition Definition { get { return _definition; } } // Constructors internal StatusInfo( Guid instanceId, string name, string location, string command, string statusMessage, JobState jobState, bool hasMoreData, DateTime? startTime, DateTime? stopTime, ScheduledJobDefinition definition) { if (definition == null) { throw new PSArgumentNullException("definition"); } _instanceId = instanceId; _name = name; _location = location; _command = command; _statusMessage = statusMessage; _jobState = jobState; _hasMoreData = hasMoreData; _startTime = startTime; _stopTime = stopTime; _definition = definition; } // ISerializable private StatusInfo( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentNullException("info"); } _instanceId = Guid.Parse(info.GetString("Status_InstanceId")); _name = info.GetString("Status_Name"); _location = info.GetString("Status_Location"); _command = info.GetString("Status_Command"); _statusMessage = info.GetString("Status_Message"); _jobState = (JobState)info.GetValue("Status_State", typeof(JobState)); _hasMoreData = info.GetBoolean("Status_MoreData"); _definition = (ScheduledJobDefinition)info.GetValue("Status_Definition", typeof(ScheduledJobDefinition)); DateTime startTime = info.GetDateTime("Status_StartTime"); if (startTime != DateTime.MinValue) { _startTime = startTime; } else { _startTime = null; } DateTime stopTime = info.GetDateTime("Status_StopTime"); if (stopTime != DateTime.MinValue) { _stopTime = stopTime; } else { _stopTime = null; } } public void GetObjectData( SerializationInfo info, StreamingContext context) { if (info == null) { throw new PSArgumentNullException("info"); } info.AddValue("Status_InstanceId", _instanceId); info.AddValue("Status_Name", _name); info.AddValue("Status_Location", _location); info.AddValue("Status_Command", _command); info.AddValue("Status_Message", _statusMessage); info.AddValue("Status_State", _jobState); info.AddValue("Status_MoreData", _hasMoreData); info.AddValue("Status_Definition", _definition); if (_startTime != null) { info.AddValue("Status_StartTime", _startTime); } else { info.AddValue("Status_StartTime", DateTime.MinValue); } if (_stopTime != null) { info.AddValue("Status_StopTime", _stopTime); } else { info.AddValue("Status_StopTime", DateTime.MinValue); } } } #endregion }