public async Task <HttpResponseMessage> ProcessRequestAsync(HttpRequestMessage request = null) { DynamicPowershellApiEvents .Raise .ReceivedRequest(Request.RequestUri.ToString()); Guid activityId = Guid.NewGuid(); if (Request.RequestUri.Segments.Length < 4) { throw new MalformedUriException(string.Format("There is {0} segments but must be at least 4 segments in the URI.", Request.RequestUri.Segments.Length)); } string apiName = Request.RequestUri.Segments[2].Replace("/", string.Empty); string methodName = Request.RequestUri.Segments[3].Replace("/", string.Empty); // Check that the verbose messaging is working DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("The api name is {0} and the method is {1}", apiName, methodName)); // find the api. var api = WebApiConfiguration.Instance.Apis[apiName]; if (api == null) { // Check that the verbose messaging is working DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Cannot find the requested web API: {0}", apiName)); throw new WebApiNotFoundException(string.Format("Cannot find the requested web API: {0}", apiName)); } // find the web method. WebMethod method = api.WebMethods[methodName]; if (method == null) { DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Cannot find the requested web method: {0}", methodName)); throw new WebMethodNotFoundException(string.Format("Cannot find web method: {0}", methodName)); } // Is this scheduled as a job? bool asJob = method.AsJob; // Is this a POST method IEnumerable <KeyValuePair <string, string> > query2; if (Request.Method == HttpMethod.Post) { string documentContents = await Request.Content.ReadAsStringAsync(); try { // Work out the parameters from JSON var queryStrings = new Dictionary <string, string>(); if (documentContents.StartsWith("[")) // it's an array. Let's use a horrible nested loop { JArray tokenArray = JArray.Parse(documentContents); foreach (JObject details in tokenArray) { foreach (var detail in details) { var name = detail.Key; var value = detail.Value.ToString(); queryStrings.Add(name, value); } } } else // it's an object. Let's just treat it as an object { JObject obj = JObject.Parse(documentContents); foreach (var details in obj) { var name = details.Key; var value = details.Value.ToString(); queryStrings.Add(name, value); } } if (method.Parameters.Any(param => queryStrings.All(q => q.Key != param.Name))) { DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Cannot find all parameters required.")); throw new MissingParametersException("Cannot find all parameters required."); } query2 = queryStrings.ToList(); } catch (Exception) { DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Cannot find all parameters required.")); throw new MissingParametersException("Cannot find all parameters required."); } } else { // Get our parameters. IEnumerable <KeyValuePair <string, string> > query = Request.GetQueryNameValuePairs(); if (method.Parameters.Any(param => query.All(q => q.Key != param.Name))) { DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Cannot find all parameters required.")); throw new MissingParametersException("Cannot find all parameters required."); } query2 = query; } // We now catch an exception from the runner try { DynamicPowershellApiEvents.Raise.VerboseMessaging(String.Format("Started Executing the runner")); if (!asJob) { PowershellReturn output = await _powershellRunner.ExecuteAsync(method.PowerShellPath, method.Snapin, method.Module, query2.ToList(), asJob); JToken token = output.ActualPowerShellData.StartsWith("[") ? (JToken)JArray.Parse(output.ActualPowerShellData) : JObject.Parse(output.ActualPowerShellData); return(new HttpResponseMessage { Content = new JsonContent(token) }); } else // run as job. { Guid jobId = Guid.NewGuid(); string requestedHost = String.Empty; if (Request.Properties.ContainsKey("MS_OwinContext")) { requestedHost = ((OwinContext)Request.Properties["MS_OwinContext"]).Request.RemoteIpAddress; } _jobListProvider.AddRequestedJob(jobId, requestedHost); // Go off and run this job please sir. var task = Task <bool> .Factory.StartNew( () => { try { Task <PowershellReturn> goTask = _powershellRunner.ExecuteAsync( method.PowerShellPath, method.Snapin, method.Module, query2.ToList(), true); goTask.Wait(); var output = goTask.Result; JToken token = output.ActualPowerShellData.StartsWith("[") ? (JToken)JArray.Parse(output.ActualPowerShellData) : JObject.Parse(output.ActualPowerShellData); _jobListProvider.CompleteJob(jobId, output.PowerShellReturnedValidData, String.Empty); string outputPath = Path.Combine(WebApiConfiguration.Instance.Jobs.JobStorePath, jobId + ".json"); using (TextWriter writer = File.CreateText(outputPath)) { JsonSerializer serializer = new JsonSerializer { Formatting = Formatting.Indented // Make it readable for Ops sake! }; serializer.Serialize(writer, token); } return(true); } catch (PowerShellExecutionException poException) { CrashLogEntry entry = new CrashLogEntry { Exceptions = poException.Exceptions, LogTime = poException.LogTime, RequestAddress = requestedHost, RequestMethod = methodName, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); DynamicPowershellApiEvents.Raise.InvalidPowerShellOutput(poException.Message + " logged to " + logFile); ErrorResponse response = new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = poException.Message }; JToken token = new JObject(response); _jobListProvider.CompleteJob(jobId, false, String.Empty); string outputPath = Path.Combine(WebApiConfiguration.Instance.Jobs.JobStorePath, jobId + ".json"); using (TextWriter writer = File.CreateText(outputPath)) { JsonSerializer serializer = new JsonSerializer { Formatting = Formatting.Indented // Make it readable for Ops sake! }; serializer.Serialize(writer, token); } return(true); } } ); // return the Job ID. return(new HttpResponseMessage { Content = new JsonContent(new JValue(jobId)) }); } } catch (PowerShellExecutionException poException) { CrashLogEntry entry = new CrashLogEntry { Exceptions = poException.Exceptions, LogTime = poException.LogTime, RequestAddress = String.Empty, // TODO: Find a way of getting the request host. RequestMethod = methodName, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); DynamicPowershellApiEvents.Raise.InvalidPowerShellOutput(poException.Message + " logged to " + logFile); HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = poException.Message } ); return(response); } catch (Exception ex) { CrashLogEntry entry = new CrashLogEntry { Exceptions = new List <PowerShellException> { new PowerShellException { ErrorMessage = ex.Message, LineNumber = 0, ScriptName = "GenericController.cs", StackTrace = ex.StackTrace } }, LogTime = DateTime.Now, RequestAddress = String.Empty, // TODO: Find a way of getting the request host. RequestMethod = methodName, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); DynamicPowershellApiEvents.Raise.UnhandledException(ex.Message + " logged to " + logFile, ex.StackTrace ?? String.Empty); HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = ex.Message } ); return(response); } }
public async Task <HttpResponseMessage> ProcessRequestAsync(HttpRequestMessage request = null) { PSCommand psCommand; if (request.Properties.TryGetValue("APP_PSCommand", out object psc) && psc is PSCommand) { psCommand = (PSCommand)psc; } else { PowerShellRestApiEvents.Raise.VerboseMessaging("Unable to retrieve the PowerShell command"); throw new PScommandNotFoundException("Unable to retrieve the PowerShell command"); } Guid activityId = Guid.NewGuid(); bool asJob = psCommand.AsJob; //var inParams = new Dictionary<string, object>(); var inParams = new List <KeyValuePair <string, object> >(); List <PSParameter> allowedParams; #region ----- Body parameters ----- allowedParams = psCommand.GetParametersByLocation(RestLocation.Body); string documentContents = await Request.Content.ReadAsStringAsync(); documentContents = documentContents.ToString().Trim(); if (!string.IsNullOrWhiteSpace(documentContents)) { // If only one parameter is defined in parameter file, not name needed /*if (allowedParams.Count == 1) * { * if (documentContents.StartsWith("[")) * { * JArray tokenArray = JArray.Parse(documentContents); * Object value = JsonHelper.Convert(tokenArray); * inParams.Add(new KeyValuePair<string, object>(allowedParams.First().Name, value)); * } * else if (documentContents.StartsWith("{")) * { * JObject obj = JObject.Parse(documentContents); * Object value = JsonHelper.Convert(obj); * inParams.Add(new KeyValuePair<string, object>(allowedParams.First().Name, value)); * } * else * { * inParams.Add(new KeyValuePair<string, object>(allowedParams.First().Name, documentContents)); * } * } * else if(allowedParams.Count > 1) * { */ if (documentContents.StartsWith("[")) // if more one parameter are defined in config file, array is not allow { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Body cannot be a json array.")); throw new MissingParametersException("Body cannot be a json array."); } else if (documentContents.StartsWith("{")) // it's an object. Let's just treat it as an object { JObject obj = JObject.Parse(documentContents); foreach (var detail in obj) { String name = detail.Key; Object value = JsonHelper.Convert(detail.Value); if (allowedParams.FirstOrDefault(x => x.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)) != null) { inParams.Add(new KeyValuePair <string, object>(name, value)); } else { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Parameter {0} is not allow in body.", name)); throw new MissingParametersException(String.Format("Parameter {0} is not allow in body.", name)); } } } else { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Boby must be a json object with {0} properties.", allowedParams.Count)); throw new MissingParametersException(String.Format("Boby must be a json object with {0} properties.", allowedParams.Count)); } //} } #endregion #region ----- QueryString parameters ----- allowedParams = psCommand.GetParametersByLocation(RestLocation.Query); foreach (var p in Request.GetQueryNameValuePairs()) { var param = allowedParams.FirstOrDefault(x => x.Name.Equals(p.Key, StringComparison.CurrentCultureIgnoreCase)); if (param != null) { var value = Convert.ChangeType(p.Value, param.Type); inParams.Add(new KeyValuePair <string, object>(param.Name, value)); } else { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Parameter {0} is not allow in QueryString.", p.Key)); throw new MissingParametersException(String.Format("Parameter {0} is not allow in QueryString.", p.Key)); } } #endregion #region ----- URI Path parameters ----- allowedParams = psCommand.GetParametersByLocation(RestLocation.Path); if (Request.RequestUri.Segments.Length - 4 <= allowedParams.Count) { for (int i = 4; i < Request.RequestUri.Segments.Length; i++) { string uriValue = Request.RequestUri.Segments[i].Replace("/", string.Empty); var param = allowedParams.Skip(i - 4).FirstOrDefault(); var value = Convert.ChangeType(uriValue, param.Type); inParams.Add(new KeyValuePair <string, object>(param.Name, value)); } } else if (Request.RequestUri.Segments.Length - 4 > allowedParams.Count) { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Too many parameters in Path.")); throw new MissingParametersException(String.Format("Too many parameters in Path.")); } #endregion #region ----- Header parameters ----- List <string> allowedParamNames = psCommand.GetParametersByLocation(RestLocation.Header).Select(x => x.Name.ToLower()).ToList(); /* * Request.Headers.Where(x => allowedParamNames.Contains(x.Key.ToLower())) * .ToList() * .ForEach(x => inParams.Add(new KeyValuePair<string, object>(x.Key, x.Value))); */ foreach (var p in Request.Headers) { var param = allowedParams.FirstOrDefault(x => x.Name.Equals(p.Key, StringComparison.CurrentCultureIgnoreCase)); if (param != null) { var value = Convert.ChangeType(p.Value, param.Type); inParams.Add(new KeyValuePair <string, object>(param.Name, value)); } } #endregion #region ----- ConfigFile parameters ----- foreach (var param in psCommand.GetParametersByLocation(RestLocation.ConfigFile, true)) { inParams.Add(new KeyValuePair <string, object>(param.Name, param.Value)); } #endregion #region ----- User parameter ----- if (!string.IsNullOrWhiteSpace(psCommand.ParameterForUser)) { string userName = this.User.Identity != null ? this.User.Identity.Name : "Anonymous"; inParams.Add(new KeyValuePair <string, object>(psCommand.ParameterForUser, userName)); } #endregion #region ----- Roles parameter ----- if (!string.IsNullOrWhiteSpace(psCommand.ParameterForRoles)) { string[] userRoles = this.User.Identity == null ? new string[0] : (this.User.Identity as ClaimsIdentity) .Claims .Where(c => c.Type == ClaimTypes.Role) .Select(c => c.Value) .ToArray(); inParams.Add(new KeyValuePair <string, object>(psCommand.ParameterForRoles, userRoles)); } #endregion #region ----- Claims parameter ----- if (!string.IsNullOrWhiteSpace(psCommand.ParameterForClaims)) { Claim[] userClaims = this.User.Identity == null ? new Claim[0] : (this.User.Identity as ClaimsIdentity) .Claims .ToArray(); inParams.Add(new KeyValuePair <string, object>(psCommand.ParameterForClaims, userClaims)); } #endregion #region ----- Check if all parameters required are found ----- if (psCommand.GetParametersRequired().Select(x => x.Name).Any(requiedName => inParams.All(q => q.Key != requiedName))) { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Cannot find all parameters required.")); throw new MissingParametersException("Cannot find all parameters required."); } #endregion // We now catch an exception from the runner try { PowerShellRestApiEvents.Raise.VerboseMessaging(String.Format("Started Executing the runner")); if (!asJob) { PowershellReturn output = await _powershellRunner.ExecuteAsync(psCommand.Name, psCommand.Snapin, psCommand.Module, inParams, asJob); JToken token = string.IsNullOrWhiteSpace(output.ActualPowerShellData) ? new JObject() : output.ActualPowerShellData.StartsWith("[") ? (JToken)JArray.Parse(output.ActualPowerShellData) : output.ActualPowerShellData.StartsWith("{") ? JObject.Parse(output.ActualPowerShellData) : JObject.Parse("{\"Message\":" + output.ActualPowerShellData + "}"); JToken token2 = ""; return(new HttpResponseMessage { Content = new JsonContent(token) }); } else // run as job. { Guid jobId = Guid.NewGuid(); string requestedHost = String.Empty; if (Request.Properties.ContainsKey("MS_OwinContext")) { requestedHost = ((OwinContext)Request.Properties["MS_OwinContext"]).Request.RemoteIpAddress; } _jobListProvider.AddRequestedJob(jobId, requestedHost); // Go off and run this job please sir. var task = Task <bool> .Factory.StartNew( () => { try { Task <PowershellReturn> goTask = _powershellRunner.ExecuteAsync( psCommand.Name, psCommand.Snapin, psCommand.Module, inParams, true); goTask.Wait(); var output = goTask.Result; JToken token = output.ActualPowerShellData.StartsWith("[") ? (JToken)JArray.Parse(output.ActualPowerShellData) : JObject.Parse(output.ActualPowerShellData); _jobListProvider.CompleteJob(jobId, output.PowerShellReturnedValidData, String.Empty); string outputPath = Path.Combine(WebApiConfiguration.Instance.Jobs.JobStorePath, jobId + ".json"); using (TextWriter writer = File.CreateText(outputPath)) { JsonSerializer serializer = new JsonSerializer { Formatting = Formatting.Indented // Make it readable for Ops sake! }; serializer.Serialize(writer, token); } return(true); } catch (PowerShellExecutionException poException) { CrashLogEntry entry = new CrashLogEntry { Exceptions = poException.Exceptions, LogTime = poException.LogTime, RequestAddress = requestedHost, RequestMethod = psCommand.Name, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); PowerShellRestApiEvents.Raise.InvalidPowerShellOutput(poException.Message + " logged to " + logFile); ErrorResponse response = new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = poException.Message }; JToken token = new JObject(response); _jobListProvider.CompleteJob(jobId, false, String.Empty); string outputPath = Path.Combine(WebApiConfiguration.Instance.Jobs.JobStorePath, jobId + ".json"); using (TextWriter writer = File.CreateText(outputPath)) { JsonSerializer serializer = new JsonSerializer { Formatting = Formatting.Indented // Make it readable for Ops sake! }; serializer.Serialize(writer, token); } return(true); } } ); // return the Job ID. return(new HttpResponseMessage { Content = new JsonContent(new JValue(jobId)) }); } } catch (PowerShellClientException ex) { PowerShellRestApiEvents.Raise.InvalidPowerShellOutput("[" + ex.Category.ToString() + "]" + ex.Message); switch (ex.Category) { case ErrorCategory.PermissionDenied: return(Request.CreateErrorResponse(HttpStatusCode.Forbidden, ex.Message)); case ErrorCategory.ObjectNotFound: return(Request.CreateErrorResponse(HttpStatusCode.NotFound, ex.Message)); case ErrorCategory.InvalidArgument: return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message)); case ErrorCategory.ResourceExists: return(Request.CreateErrorResponse(HttpStatusCode.Conflict, ex.Message)); } return(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "[" + ex.Category.ToString() + "]" + ex.Message)); } catch (PowerShellExecutionException poException) { string requestedHost = String.Empty; if (Request.Properties.ContainsKey("MS_OwinContext")) { requestedHost = ((OwinContext)Request.Properties["MS_OwinContext"]).Request.RemoteIpAddress; } CrashLogEntry entry = new CrashLogEntry { Exceptions = poException.Exceptions, LogTime = poException.LogTime, RequestAddress = requestedHost, RequestMethod = psCommand.Name, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); PowerShellRestApiEvents.Raise.InvalidPowerShellOutput(poException.Message + " logged to " + logFile); HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = poException.Message } ); return(response); } catch (Exception ex) { CrashLogEntry entry = new CrashLogEntry { Exceptions = new List <PowerShellException> { new PowerShellException { ErrorMessage = ex.Message, LineNumber = 0, ScriptName = "GenericController.cs", StackTrace = ex.StackTrace } }, LogTime = DateTime.Now, RequestAddress = String.Empty, // TODO: Find a way of getting the request host. RequestMethod = psCommand.Name, RequestUrl = Request.RequestUri.ToString() }; entry.SetActivityId(activityId); string logFile = _crashLogger.SaveLog(entry); PowerShellRestApiEvents.Raise.UnhandledException(ex.Message + " logged to " + logFile, ex.StackTrace ?? String.Empty); HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, new ErrorResponse { ActivityId = activityId, LogFile = logFile, Message = ex.Message } ); return(response); } }