Lib/QnaSessionManager.cs
using Microsoft.Win32;
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation; using System.Security.Cryptography; using System.Threading.Tasks; using System.Text; namespace BigFix.Qna { public class Result { private string _hash = null; public string Relevance { get; private set; } public string[] Answer { get; private set; } public string Error { get; private set; } public long EvalTime { get; private set; } bool IsError { get { return this.Error != null; } } public Result(string Relevance, string[] Answer, string Error, long EvalTime) { this.Relevance = Relevance; this.Answer = Answer; this.Error = Error; this.EvalTime = EvalTime; } public string GetHash() { if (this._hash == null) { using (SHA256 hashAlgorithm = SHA256.Create()) { byte[] hash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(this.Relevance)); StringBuilder hashString = new StringBuilder(); for (int i = 0; i < hash.Length; i++) { hashString.Append(hash[i].ToString("x2")); } this._hash = hashString.ToString(); } } return this._hash; } public override string ToString() { StringBuilder output = new StringBuilder(); output.AppendFormat("Q: {0}\n", Relevance); foreach (string answer in Answer) { output.AppendFormat("A: {0}\n", answer); } if (IsError) { output.AppendFormat("E: {0}\n", Error); } output.AppendFormat("T: {0}\n", TimeSpan.FromMilliseconds(EvalTime)); return output.ToString(); } } public class Session : IDisposable { /// <summary> /// Default name of an environmental variable that can be set to define the location /// where the BigFix QnA executable is installed. /// </summary> const string DEFAULT_QNA_ENVIRONMENTAL_VARIABLE = "QnA"; /// <summary> /// Default name of the BigFix QnA executable if one was not provided. /// </summary> const string DEFAULT_QNA_EXECUTABLE = "qna.exe"; /// <summary> /// Default amount of time (in seconds) to wait for the QnA process to return a result /// before considering it not-responsive. /// </summary> const int DEFAULT_QNA_EVALUATION_TIMEOUT = 60; /// <summary> /// Default amount of time (in milliseconds) the idle monitor will sleep between checking /// if a timeout event occurred. Values over 1,000 should be avoided as it could cause the /// idle timeout interval to exceed the 1-second granularity accuracy implied. /// </summary> const int DEFAULT_QNA_IDLE_POLLING = 300; /// <summary> /// Default amount of time (in seconds) the idle monitor will wait before stopping the /// spawned QnA process due to inactivity. /// </summary> const int DEFAULT_QNA_IDLE_TIMEOUT = 300; private readonly Object _lock = new object(); private Process _process; private DateTime _lastActivity; private TimeSpan _idleTimeout; private TimeSpan _evaluationTimeout; private Task _monitor; private bool _evaluating; /// <summary> /// Amount of time (in seconds) to keep the QnA process running while waiting /// for new queries. /// </summary> public int IdleTimeout { get { return (int)_idleTimeout.TotalSeconds; } set { lock (_lock) { _idleTimeout = TimeSpan.FromSeconds(value); } } } /// <summary> /// Amount of time (in seconds) to wait for the QnA process to evaluate a query /// before considering it not-responsive. /// </summary> public int EvaluationTimeout { get { return (int)_evaluationTimeout.TotalSeconds; } set { lock (_lock) { _evaluationTimeout = TimeSpan.FromSeconds(value); } } } /// <summary> /// Full path to the BigFix QnA executable. /// </summary> public string ExecutablePath { get; private set; } /// <summary> /// Version of the BigFix QnA executable. /// </summary> public string Version { get; private set; } public Session() : this(null) { } public Session(string path) { EvaluationTimeout = DEFAULT_QNA_EVALUATION_TIMEOUT; IdleTimeout = DEFAULT_QNA_IDLE_TIMEOUT; SetExecutablePath(path); if (!SpawnQnaProcess()) { throw new Exception("Unable to spawn BigFix QnA process!"); } } private async Task ProcessIdleMonitor() { try { while (true) { DateTime now = DateTime.Now; TimeSpan elapsedTime = now - _lastActivity; if (_process != null && _evaluating != true && elapsedTime >= _idleTimeout) { lock (_lock) { try { _process.Kill(); _process.Dispose(); } catch (ObjectDisposedException) { } catch (InvalidOperationException) { _process.Dispose(); _process = null; } catch (Win32Exception) { _process.Dispose(); _process = null; } finally { _process = null; } } } await Task.Delay(DEFAULT_QNA_IDLE_POLLING); } } finally { lock (_lock) { try { if (_process != null) { if (_process.HasExited == true) { _process.Dispose(); _process = null; } if (_process.Responding == false) { _process.Kill(); _process.Dispose(); _process = null; } } } catch (ObjectDisposedException) { _process = null; } catch (InvalidOperationException) { _process.Dispose(); _process = null; } catch (Win32Exception) { _process.Dispose(); _process = null; } } } } private bool SpawnQnaProcess() { lock (_lock) { _lastActivity = DateTime.Now; try { if (_process != null) { if (_process.HasExited == true) { _process.Dispose(); _process = null; } if (_process.Responding == false) { _process.Kill(); _process.Dispose(); _process = null; } } } catch (ObjectDisposedException) { _process = null; } catch (InvalidOperationException) { _process.Dispose(); _process = null; } catch (Win32Exception) { _process.Dispose(); _process = null; } } if (_process == null) { Process process = null; try { process = new Process(); process.StartInfo.FileName = this.ExecutablePath; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.ErrorDialog = false; if (process.Start()) { process.StandardInput.AutoFlush = true; lock (_lock) { _process = process; if (_monitor == null || _monitor.IsCompleted) { _monitor = ProcessIdleMonitor(); } } return true; } else { process.Kill(); process.Dispose(); } } catch { return false; } return false; } return _process != null ? _process.Responding == true : false; } private bool SetExecutablePath() { List<string> searchPaths = new List<string>(); // Environmental variable. searchPaths.Add(Environment.ExpandEnvironmentVariables(Environment.GetEnvironmentVariable(DEFAULT_QNA_ENVIRONMENTAL_VARIABLE) ?? String.Empty)); // Current working directory. searchPaths.Add(Directory.GetCurrentDirectory()); // Look to see if the BigFix Client is installed as the QnA utility is [typically] co-installed along side. searchPaths.Add(Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\BigFix\EnterpriseClient", "EnterpriseClientFolder", Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\BigFix\EnterpriseClient", "EnterpriseClientFolder", null)) as string); // System environmental search path(s). searchPaths.AddRange(Environment.ExpandEnvironmentVariables(Environment.GetEnvironmentVariable("PATH") ?? String.Empty).Split(new char[] { ';' })); foreach (string path in searchPaths) { if (!String.IsNullOrWhiteSpace(path)) { if (SetExecutablePath(path)) { return true; } } } return false; } private bool SetExecutablePath(string path) { if (String.IsNullOrWhiteSpace(path)) { return SetExecutablePath(); } string executablePath = null; if (".exe".Equals(Path.GetExtension(path), StringComparison.InvariantCultureIgnoreCase)) { executablePath = path; } else { executablePath = Path.Combine(path, DEFAULT_QNA_EXECUTABLE); } if (File.Exists(executablePath)) { try { var fileInfo = FileVersionInfo.GetVersionInfo(executablePath); if (fileInfo != null && (fileInfo.ProductName.Contains("QnA") || fileInfo.FileDescription.Contains("QnA"))) { ExecutablePath = executablePath; Version = fileInfo.ProductVersion; return true; } } catch { } } return false; } public Result Query(string relevance) { List<string> answers = new List<string>(); string error = null; long evalTime = 0; try { if (SpawnQnaProcess()) { lock (_lock) { _evaluating = true; } _process.StandardInput.WriteLine(relevance); while (true) { string line = _process.StandardOutput.ReadLine(); if (line.StartsWith("Q: ")) { line = line.Substring(3); } if (String.IsNullOrWhiteSpace(line)) { break; } switch (line[0]) { case 'A': answers.Add(line.Substring(3)); break; case 'E': error = line.Substring(3); break; case 'T': if (!long.TryParse(line.Substring(3), out evalTime)) { evalTime = 0; } break; } } } else { error = "Unable to spawn BigFix QnA process!"; } } catch (Exception e) { error = e.Message; } finally { lock (_lock) { _evaluating = false; _lastActivity = DateTime.Now; } } return new Result(relevance, answers.ToArray(), error, evalTime); } public void Dispose() { if (_process != null) { lock (_lock) { _process.Kill(); _process.Dispose(); } } try { if (_monitor != null) { if (_monitor.IsCompleted == false) { _monitor.Wait(DEFAULT_QNA_IDLE_POLLING); } } } catch { } } } } |