/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
namespace Microsoft.PowerShell.Cmdletization
{
///
/// Provides common code for processing session-based object models. The common code
/// Session, ThrottleLimit, AsJob parameters and delegates creation of jobs to derived classes.
///
/// Type that represents instances of objects from the wrapped object model
/// Type representing remote sessions
[SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
public abstract class SessionBasedCmdletAdapter : CmdletAdapter, IDisposable
where TObjectInstance : class
where TSession : class
{
internal SessionBasedCmdletAdapter()
{
}
#region Constants
private const string CIMJobType = "CimJob";
#endregion
#region IDisposable Members
private bool disposed;
///
/// Releases resources associated with this object
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases resources associated with this object
///
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (this.parentJob != null)
{
this.parentJob.Dispose();
this.parentJob = null;
}
}
disposed = true;
}
}
#endregion
#region Common parameters (AsJob, ThrottleLimit, Session)
///
/// Session to operate on
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
protected TSession[] Session
{
get
{
if (this.session == null)
{
this.session = new TSession[] {this.DefaultSession};
}
return this.session;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.session = value;
this.sessionWasSpecified = true;
}
}
private TSession[] session;
private bool sessionWasSpecified;
///
/// Whether to wrap and emit the whole operation as a background job
///
[Parameter]
public SwitchParameter AsJob
{
get { return this.asJob; }
set { this.asJob = value; }
}
private bool asJob;
///
/// Maximum number of remote connections that can remain active at any given time.
///
[Parameter]
public virtual int ThrottleLimit { get; set; }
#endregion Common CIM-related parameters
#region Abstract methods to be overriden in derived classes
///
/// Creates a object that performs a query against the wrapped object model.
///
/// Remote session to query
/// Query parameters
///
///
/// This method shouldn't do any processing or interact with the remote session.
/// Doing so will interfere with ThrottleLimit functionality.
///
///
/// (and other methods returning job results) will block to support throttling and flow-control.
/// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further procesing,
/// until calls from (and other methods returning job results) return.
///
///
internal abstract StartableJob CreateQueryJob(TSession session, QueryBuilder query);
private StartableJob DoCreateQueryJob(TSession sessionForJob, QueryBuilder query, Action actionAgainstResults)
{
StartableJob queryJob = this.CreateQueryJob(sessionForJob, query);
if (queryJob != null)
{
if (actionAgainstResults != null)
{
queryJob.SuppressOutputForwarding = true;
}
bool discardNonPipelineResults = (actionAgainstResults != null) || !this.AsJob.IsPresent;
HandleJobOutput(
queryJob,
sessionForJob,
discardNonPipelineResults,
actionAgainstResults == null
? (Action) null
: delegate(PSObject pso)
{
var objectInstance =
(TObjectInstance)
LanguagePrimitives.ConvertTo(pso, typeof (TObjectInstance),
CultureInfo.InvariantCulture);
actionAgainstResults(sessionForJob, objectInstance);
});
}
return queryJob;
}
///
/// Creates a object that invokes an instance method in the wrapped object model.
///
/// Remote session to invoke the method in
/// The object on which to invoke the method
/// Method invocation details
/// true if successful method invocations should emit downstream the being operated on
///
///
/// This method shouldn't do any processing or interact with the remote session.
/// Doing so will interfere with ThrottleLimit functionality.
///
///
/// (and other methods returning job results) will block to support throttling and flow-control.
/// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further procesing,
/// until calls from (and other methods returning job results) return.
///
///
internal abstract StartableJob CreateInstanceMethodInvocationJob(TSession session, TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru);
private StartableJob DoCreateInstanceMethodInvocationJob(TSession sessionForJob, TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru, bool asJob)
{
StartableJob methodInvocationJob = this.CreateInstanceMethodInvocationJob(sessionForJob, objectInstance, methodInvocationInfo, passThru);
if (methodInvocationJob != null)
{
bool discardNonPipelineResults = !asJob;
HandleJobOutput(
methodInvocationJob,
sessionForJob,
discardNonPipelineResults,
outputAction: null);
}
return methodInvocationJob;
}
///
/// Creates a object that invokes a static method in the wrapped object model.
///
/// Remote session to invoke the method in
/// Method invocation details
///
///
/// This method shouldn't do any processing or interact with the remote session.
/// Doing so will interfere with ThrottleLimit functionality.
///
///
/// (and other methods returning job results) will block to support throttling and flow-control.
/// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further procesing,
/// until calls from (and other methods returning job results) return.
///
///
internal abstract StartableJob CreateStaticMethodInvocationJob(TSession session, MethodInvocationInfo methodInvocationInfo);
private StartableJob DoCreateStaticMethodInvocationJob(TSession sessionForJob, MethodInvocationInfo methodInvocationInfo)
{
StartableJob methodInvocationJob = this.CreateStaticMethodInvocationJob(sessionForJob, methodInvocationInfo);
if (methodInvocationJob != null)
{
bool discardNonPipelineResults = !this.AsJob.IsPresent;
HandleJobOutput(
methodInvocationJob,
sessionForJob,
discardNonPipelineResults,
outputAction: null);
}
return methodInvocationJob;
}
private void HandleJobOutput(Job job, TSession sessionForJob, bool discardNonPipelineResults, Action outputAction)
{
Action processOutput =
delegate(PSObject pso)
{
if (pso == null)
{
return;
}
if (outputAction != null)
{
outputAction(pso);
}
};
job.Output.DataAdded +=
delegate(object sender, DataAddedEventArgs eventArgs)
{
var dataCollection = (PSDataCollection) sender;
if (discardNonPipelineResults)
{
foreach (PSObject pso in dataCollection.ReadAll())
{
// TODO/FIXME - need to make sure that the dataCollection will be throttled
// (i.e. it won't accept more items - it will block in Add method)
// until this processOutput call completes
processOutput(pso);
}
}
else
{
PSObject pso = dataCollection[eventArgs.Index];
processOutput(pso);
}
};
if (discardNonPipelineResults)
{
DiscardJobOutputs(job, JobOutputs.NonPipelineResults & (~JobOutputs.Output));
}
}
internal virtual TSession GetSessionOfOriginFromInstance(TObjectInstance instance)
{
return null;
}
///
/// Returns default sessions to use when the user doesn't specify the -Session cmdlet parameter.
///
/// Default sessions to use when the user doesn't specify the -Session cmdlet parameter
protected abstract TSession DefaultSession { get; }
///
/// A new job name to use for the parent job that handles throttling of the child jobs that actually perform querying and method invocation.
///
protected abstract string GenerateParentJobName();
#endregion
#region Helper methods
private static void DiscardJobOutputs(PSDataCollection psDataCollection)
{
psDataCollection.DataAdded +=
delegate(object sender, DataAddedEventArgs e)
{
var localDataCollection = (PSDataCollection)sender;
localDataCollection.Clear();
};
}
[Flags]
private enum JobOutputs
{
Output = 0x1,
Error = 0x2,
Warning = 0x4,
Verbose = 0x8,
Debug = 0x10,
Progress = 0x20,
Results = 0x40,
NonPipelineResults = Output | Error | Warning | Verbose | Debug | Progress,
PipelineResults = Results,
}
private static void DiscardJobOutputs(Job job, JobOutputs jobOutputsToDiscard)
{
if (JobOutputs.Output == (jobOutputsToDiscard & JobOutputs.Output))
{
DiscardJobOutputs(job.Output);
}
if (JobOutputs.Error == (jobOutputsToDiscard & JobOutputs.Error))
{
DiscardJobOutputs(job.Error);
}
if (JobOutputs.Warning == (jobOutputsToDiscard & JobOutputs.Warning))
{
DiscardJobOutputs(job.Warning);
}
if (JobOutputs.Verbose == (jobOutputsToDiscard & JobOutputs.Verbose))
{
DiscardJobOutputs(job.Verbose);
}
if (JobOutputs.Debug == (jobOutputsToDiscard & JobOutputs.Debug))
{
DiscardJobOutputs(job.Debug);
}
if (JobOutputs.Progress == (jobOutputsToDiscard & JobOutputs.Progress))
{
DiscardJobOutputs(job.Progress);
}
if (JobOutputs.Results == (jobOutputsToDiscard & JobOutputs.Results))
{
DiscardJobOutputs(job.Results);
}
}
#endregion Helper methods
#region Implementation of ObjectModelWrapper functionality
private ThrottlingJob parentJob;
///
/// Queries for object instances in the object model.
///
/// Query parameters
/// A lazy evaluated collection of object instances
public override void ProcessRecord(QueryBuilder query)
{
this.parentJob.DisableFlowControlForPendingCmdletActionsQueue();
foreach (TSession sessionForJob in this.GetSessionsToActAgainst(query))
{
StartableJob childJob = this.DoCreateQueryJob(sessionForJob, query, actionAgainstResults: null);
if (childJob != null)
{
if (!this.AsJob.IsPresent)
{
this.parentJob.AddChildJobAndPotentiallyBlock(this.Cmdlet, childJob, ThrottlingJob.ChildJobFlags.None);
}
else
{
this.parentJob.AddChildJobWithoutBlocking(childJob, ThrottlingJob.ChildJobFlags.None);
}
}
}
}
///
/// Queries for instance and invokes an instance method
///
/// Query parameters
/// Method invocation details
/// true if successful method invocations should emit downstream the object instance being operated on
public override void ProcessRecord(QueryBuilder query, MethodInvocationInfo methodInvocationInfo, bool passThru)
{
this.parentJob.DisableFlowControlForPendingJobsQueue();
ThrottlingJob closureOverParentJob = this.parentJob;
SwitchParameter closureOverAsJob = this.AsJob;
foreach (TSession sessionForJob in this.GetSessionsToActAgainst(query))
{
StartableJob queryJob = this.DoCreateQueryJob(
sessionForJob,
query,
delegate(TSession sessionForMethodInvocationJob, TObjectInstance objectInstance)
{
StartableJob methodInvocationJob = this.DoCreateInstanceMethodInvocationJob(
sessionForMethodInvocationJob,
objectInstance,
methodInvocationInfo,
passThru,
closureOverAsJob.IsPresent);
if (methodInvocationJob != null)
{
closureOverParentJob.AddChildJobAndPotentiallyBlock(methodInvocationJob, ThrottlingJob.ChildJobFlags.None);
}
});
if (queryJob != null)
{
if (!this.AsJob.IsPresent)
{
this.parentJob.AddChildJobAndPotentiallyBlock(this.Cmdlet, queryJob, ThrottlingJob.ChildJobFlags.CreatesChildJobs);
}
else
{
this.parentJob.AddChildJobWithoutBlocking(queryJob, ThrottlingJob.ChildJobFlags.CreatesChildJobs);
}
}
}
}
private IEnumerable GetSessionsToActAgainst(TObjectInstance objectInstance)
{
if (this.sessionWasSpecified)
{
return this.Session;
}
TSession associatedSession = this.GetSessionOfOriginFromInstance(objectInstance);
if (associatedSession != null)
{
return new[] {associatedSession};
}
return new[] { this.GetImpliedSession() };
}
private TSession GetSessionAssociatedWithPipelineObject()
{
object inputVariableValue = this.Cmdlet.Context.GetVariableValue(SpecialVariables.InputVarPath, null);
if (inputVariableValue == null)
{
return null;
}
IEnumerable inputEnumerable = LanguagePrimitives.GetEnumerable(inputVariableValue);
if (inputEnumerable == null)
{
return null;
}
List