Types/PSAdapter.Template/DotNetAdapter.cs

namespace PSAdapter
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Management.Automation;
    using Microsoft.PowerShell.Cmdletization;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Collections.Specialized;
    using System.Management.Automation.Language;
    using System.Management.Automation.Runspaces;

    public class PSDotNetQueryFilter
    {
        public enum QueryFilterType
        {
            Include,
            Exclude,
            Minimum,
            Maximum
        }
        public QueryFilterType FilterType;
        public string PropertyName;
        public IEnumerable Values;
        public bool wildcardsEnabled;
    }

    public class PSDotNetQueryBuilder : QueryBuilder
    {
        Collection<PSDotNetQueryFilter> filters;
 
        Type type;
 
        Type Type
        {
            get
            {
                return type;
            }
        }
        
        public PSDotNetQueryBuilder(Type type)
        {
            this.type = type;
            filters = new Collection<PSDotNetQueryFilter>();
        }
       
        public override void ExcludeByProperty(string propertyName, System.Collections.IEnumerable excludedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) {
            filters.Add(
                new PSDotNetQueryFilter()
                {
                    PropertyName = propertyName,
                    Values = excludedPropertyValues,
                    wildcardsEnabled = wildcardsEnabled,
                    FilterType = PSDotNetQueryFilter.QueryFilterType.Exclude
                }
            );
        }
 
        public override void FilterByMaxPropertyValue(string propertyName, object maxPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch)
        {
            filters.Add(
                new PSDotNetQueryFilter()
                {
                    PropertyName = propertyName,
                    Values = new Object[]{ maxPropertyValue } ,
                    FilterType = PSDotNetQueryFilter.QueryFilterType.Maximum
                }
            );
        }
 
        public override void FilterByMinPropertyValue(string propertyName, object minPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch)
        {
            filters.Add(
                new PSDotNetQueryFilter()
                {
                    PropertyName = propertyName,
                    Values = new Object[]{ minPropertyValue } ,
                    FilterType = PSDotNetQueryFilter.QueryFilterType.Minimum
                }
            );
        }
 
        public override void FilterByProperty(string propertyName, System.Collections.IEnumerable propertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch )
        {
            filters.Add(
                new PSDotNetQueryFilter()
                {
                    PropertyName = propertyName,
                    Values = propertyValues,
                    wildcardsEnabled = wildcardsEnabled,
                    FilterType = PSDotNetQueryFilter.QueryFilterType.Include
                }
            );
            PSDotNetQueryFilter qf = new PSDotNetQueryFilter();
        }

        public override void FilterByAssociatedInstance(object associatedInstance, string associationName, string sourceRole, string resultRole, BehaviorOnNoMatch behaviorOnNoMatch)
        {
            
        }
        
 
        public bool MatchesFilters(object value, PSCmdlet cmdlet)
        {
            cmdlet.WriteDebug(String.Format("Confirming match: {0}. {1} Filters to process", value, filters.Count));
 
            int filterCount = 1;
            foreach (PSDotNetQueryFilter filter in filters)
            {
                cmdlet.WriteDebug(String.Format("Processing filter #{0}. Type: {1}", filterCount, filter.FilterType));
                filterCount++;
                PropertyInfo pi = value.GetType().GetProperty(filter.PropertyName,
                    BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Instance);
                cmdlet.WriteDebug(String.Format("Property Found {0}", pi));
                if (pi != null)
                {
                    bool excluded = false;
                    string propertyValueAsString;
                    object propValue = pi.GetValue(value, null);
                    propertyValueAsString = propValue.ToString();
                    switch (filter.FilterType)
                    {
                        case PSDotNetQueryFilter.QueryFilterType.Exclude:
                            cmdlet.WriteDebug(String.Format("Processing Exclude Filter: Value ( {0} ) : Wildcards Enabled ( {1} ) : Possible Values ( {2} )", propValue, filter.wildcardsEnabled, filter.Values));
                            if (filter.wildcardsEnabled)
                            {
                                foreach (string exV in filter.Values)
                                {
                                    WildcardPattern wp = new WildcardPattern(exV,WildcardOptions.CultureInvariant| WildcardOptions.IgnoreCase);
                                    if (propValue != null)
                                    {
                                        propertyValueAsString = propValue.ToString();
                                        if (wp.IsMatch(propertyValueAsString))
                                        {
                                            excluded = true;
                                            break;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                foreach (object exV in filter.Values)
                                {
                                    if (propValue != null && propValue == exV)
                                    {
                                        excluded = true;
                                    }
                                }

                            }
                            break;
                        case PSDotNetQueryFilter.QueryFilterType.Include:
                            cmdlet.WriteDebug(String.Format("Processing Include Filter: Value ( {0} ) : Wildcards Enabled ( {1} ) : Possible Values ( {2} )", propValue, filter.wildcardsEnabled, filter.Values));
                            excluded = true;
                            if (filter.wildcardsEnabled)
                            {
                                foreach (string exV in filter.Values)
                                {
                                    WildcardPattern wp = new WildcardPattern(exV, WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase);
                                    if (propValue != null)
                                    {
                                        propertyValueAsString = propValue.ToString();
                                        if (wp.IsMatch(propertyValueAsString))
                                        {
                                            excluded = false;
                                            break;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                foreach (object exV in filter.Values)
                                {
                                    if (propValue != null && propValue == exV)
                                    {
                                        excluded = false;
                                    }
                                }

                            }
                            break;
                        case PSDotNetQueryFilter.QueryFilterType.Maximum:
                            cmdlet.WriteDebug(String.Format("Processing Maximum Filter: Value ( {0} ) : Max Values ( {1} )", propValue, filter.Values));
                            excluded = true;
                            foreach (object exV in filter.Values)
                            {
                                if (exV is IComparable && propValue is IComparable)
                                {
                                    IComparable orignal = propValue as IComparable;
                                    IComparable comparable = exV as IComparable;
                                    if (orignal.CompareTo(comparable) <= 0)
                                    {
                                        excluded = false;
                                    }
                                }
                            }

                            break;
                        case PSDotNetQueryFilter.QueryFilterType.Minimum:
                            cmdlet.WriteDebug(String.Format("Processing Minimum Filter: Value ( {0} ) : Max Values ( {1} )", propValue, filter.Values));
                            excluded = true;
                            foreach (object exV in filter.Values)
                            {
                                if (exV is IComparable && propValue is IComparable)
                                {
                                    IComparable orignal = propValue as IComparable;
                                    IComparable comparable = exV as IComparable;
                                    if (orignal.CompareTo(comparable) >= 0)
                                    {
                                        excluded = false;
                                    }
                                }
                            }

                            break;

                    }
                    if (excluded) { return false; }
                }

            }
            return true;
        }
    }
 
 
    public class PSDotNetAdapter : CmdletAdapter<Object>
    {
        public DateTime InitializationTime = DateTime.Now;
        public bool StopRequested = false;
        public CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();

        public override QueryBuilder GetQueryBuilder() {
            return new PSDotNetQueryBuilder(Type.GetType(this.ClassName));;
        }

        public override void BeginProcessing() {
            this.Cmdlet.WriteDebug("Begin Processing");
            this.StopRequested = false;
        }
            
        public override void EndProcessing() {
            this.Cmdlet.WriteDebug("End Processing");
        }

        public override void StopProcessing() {
            this.Cmdlet.WriteDebug("Stop Processing");
            this.StopRequested = true;
            this.CancellationTokenSource.Cancel();
        }

        PSDataCollection<PSObject> OutputCollection = new PSDataCollection<PSObject>();

                
        public PSDotNetAdapter()
        {
            OutputCollection.DataAdded += (sender, e) => {
                this.Cmdlet.WriteObject(OutputCollection[e.Index], true);
            };
        }

        public ScriptBlock GetMethodScriptBlock(MethodInvocationInfo methodInvocationInfo) {
            try {
                string methodName = methodInvocationInfo.MethodName;
                if (methodInvocationInfo.MethodName.StartsWith('{') && methodInvocationInfo.MethodName.EndsWith('}')) {
                    return ScriptBlock.Create(methodInvocationInfo.MethodName.Substring(1, methodInvocationInfo.MethodName.Length - 2));
                }
                return null;
            } catch (Exception ex) {
                this.Cmdlet.WriteError(new ErrorRecord(ex, "PSDotNetAdapter.InvalidScriptBlock", ErrorCategory.InvalidOperation, methodInvocationInfo));
                return null;
            }
        }

        public string[] GetScriptParameterName(ScriptBlock scriptBlock) {
            List<string> parameterNames = new List<string>();
            scriptBlock.Ast.FindAll(
                (ast) => {
                    if (ast is ParameterAst) {
                        ParameterAst paramAst = ast as ParameterAst;
                        parameterNames.Add(paramAst.Name.VariablePath.UserPath.ToString());
                    }
                    return true;
                },
                false
            );
            return parameterNames.ToArray();
        }
 
        MemberInfo ResolveMethod(Type t, string methodName, MethodInvocationInfo methodInfo, out Object[] RealParameters)
        {
            MemberInfo realMethod = null;
            RealParameters = new Object[0];
            Regex isConstructor = new Regex(@"^:{1,2}(?>Constructor|New)$",RegexOptions.IgnoreCase);
            
            if (isConstructor.IsMatch(methodName)) {
                foreach (ConstructorInfo method in t.GetConstructors()) {
                    Collection<Object> realParameters = new Collection<object>();
                    bool anyParameterNotFound = false;
                    foreach (ParameterInfo pi in method.GetParameters())
                    {
                        foreach (MethodParameter mp in methodInfo.Parameters)
                        {
                            if (String.Compare(mp.Name, pi.Name, true) == 0)
                            {
                                realParameters.Add(mp.Value);
                                break;
                            } else {
                                anyParameterNotFound = true;
                                break;
                            }
                        }
                    }
                    if (! anyParameterNotFound) {
                        realMethod = method;
                        break;
                    }
                    RealParameters = new Object[realParameters.Count];
                    realParameters.CopyTo(RealParameters, 0);
                }
            } else {
                foreach (MethodInfo method in t.GetMethods())
                {
                    if (String.Compare(method.Name, methodName, true) == 0)
                    {
                        realMethod = method;
                        break;
                    }
                }
                if (realMethod != null)
                {
                    this.Cmdlet.WriteDebug(String.Format("Method match found. Method is {0}", realMethod.ToString()));
                    Collection<Object> realParameters = new Collection<object>();
 
                    ParameterInfo[] parameters =null;
                    if (realMethod is MethodInfo) {
                        parameters = ((MethodInfo)realMethod).GetParameters();
                    }
                    if (realMethod is ConstructorInfo) {
                        parameters = ((ConstructorInfo)realMethod).GetParameters();
                    }
                    if (parameters != null) {
                        foreach (ParameterInfo pi in parameters)
                        {
                            foreach (MethodParameter mp in methodInfo.Parameters)
                            {
                                this.Cmdlet.WriteDebug(String.Format("Comparing Parameter {0} to method parameter {1}", pi.Name, mp.Name));
                                if (String.Compare(mp.Name, pi.Name, true) == 0)
                                {
                                    this.Cmdlet.WriteDebug(String.Format("Adding Parameter {0} to method parameter {1}", pi.Name, mp.Name));
                                    realParameters.Add(mp.Value);
                                    break;
                                }
                                if (mp.ParameterType != null && pi.ParameterType != null && mp.ParameterType.IsAssignableFrom(pi.ParameterType))
                                {
                                    this.Cmdlet.WriteDebug(String.Format("Adding Parameter {0} to method parameter {1}", pi.Name, mp.Name));
                                    realParameters.Add(mp.Value);
                                    break;
                                }
                            }
                        }
                        RealParameters = new Object[realParameters.Count];
                        realParameters.CopyTo(RealParameters, 0);
                    } else {
                        RealParameters = new Object[0];
                    }
                }
                else
                {
                    RealParameters = new Object[0];
                }
            }
            return realMethod;
        }
 
        public override void ProcessRecord(QueryBuilder query)
        {
            this.Cmdlet.WriteDebug("Process Query");
            Collection<PSObject> results = GetInstances();
                         
            foreach (PSObject result in results)
            {
                this.Cmdlet.WriteDebug(String.Format("Processing Instance {0}", result.ImmediateBaseObject));
                if ((query as PSDotNetQueryBuilder).MatchesFilters(result.ImmediateBaseObject, this.Cmdlet))
                {
                    this.Cmdlet.WriteDebug(String.Format("Match found! {0}", result.ImmediateBaseObject));
                    this.Cmdlet.WriteObject(result, true);
                }
            }
        }
 
        public override void ProcessRecord(object objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru)
        {
            this.Cmdlet.WriteDebug("Process instance method");
            ScriptBlock methodScriptBlock = GetMethodScriptBlock(methodInvocationInfo);
            if (methodScriptBlock != null) {
                this.Cmdlet.WriteDebug($"Invoking method script: {methodScriptBlock}");
                this.Cmdlet.SessionState.PSVariable.Set("this", this);
                PowerShell psCmd = PowerShell.Create(RunspaceMode.CurrentRunspace).AddScript(methodScriptBlock.ToString());
                foreach (string parameterName in this.GetScriptParameterName(methodScriptBlock)) {
                    foreach (var methodParameter in methodInvocationInfo.Parameters) {
                        if (string.Compare(methodParameter.Name, parameterName, true) == 0) {
                            psCmd.AddParameter(parameterName, methodParameter.Value);
                        }
                    }
                }
                psCmd.AddArgument(methodInvocationInfo);
                PSDataCollection<object> inputCollection = new PSDataCollection<object>();
                inputCollection.Add(objectInstance);
                PSInvocationSettings settings = new PSInvocationSettings();
                psCmd.Invoke<object, PSObject>(inputCollection, OutputCollection, settings);
                return;
            }
            if (objectInstance == null) { return; }
            
            Type t = objectInstance.GetType();
            
            this.Cmdlet.WriteDebug(String.Format("Found Type {0} in Assembly {1}", this.ClassName, t.Assembly));
            Object[] realMethodParameters;
            MemberInfo realMethod = ResolveMethod(t, methodInvocationInfo.MethodName, methodInvocationInfo, out realMethodParameters);
            if (realMethod == null) {
                this.Cmdlet.WriteDebug(String.Format("Could not find {0} on type {1}", methodInvocationInfo.MethodName, this.ClassName));
            }

            try
            {
                Object result = null;
                if (realMethod is MethodInfo) {
                    result = (realMethod as MethodInfo).Invoke(objectInstance, realMethodParameters);
                } else if (realMethod is ConstructorInfo) {
                    result = (realMethod as ConstructorInfo).Invoke(objectInstance, realMethodParameters);
                }
                if (passThru)
                {
                    if (result != null) {
                        this.Cmdlet.WriteObject(objectInstance, false);
                    }
                }
                else
                {
                    if (result != null)
                    {
                        if (result is Task) {
                            (result as Task).Wait(CancellationTokenSource.Token);
                            Pipeline awaitResultPipeline = Runspace.DefaultRunspace.CreateNestedPipeline(@"
                            param($task) $task.Result
                        ", true);
                            awaitResultPipeline.Commands[0].Parameters.Add("this", this);
                            awaitResultPipeline.Commands[0].Parameters.Add("task", result);
                            foreach (PSObject awaitResult in awaitResultPipeline.Invoke()) {
                                this.Cmdlet.WriteObject(awaitResult, true);
                            }
                        } else {
                            this.Cmdlet.WriteObject(result, true);
                        }
                        
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null) {
                    this.Cmdlet.WriteError(new ErrorRecord(ex.InnerException, "PSDotNetAdapter.MethodInvocationError", ErrorCategory.InvalidOperation, objectInstance));
                } else {
                    this.Cmdlet.WriteError(new ErrorRecord(ex, "PSDotNetAdapter.MethodInvocationError", ErrorCategory.InvalidOperation, objectInstance));
                }
            }
        }
 
        public override void ProcessRecord(MethodInvocationInfo methodInvocationInfo)
        {
            this.Cmdlet.WriteDebug("Process Static Method");
            ScriptBlock methodScriptBlock = GetMethodScriptBlock(methodInvocationInfo);
            if (methodScriptBlock != null) {
                this.Cmdlet.WriteDebug("Invoking method script:" + methodScriptBlock.ToString());
                this.Cmdlet.SessionState.PSVariable.Set("this", this);
                PowerShell psCmd = PowerShell.Create(RunspaceMode.CurrentRunspace).AddScript(methodScriptBlock.ToString());
                psCmd.AddArgument(methodInvocationInfo);
                PSDataCollection<object> inputCollection = new PSDataCollection<object>();
                PSInvocationSettings settings = new PSInvocationSettings();
                psCmd.Invoke<object, PSObject>(inputCollection, OutputCollection, settings);
                return;
            }
            string instanceScript = String.Empty;
            
            foreach (var kv in this.PrivateData) {
                if (kv.Key.ToLower().StartsWith(this.Cmdlet.MyInvocation.InvocationName.ToLower())) {
                    if (kv.Key.Substring(this.Cmdlet.MyInvocation.InvocationName.Length).ToLower() == "_instance") {
                        instanceScript = kv.Value;
                    }
                    if (kv.Key.Substring(this.Cmdlet.MyInvocation.InvocationName.Length).ToLower() == "_instanceparameter") {
                        if (this.Cmdlet.MyInvocation.BoundParameters.ContainsKey(kv.Value)) {
                            ProcessRecord(this.Cmdlet.MyInvocation.BoundParameters[kv.Value], methodInvocationInfo, false);
                            return;
                        }
                    }
                    this.Cmdlet.WriteDebug(kv.Key + " : " + kv.Value);
                    
                }
            }

            if (! String.IsNullOrEmpty(instanceScript)) {
                this.Cmdlet.WriteDebug("Running instance script" + instanceScript);
                Pipeline pipeline = Runspace.DefaultRunspace.CreateNestedPipeline(instanceScript, false);
                Collection<PSObject> results = pipeline.Invoke();
                foreach (PSObject result in results) {
                    ProcessRecord(result, methodInvocationInfo, false);
                }
                
                pipeline.Dispose();
                return;
            }

            Type t = null;
            if (LanguagePrimitives.TryConvertTo<Type>(this.ClassName, out t))
            {
                this.Cmdlet.WriteDebug(String.Format("Found Type {0} in Assembly {1}", this.ClassName, t.Assembly));
                Object[] realMethodParameters;
                MemberInfo realMethod = ResolveMethod(t, methodInvocationInfo.MethodName, methodInvocationInfo, out realMethodParameters);
                if (realMethod == null) { return; }
                this.Cmdlet.WriteDebug(String.Format("Method Found {0}", realMethod));
                try
                {
                    Object result = null;
                    if ((realMethod is ConstructorInfo)) {
                        result = ((ConstructorInfo)realMethod).Invoke(realMethodParameters);
                    } else if ((realMethod is MethodInfo)) {
                        result = ((MethodInfo)realMethod).Invoke(null, realMethodParameters);
                    }
                                       
                    if (result != null)
                    {
                        this.Cmdlet.WriteObject(result, true);
                    }
                }
                catch (Exception ex)
                {
                    if (ex.InnerException != null) {
                        this.Cmdlet.WriteError(new ErrorRecord(ex.InnerException, "PSDotNetAdapter.MethodInvocationError", ErrorCategory.InvalidOperation, t));
                    } else {
                        this.Cmdlet.WriteError(new ErrorRecord(ex, "PSDotNetAdapter.MethodInvocationError", ErrorCategory.InvalidOperation, t));
                    }
                }
            } else {
                this.Cmdlet.WriteDebug(String.Format("Could not find type {0}", this.ClassName));
            }
 
        }

        private Collection<PSObject> GetInstances() {
            this.Cmdlet.WriteDebug(this.Cmdlet.MyInvocation.InvocationName);
            
            string instanceScript = String.Empty;
            foreach (var kv in this.PrivateData) {
                if (kv.Key.ToLower().StartsWith(this.Cmdlet.MyInvocation.InvocationName.ToLower())) {
                    if (kv.Key.Substring(this.Cmdlet.MyInvocation.InvocationName.Length).ToLower() == "_instance") {
                        instanceScript = kv.Value;
                    }
                    if (kv.Key.Substring(this.Cmdlet.MyInvocation.InvocationName.Length).ToLower() == "_instanceparameter") {
                        if (this.Cmdlet.MyInvocation.BoundParameters.ContainsKey(kv.Value)) {
                            Collection<PSObject> instanceSet = new Collection<PSObject>();
                            instanceSet.Add(new PSObject(this.Cmdlet.MyInvocation.BoundParameters[kv.Value]));
                            return instanceSet;
                        }
                        instanceScript = kv.Value;
                    }
                    this.Cmdlet.WriteDebug(kv.Key + " : " + kv.Value);
                    
                }
            }

            if (String.IsNullOrEmpty(instanceScript)) {
                instanceScript = @"
param($pattern)
Write-Verbose ('Matching pattern: ' + ""$pattern"")
foreach ($var in Get-Variable -ValueOnly) {
    if ($var.pstypenames -match $pattern) {
        $var
    }
}
";
            }
            this.Cmdlet.WriteDebug(instanceScript);
            Pipeline pipeline = Runspace.DefaultRunspace.CreateNestedPipeline(instanceScript, false);
            pipeline.Commands[0].Parameters.Add("pattern", Regex.Escape(this.ClassName));
            Collection<PSObject> results = pipeline.Invoke();
            pipeline.Dispose();
            return results;
        }
 
        public override void ProcessRecord(QueryBuilder query, MethodInvocationInfo methodInvocationInfo, bool passThru)
        {
            this.Cmdlet.WriteDebug("Process Query and Method");
            Collection<PSObject> results = GetInstances();
 
            foreach (PSObject result in results)
            {
                this.Cmdlet.WriteDebug(String.Format("Processing Instance {0}", result.ImmediateBaseObject));
                if ((query as PSDotNetQueryBuilder).MatchesFilters(result.ImmediateBaseObject, this.Cmdlet))
                {
                    this.Cmdlet.WriteDebug(String.Format("Match found! {0}", result.ImmediateBaseObject));
                    ProcessRecord(result.ImmediateBaseObject, methodInvocationInfo, passThru);
                }
            }
        }
    }
}