/// <summary>
 /// LogObject: Logs the given object though WriteObject.
 /// </summary>
 /// <param name="obj">The object to be logged</param>
 /// <param name="command">The Invoke-PSLint command this logger is running through</param>
 public void LogObject(Object obj, InvokeScriptAnalyzerCommand command)
 {
     if (command == null)
     {
         throw new ArgumentNullException("command");
     }
     if (obj == null)
     {
         throw new ArgumentNullException("diagnostic");
     }
     command.WriteObject(obj);
 }
        /// <summary>
        /// GetExternalRecord: Get external rules in parallel using RunspacePool and run each rule in its own runspace.
        /// </summary>
        /// <param name="ast"></param>
        /// <param name="token"></param>
        /// <param name="rules"></param>
        /// <param name="command"></param>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public IEnumerable<DiagnosticRecord> GetExternalRecord(Ast ast, Token[] token, ExternalRule[] rules, InvokeScriptAnalyzerCommand command, string filePath)
        {
            // Defines InitialSessionState.
            InitialSessionState state = InitialSessionState.CreateDefault2();

            // Groups rules by module paths and imports them.
            Dictionary<string, List<ExternalRule>> modules = rules
                .GroupBy<ExternalRule, string>(item => item.GetFullModulePath())
                .ToDictionary(item => item.Key, item => item.ToList());
            state.ImportPSModule(modules.Keys.ToArray<string>());

            // Creates and opens RunspacePool
            RunspacePool rsp = RunspaceFactory.CreateRunspacePool(state);
            rsp.SetMinRunspaces(1);
            rsp.SetMaxRunspaces(5);
            rsp.Open();

            // Groups rules by AstType and Tokens.
            Dictionary<string, List<ExternalRule>> astRuleGroups = rules
                .Where<ExternalRule>(item => item.GetParameter().EndsWith("ast", StringComparison.OrdinalIgnoreCase))
                .GroupBy<ExternalRule, string>(item => item.GetParameterType())
                .ToDictionary(item => item.Key, item => item.ToList());

            Dictionary<string, List<ExternalRule>> tokenRuleGroups = rules
                .Where<ExternalRule>(item => item.GetParameter().EndsWith("token", StringComparison.OrdinalIgnoreCase))
                .GroupBy<ExternalRule, string>(item => item.GetParameterType())
                .ToDictionary(item => item.Key, item => item.ToList());

            using (rsp)
            {
                // Defines the commands to be run.
                List<System.Management.Automation.PowerShell> powerShellCommands
                    = new List<System.Management.Automation.PowerShell>();

                // Defines the command results.
                List<IAsyncResult> powerShellCommandResults = new List<IAsyncResult>();

                #region Builds and invokes commands list

                foreach (KeyValuePair<string, List<ExternalRule>> tokenRuleGroup in tokenRuleGroups)
                {
                    foreach (IExternalRule rule in tokenRuleGroup.Value)
                    {
                        System.Management.Automation.PowerShell posh =
                            System.Management.Automation.PowerShell.Create();
                        posh.RunspacePool = rsp;

                        // Adds command to run external analyzer rule, like
                        // Measure-CurlyBracket -ScriptBlockAst $ScriptBlockAst
                        // Adds module name (source name) to handle ducplicate function names in different modules.
                        string ruleName = string.Format("{0}\\{1}", rule.GetSourceName(), rule.GetName());
                        posh.Commands.AddCommand(ruleName);
                        posh.Commands.AddParameter(rule.GetParameter(), token);

                        // Merges results because external analyzer rules may throw exceptions.
                        posh.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error,
                            PipelineResultTypes.Output);

                        powerShellCommands.Add(posh);
                        powerShellCommandResults.Add(posh.BeginInvoke());
                    }
                }

                foreach (KeyValuePair<string, List<ExternalRule>> astRuleGroup in astRuleGroups)
                {
                    // Find all AstTypes that appeared in rule groups.
                    IEnumerable<Ast> childAsts = ast.FindAll(new Func<Ast, bool>((testAst) =>
                        (astRuleGroup.Key.IndexOf(testAst.GetType().FullName,StringComparison.OrdinalIgnoreCase) != -1)), false);

                    foreach (Ast childAst in childAsts)
                    {
                        foreach (IExternalRule rule in astRuleGroup.Value)
                        {
                            System.Management.Automation.PowerShell posh =
                                System.Management.Automation.PowerShell.Create();
                            posh.RunspacePool = rsp;

                            // Adds command to run external analyzer rule, like
                            // Measure-CurlyBracket -ScriptBlockAst $ScriptBlockAst
                            // Adds module name (source name) to handle ducplicate function names in different modules.
                            string ruleName = string.Format("{0}\\{1}", rule.GetSourceName(), rule.GetName());
                            posh.Commands.AddCommand(ruleName);
                            posh.Commands.AddParameter(rule.GetParameter(), childAst);

                            // Merges results because external analyzer rules may throw exceptions.
                            posh.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error,
                                PipelineResultTypes.Output);

                            powerShellCommands.Add(posh);
                            powerShellCommandResults.Add(posh.BeginInvoke());
                        }
                    }
                }

                #endregion
                #region Collects the results from commands.
                List<DiagnosticRecord> diagnostics = new List<DiagnosticRecord>();
                try
                {
                    for (int i = 0; i < powerShellCommands.Count; i++)
                    {
                        // EndInvoke will wait for each command to finish, so we will be getting the commands
                        // in the same order that they have been invoked withy BeginInvoke.
                        PSDataCollection<PSObject> psobjects = powerShellCommands[i].EndInvoke(powerShellCommandResults[i]);

                        foreach (var psobject in psobjects)
                        {
                            DiagnosticSeverity severity;
                            IScriptExtent extent;
                            string message = string.Empty;
                            string ruleName = string.Empty;

                            if (psobject != null && psobject.ImmediateBaseObject != null)
                            {
                                // Because error stream is merged to output stream,
                                // we need to handle the error records.
                                if (psobject.ImmediateBaseObject is ErrorRecord)
                                {
                                    ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject;
                                    command.WriteError(record);
                                    continue;
                                }

                                // DiagnosticRecord may not be correctly returned from external rule.
                                try
                                {
                                    Enum.TryParse<DiagnosticSeverity>(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity);
                                    message = psobject.Properties["Message"].Value.ToString();
                                    extent = (IScriptExtent)psobject.Properties["Extent"].Value;
                                    ruleName = psobject.Properties["RuleName"].Value.ToString();
                                }
                                catch (Exception ex)
                                {
                                    command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this));
                                    continue;
                                }

                                if (!string.IsNullOrEmpty(message))
                                {
                                    diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, null));
                                }
                            }
                        }
                    }
                }
                //Catch exception where customized defined rules have exceptins when doing invoke
                catch(Exception ex)
                {
                    command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this));
                }

                return diagnostics;
                #endregion
            }
        }