added a bunch of comments
This commit is contained in:
Родитель
91b2bf8516
Коммит
e395476fea
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"disabled": false,
|
||||
"entryPoint":"FunctionName",
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
|
@ -15,7 +14,7 @@
|
|||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "$return"
|
||||
"name": "res"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
function FunctionName {
|
||||
$global:res = $req.GetHttpResponseContext()
|
||||
"hello verbose"
|
||||
$res.Json('{"Hello":"World"}')
|
||||
$res.SetHeader("foo", "bar")
|
||||
$name = 'World'
|
||||
if($req.Query.Name) {
|
||||
$name = $req.Query.Name
|
||||
}
|
||||
|
||||
Write-Verbose "Hello $name" -Verbose
|
||||
Write-Warning "Warning $name"
|
||||
|
||||
$res = [HttpResponseContext]@{
|
||||
Body = @{ Hello = $name }
|
||||
ContentType = 'application/json'
|
||||
}
|
|
@ -21,6 +21,9 @@ namespace Azure.Functions.PowerShell.Worker.Messaging
|
|||
public async Task WriteAsync(StreamingMessage message)
|
||||
{
|
||||
if(isDisposed) return;
|
||||
|
||||
// Wait for the handle to be released because we can't have
|
||||
// more than one message being sent at the same time
|
||||
await _writeStreamHandle.WaitAsync();
|
||||
try
|
||||
{
|
||||
|
|
|
@ -24,12 +24,10 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
{
|
||||
Bindings.Add(binding.Key, binding.Value);
|
||||
|
||||
// Only add Out and InOut bindings to the OutputBindings
|
||||
if (binding.Value.Direction != BindingInfo.Types.Direction.In)
|
||||
{
|
||||
if(binding.Value.Type == "http")
|
||||
{
|
||||
HttpOutputName = binding.Key;
|
||||
}if(binding.Value.Type == "http")
|
||||
{
|
||||
HttpOutputName = binding.Key;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,5 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
public MapField<string, string> Params {get; set;}
|
||||
public object Body {get; set;}
|
||||
public object RawBody {get; set;}
|
||||
|
||||
public HttpResponseContext GetHttpResponseContext()
|
||||
{
|
||||
return new HttpResponseContext();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections;
|
||||
using Google.Protobuf.Collections;
|
||||
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
|
||||
|
||||
|
@ -5,78 +6,10 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
{
|
||||
public class HttpResponseContext
|
||||
{
|
||||
#region properties
|
||||
public string StatusCode {get; set;} = "200";
|
||||
public MapField<string, string> Headers {get; set;} = new MapField<string,string>();
|
||||
public TypedData Body {get; set;} = new TypedData { String = "" };
|
||||
public Hashtable Headers {get; set;} = new Hashtable();
|
||||
public object Body {get; set;}
|
||||
public string ContentType {get; set;} = "text/plain";
|
||||
public bool EnableContentNegotiation {get; set;} = false;
|
||||
#endregion
|
||||
#region Helper functions for user to use to set data
|
||||
public HttpResponseContext Header(string field, string value) =>
|
||||
SetHeader(field, value);
|
||||
public HttpResponseContext SetHeader(string field, string value)
|
||||
{
|
||||
Headers.Add(field, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public string GetHeader(string field) =>
|
||||
Headers[field];
|
||||
|
||||
public HttpResponseContext RemoveHeader(string field)
|
||||
{
|
||||
Headers.Remove(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponseContext Status(int statusCode) =>
|
||||
SetStatus(statusCode);
|
||||
public HttpResponseContext Status(string statusCode) =>
|
||||
SetStatus(statusCode);
|
||||
public HttpResponseContext SetStatus(int statusCode) =>
|
||||
SetStatus(statusCode);
|
||||
public HttpResponseContext SetStatus(string statusCode)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponseContext Type(string type) =>
|
||||
SetHeader("content-type", type);
|
||||
public HttpResponseContext SetContentType(string type) =>
|
||||
SetHeader("content-type", type);
|
||||
|
||||
public HttpResponseContext Send(int val)
|
||||
{
|
||||
Body = new TypedData
|
||||
{
|
||||
Int = val
|
||||
};
|
||||
return this;
|
||||
}
|
||||
public HttpResponseContext Send(double val)
|
||||
{
|
||||
Body = new TypedData
|
||||
{
|
||||
Double = val
|
||||
};
|
||||
return this;
|
||||
}
|
||||
public HttpResponseContext Send(string val)
|
||||
{
|
||||
Body = new TypedData
|
||||
{
|
||||
String = val
|
||||
};
|
||||
return this;
|
||||
}
|
||||
public HttpResponseContext Json(string val) {
|
||||
Body = new TypedData
|
||||
{
|
||||
Json = val
|
||||
};
|
||||
return Type("application/json");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -10,8 +10,11 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// applications. Not all members are implemented. Those that aren't throw a
|
||||
/// NotImplementedException.
|
||||
/// </summary>
|
||||
internal class Host : PSHost
|
||||
internal class AzureFunctionsHost : PSHost
|
||||
{
|
||||
/// <summary>
|
||||
/// The private reference of the logger.
|
||||
/// </summary>
|
||||
private RpcLogger _logger;
|
||||
|
||||
/// <summary>
|
||||
|
@ -80,15 +83,14 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// </summary>
|
||||
public override Version Version => new Version(1, 0, 0, 0);
|
||||
|
||||
public Host(RpcLogger logger)
|
||||
public AzureFunctionsHost(RpcLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
HostUI = new HostUserInterface(logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not implemented by this example class. The call fails with an exception.
|
||||
/// Not implemented by this class. The call fails with an exception.
|
||||
/// </summary>
|
||||
public override void EnterNestedPrompt()
|
||||
{
|
||||
|
@ -96,7 +98,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not implemented by this example class. The call fails with an exception.
|
||||
/// Not implemented by this class. The call fails with an exception.
|
||||
/// </summary>
|
||||
public override void ExitNestedPrompt()
|
||||
{
|
||||
|
@ -106,7 +108,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// <summary>
|
||||
/// This API is called before an external application process is started. Typically
|
||||
/// it's used to save state that the child process may alter so the parent can
|
||||
/// restore that state when the child exits. In this sample, we don't need this so
|
||||
/// restore that state when the child exits. In this, we don't need this so
|
||||
/// the method simple returns.
|
||||
/// </summary>
|
||||
public override void NotifyBeginApplication()
|
||||
|
@ -116,8 +118,8 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
|
||||
/// <summary>
|
||||
/// This API is called after an external application process finishes. Typically
|
||||
/// it's used to restore state that the child process may have altered. In this
|
||||
/// sample, we don't need this so the method simple returns.
|
||||
/// it's used to restore state that the child process may have altered. In this,
|
||||
/// we don't need this so the method simple returns.
|
||||
/// </summary>
|
||||
public override void NotifyEndApplication()
|
||||
{
|
|
@ -15,6 +15,9 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// </summary>
|
||||
internal class HostUserInterface : PSHostUserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// The private reference of the logger.
|
||||
/// </summary>
|
||||
private RpcLogger _logger;
|
||||
|
||||
/// <summary>
|
||||
|
@ -40,7 +43,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// <param name="message">The text of the prompt.</param>
|
||||
/// <param name="descriptions">A collection of FieldDescription objects that
|
||||
/// describe each field of the prompt.</param>
|
||||
/// <returns>Throws a NotImplementedException exception.</returns>
|
||||
/// <returns>Throws a NotImplementedException exception because we don't need a prompt.</returns>
|
||||
public override Dictionary<string, PSObject> Prompt(string caption, string message, System.Collections.ObjectModel.Collection<FieldDescription> descriptions)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -55,7 +58,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// each choice.</param>
|
||||
/// <param name="defaultChoice">The index of the label in the Choices parameter
|
||||
/// collection. To indicate no default choice, set to -1.</param>
|
||||
/// <returns>Throws a NotImplementedException exception.</returns>
|
||||
/// <returns>Throws a NotImplementedException exception because we don't need a prompt.</returns>
|
||||
public override int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection<ChoiceDescription> choices, int defaultChoice)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -69,7 +72,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// <param name="message">The text of the message.</param>
|
||||
/// <param name="userName">The user name whose credential is to be prompted for.</param>
|
||||
/// <param name="targetName">The name of the target for which the credential is collected.</param>
|
||||
/// <returns>Throws a NotImplementedException exception.</returns>
|
||||
/// <returns>Throws a NotImplementedException exception because we don't need a prompt.</returns>
|
||||
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -88,7 +91,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// identifies the type of credentials that can be returned.</param>
|
||||
/// <param name="options">A PSCredentialUIOptions constant that identifies the UI
|
||||
/// behavior when it gathers the credentials.</param>
|
||||
/// <returns>Throws a NotImplementedException exception.</returns>
|
||||
/// <returns>Throws a NotImplementedException exception because we don't need a prompt.</returns>
|
||||
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -98,7 +101,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// Reads characters that are entered by the user until a newline
|
||||
/// (carriage return) is encountered.
|
||||
/// </summary>
|
||||
/// <returns>The characters that are entered by the user.</returns>
|
||||
/// <returns>Throws a NotImplemented exception because we are in a non-interactive experience.</returns>
|
||||
public override string ReadLine()
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -108,7 +111,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// Reads characters entered by the user until a newline (carriage return)
|
||||
/// is encountered and returns the characters as a secure string.
|
||||
/// </summary>
|
||||
/// <returns>Throws a NotImplemented exception.</returns>
|
||||
/// <returns>Throws a NotImplemented exception because we are in a non-interactive experience.</returns>
|
||||
public override System.Security.SecureString ReadLineAsSecureString()
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
|
@ -161,7 +164,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// </summary>
|
||||
public override void WriteLine()
|
||||
{
|
||||
//do nothing
|
||||
//do nothing because we don't need to log empty lines
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -203,7 +206,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// <param name="message">The verbose message that is displayed.</param>
|
||||
public override void WriteVerboseLine(string message)
|
||||
{
|
||||
//Console.WriteLine(String.Format(CultureInfo.CurrentCulture, "VERBOSE: {0}", message));
|
||||
_logger.LogTrace(String.Format(CultureInfo.CurrentCulture, "VERBOSE: {0}", message));
|
||||
}
|
||||
|
||||
|
@ -213,7 +215,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell.Host
|
|||
/// <param name="message">The warning message that is displayed.</param>
|
||||
public override void WriteWarningLine(string message)
|
||||
{
|
||||
//Console.WriteLine(String.Format(CultureInfo.CurrentCulture, "WARNING: {0}", message));
|
||||
_logger.LogWarning(String.Format(CultureInfo.CurrentCulture, "WARNING: {0}", message));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
|
||||
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
|
||||
|
||||
namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
|
||||
{
|
||||
using System.Management.Automation;
|
||||
|
||||
public static class PowerShellWorkerExtensions
|
||||
{
|
||||
// This script handles when the user adds something to the pipeline.
|
||||
// It logs the item that comes and stores it as the $return out binding.
|
||||
// The last item stored as $return will be returned to the function host.
|
||||
|
||||
private static string s_LogAndSetReturnValueScript = @"
|
||||
param([Parameter(ValueFromPipeline=$true)]$return)
|
||||
|
||||
$return | Out-Default
|
||||
|
||||
Set-Variable -Name '$return' -Value $return -Scope global
|
||||
";
|
||||
|
||||
public static PowerShell SetGlobalVariables(this PowerShell ps, Hashtable triggerMetadata, IList<ParameterBinding> inputData)
|
||||
{
|
||||
try {
|
||||
// Set the global $Context variable which contains trigger metadata
|
||||
ps.AddCommand("Set-Variable").AddParameters( new Hashtable {
|
||||
{ "Name", "Context"},
|
||||
{ "Scope", "Global"},
|
||||
{ "Value", triggerMetadata}
|
||||
}).Invoke();
|
||||
|
||||
// Sets a global variable for each input binding
|
||||
foreach (ParameterBinding binding in inputData)
|
||||
{
|
||||
ps.AddCommand("Set-Variable").AddParameters( new Hashtable {
|
||||
{ "Name", binding.Name},
|
||||
{ "Scope", "Global"},
|
||||
{ "Value", binding.Data.ToObject()}
|
||||
}).Invoke();
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
ps.CleanupRunspace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static PowerShell InvokeFunctionAndSetGlobalReturn(this PowerShell ps, string scriptPath, string entryPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We need to take into account if the user has an entry point.
|
||||
// If it does, we invoke the command of that name
|
||||
if(entryPoint != "")
|
||||
{
|
||||
ps.AddScript($@". {scriptPath}").Invoke();
|
||||
ps.AddScript($@". {entryPoint}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ps.AddScript($@". {scriptPath}");
|
||||
}
|
||||
|
||||
// This script handles when the user adds something to the pipeline.
|
||||
ps.AddScript(s_LogAndSetReturnValueScript).Invoke();
|
||||
return ps;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
ps.CleanupRunspace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static Hashtable ReturnBindingHashtable(this PowerShell ps, IDictionary<string, BindingInfo> outBindings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This script returns a hashtable that contains the
|
||||
// output bindings that we will return to the function host.
|
||||
var result = ps.AddScript(BuildBindingHashtableScript(outBindings)).Invoke<Hashtable>()[0];
|
||||
ps.Commands.Clear();
|
||||
return result;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
ps.CleanupRunspace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildBindingHashtableScript(IDictionary<string, BindingInfo> outBindings)
|
||||
{
|
||||
// Since all of the out bindings are stored in variables at this point,
|
||||
// we must construct a script that will return those output bindings in a hashtable
|
||||
StringBuilder script = new StringBuilder();
|
||||
script.AppendLine("@{");
|
||||
foreach (KeyValuePair<string, BindingInfo> binding in outBindings)
|
||||
{
|
||||
script.Append("'");
|
||||
script.Append(binding.Key);
|
||||
|
||||
// since $return has a dollar sign, we have to treat it differently
|
||||
if (binding.Key == "$return")
|
||||
{
|
||||
script.Append("' = ");
|
||||
}
|
||||
else
|
||||
{
|
||||
script.Append("' = $");
|
||||
}
|
||||
script.AppendLine(binding.Key);
|
||||
}
|
||||
script.AppendLine("}");
|
||||
|
||||
return script.ToString();
|
||||
}
|
||||
|
||||
// TODO: make sure this completely cleans up the runspace
|
||||
private static void CleanupRunspace(this PowerShell ps)
|
||||
{
|
||||
ps.Commands.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
||||
{
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
|
||||
|
||||
|
@ -15,20 +16,33 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
|||
RpcLogger logger)
|
||||
{
|
||||
FunctionLoadRequest functionLoadRequest = request.FunctionLoadRequest;
|
||||
functionLoader.Load(functionLoadRequest.FunctionId, functionLoadRequest.Metadata);
|
||||
var response = new StreamingMessage()
|
||||
|
||||
// Assume success unless something bad happens
|
||||
StatusResult status = new StatusResult()
|
||||
{
|
||||
Status = StatusResult.Types.Status.Success
|
||||
};
|
||||
|
||||
// Try to load the functions
|
||||
try
|
||||
{
|
||||
functionLoader.Load(functionLoadRequest.FunctionId, functionLoadRequest.Metadata);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status.Status = StatusResult.Types.Status.Failure;
|
||||
status.Exception = e.ToRpcException();
|
||||
}
|
||||
|
||||
return new StreamingMessage()
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
FunctionLoadResponse = new FunctionLoadResponse()
|
||||
{
|
||||
FunctionId = functionLoadRequest.FunctionId,
|
||||
Result = new StatusResult()
|
||||
{
|
||||
Status = StatusResult.Types.Status.Success
|
||||
}
|
||||
Result = status
|
||||
}
|
||||
};
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
|
||||
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.PowerShell;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
||||
|
@ -20,8 +21,22 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
|||
RpcLogger logger)
|
||||
{
|
||||
InvocationRequest invocationRequest = request.InvocationRequest;
|
||||
|
||||
// Set the RequestId and InvocationId for logging purposes
|
||||
logger.SetContext(request.RequestId, invocationRequest.InvocationId);
|
||||
|
||||
// Load information about the function
|
||||
var functionInfo = functionLoader.GetInfo(invocationRequest.FunctionId);
|
||||
(string scriptPath, string entryPoint) = functionLoader.GetFunc(invocationRequest.FunctionId);
|
||||
|
||||
// Bundle all TriggerMetadata into Hashtable to send down to PowerShell
|
||||
Hashtable triggerMetadata = new Hashtable();
|
||||
foreach (var dataItem in invocationRequest.TriggerMetadata)
|
||||
{
|
||||
triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject());
|
||||
}
|
||||
|
||||
// Assume success unless something bad happens
|
||||
var status = new StatusResult() { Status = StatusResult.Types.Status.Success };
|
||||
var response = new StreamingMessage()
|
||||
{
|
||||
|
@ -33,113 +48,34 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
|||
}
|
||||
};
|
||||
|
||||
var info = functionLoader.GetInfo(invocationRequest.FunctionId);
|
||||
|
||||
// Add $Context variable, which contains trigger metadata, to the Global scope
|
||||
Hashtable triggerMetadata = new Hashtable();
|
||||
foreach (var dataItem in invocationRequest.TriggerMetadata)
|
||||
{
|
||||
triggerMetadata.Add(dataItem.Key, TypeConverter.FromTypedData(dataItem.Value));
|
||||
}
|
||||
|
||||
if (triggerMetadata.Count > 0)
|
||||
{
|
||||
powershell.AddCommand("Set-Variable").AddParameters( new Hashtable {
|
||||
{ "Name", "Context"},
|
||||
{ "Scope", "Global"},
|
||||
{ "Value", triggerMetadata}
|
||||
});
|
||||
powershell.Invoke();
|
||||
}
|
||||
|
||||
foreach (ParameterBinding binding in invocationRequest.InputData)
|
||||
{
|
||||
powershell.AddCommand("Set-Variable").AddParameters( new Hashtable {
|
||||
{ "Name", binding.Name},
|
||||
{ "Scope", "Global"},
|
||||
{ "Value", TypeConverter.FromTypedData(binding.Data)}
|
||||
});
|
||||
powershell.Invoke();
|
||||
}
|
||||
|
||||
// foreach (KeyValuePair<string, BindingInfo> binding in info.OutputBindings)
|
||||
// {
|
||||
// powershell.AddCommand("Set-Variable").AddParameters( new Hashtable {
|
||||
// { "Name", binding.Key},
|
||||
// { "Scope", "Global"},
|
||||
// { "Value", null}
|
||||
// });
|
||||
// powershell.Invoke();
|
||||
// }
|
||||
|
||||
(string scriptPath, string entryPoint) = functionLoader.GetFunc(invocationRequest.FunctionId);
|
||||
|
||||
if(entryPoint != "")
|
||||
{
|
||||
powershell.AddScript($@". {scriptPath}");
|
||||
powershell.Invoke();
|
||||
powershell.AddCommand(entryPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
powershell.AddCommand(scriptPath);
|
||||
}
|
||||
|
||||
powershell.AddScript(@"
|
||||
param([Parameter(ValueFromPipeline=$true)]$return)
|
||||
|
||||
$return | Out-Default
|
||||
|
||||
Set-Variable -Name '$return' -Value $return -Scope global
|
||||
");
|
||||
|
||||
StringBuilder script = new StringBuilder();
|
||||
script.AppendLine("@{");
|
||||
foreach (KeyValuePair<string, BindingInfo> binding in info.OutputBindings)
|
||||
{
|
||||
script.Append("'");
|
||||
script.Append(binding.Key);
|
||||
|
||||
// since $return has a dollar sign, we have to treat it differently
|
||||
if (binding.Key == "$return")
|
||||
{
|
||||
script.Append("' = ");
|
||||
}
|
||||
else
|
||||
{
|
||||
script.Append("' = $");
|
||||
}
|
||||
script.AppendLine(binding.Key);
|
||||
}
|
||||
script.AppendLine("}");
|
||||
|
||||
// Invoke powershell logic and return hashtable of out binding data
|
||||
Hashtable result = null;
|
||||
try
|
||||
{
|
||||
powershell.Invoke();
|
||||
powershell.AddScript(script.ToString());
|
||||
result = powershell.Invoke<Hashtable>()[0];
|
||||
result = powershell
|
||||
.SetGlobalVariables(triggerMetadata, invocationRequest.InputData)
|
||||
.InvokeFunctionAndSetGlobalReturn(scriptPath, entryPoint)
|
||||
.ReturnBindingHashtable(functionInfo.OutputBindings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status.Status = StatusResult.Types.Status.Failure;
|
||||
status.Exception = TypeConverter.ToRpcException(e);
|
||||
powershell.Commands.Clear();
|
||||
status.Exception = e.ToRpcException();
|
||||
return response;
|
||||
}
|
||||
powershell.Commands.Clear();
|
||||
|
||||
foreach (KeyValuePair<string, BindingInfo> binding in info.OutputBindings)
|
||||
// Set out binding data and return response to be sent back to host
|
||||
foreach (KeyValuePair<string, BindingInfo> binding in functionInfo.OutputBindings)
|
||||
{
|
||||
ParameterBinding paramBinding = new ParameterBinding()
|
||||
{
|
||||
Name = binding.Key,
|
||||
Data = TypeConverter.ToTypedData(
|
||||
result[binding.Key])
|
||||
Data = result[binding.Key].ToTypedData()
|
||||
};
|
||||
|
||||
response.InvocationResponse.OutputData.Add(paramBinding);
|
||||
|
||||
// if one of the bindings is $return we need to also set the ReturnValue
|
||||
if(binding.Key == "$return")
|
||||
{
|
||||
response.InvocationResponse.ReturnValue = paramBinding.Data;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
|||
StreamingMessage request,
|
||||
RpcLogger logger)
|
||||
{
|
||||
var response = new StreamingMessage()
|
||||
return new StreamingMessage()
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
WorkerInitResponse = new WorkerInitResponse()
|
||||
|
@ -25,7 +25,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Requests
|
|||
}
|
||||
}
|
||||
};
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,12 +27,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"host: {arguments.Host}");
|
||||
Console.WriteLine($"port: {arguments.Port}");
|
||||
Console.WriteLine($"workerId: {arguments.WorkerId}");
|
||||
Console.WriteLine($"requestId: {arguments.RequestId}");
|
||||
Console.WriteLine($"grpcMaxMessageLength: {arguments.GrpcMaxMessageLength}");
|
||||
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
RequestId = _requestId,
|
||||
RpcLog = new RpcLog()
|
||||
{
|
||||
Exception = exception == null ? null : TypeConverter.ToRpcException(exception),
|
||||
Exception = exception == null ? null : exception.ToRpcException(),
|
||||
InvocationId = _invocationId,
|
||||
Level = ConvertLogLevel(logLevel),
|
||||
Message = formatter(state, exception)
|
||||
|
|
|
@ -7,24 +7,29 @@ using static Microsoft.Azure.WebJobs.Script.Grpc.Messages.TypedData;
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
||||
{
|
||||
public class TypeConverter
|
||||
public static class TypeExtensions
|
||||
{
|
||||
public static object ToObject (TypedData data)
|
||||
public static object ToObject (this TypedData data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (data.DataCase)
|
||||
{
|
||||
case DataOneofCase.Json:
|
||||
// consider doing ConvertFrom-Json
|
||||
return data.Json;
|
||||
return JsonConvert.DeserializeObject<Hashtable>(data.Json);
|
||||
case DataOneofCase.Bytes:
|
||||
return data.Bytes;
|
||||
case DataOneofCase.Double:
|
||||
return data.Double;
|
||||
case DataOneofCase.Http:
|
||||
return ToHttpContext(data.Http);
|
||||
return data.Http.ToHttpContext();
|
||||
case DataOneofCase.Int:
|
||||
return data.Int;
|
||||
case DataOneofCase.Stream:
|
||||
|
@ -38,7 +43,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
}
|
||||
}
|
||||
|
||||
public static TypedData ToTypedData(object value)
|
||||
public static TypedData ToTypedData(this object value)
|
||||
{
|
||||
TypedData typedData = new TypedData();
|
||||
|
||||
|
@ -55,7 +60,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
else if(LanguagePrimitives.TryConvertTo<HttpResponseContext>(
|
||||
value, out HttpResponseContext http))
|
||||
{
|
||||
typedData.Http = ToRpcHttp(http);
|
||||
typedData.Http = http.ToRpcHttp();
|
||||
}
|
||||
else if (LanguagePrimitives.TryConvertTo<Hashtable>(
|
||||
value, out Hashtable hashtable))
|
||||
|
@ -65,6 +70,8 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
else if (LanguagePrimitives.TryConvertTo<string>(
|
||||
value, out string str))
|
||||
{
|
||||
// Attempt to parse the string into json. If it fails,
|
||||
// fallback to storing as a string
|
||||
try
|
||||
{
|
||||
typedData.Json = JsonConvert.SerializeObject(str);
|
||||
|
@ -77,7 +84,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
return typedData;
|
||||
}
|
||||
|
||||
public static HttpRequestContext ToHttpContext (RpcHttp rpcHttp)
|
||||
public static HttpRequestContext ToHttpContext (this RpcHttp rpcHttp)
|
||||
{
|
||||
var httpRequestContext = new HttpRequestContext
|
||||
{
|
||||
|
@ -91,35 +98,40 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
|
|||
|
||||
if (rpcHttp.Body != null)
|
||||
{
|
||||
httpRequestContext.Body = ToObject(rpcHttp.Body);
|
||||
httpRequestContext.Body = rpcHttp.Body.ToObject();
|
||||
}
|
||||
|
||||
if (rpcHttp.RawBody != null)
|
||||
{
|
||||
httpRequestContext.Body = ToObject(rpcHttp.RawBody);
|
||||
httpRequestContext.Body = rpcHttp.RawBody.ToObject();
|
||||
}
|
||||
|
||||
return httpRequestContext;
|
||||
}
|
||||
|
||||
public static RpcHttp ToRpcHttp (HttpResponseContext httpResponseContext)
|
||||
public static RpcHttp ToRpcHttp (this HttpResponseContext httpResponseContext)
|
||||
{
|
||||
var rpcHttp = new RpcHttp
|
||||
{
|
||||
StatusCode = httpResponseContext.StatusCode?? "200"
|
||||
StatusCode = httpResponseContext.StatusCode
|
||||
};
|
||||
|
||||
if (httpResponseContext.Body != null)
|
||||
{
|
||||
rpcHttp.Body = httpResponseContext.Body;
|
||||
rpcHttp.Body = httpResponseContext.Body.ToTypedData();
|
||||
}
|
||||
|
||||
rpcHttp.Headers.Add(httpResponseContext.Headers);
|
||||
// Add all the headers. ContentType is separated for convenience
|
||||
foreach (DictionaryEntry item in httpResponseContext.Headers)
|
||||
{
|
||||
rpcHttp.Headers.Add(item.Key.ToString(), item.Value.ToString());
|
||||
}
|
||||
rpcHttp.Headers.Add("content-type", httpResponseContext.ContentType);
|
||||
|
||||
return rpcHttp;
|
||||
}
|
||||
|
||||
public static RpcException ToRpcException (Exception exception)
|
||||
public static RpcException ToRpcException (this Exception exception)
|
||||
{
|
||||
return new RpcException
|
||||
{
|
|
@ -9,7 +9,6 @@ using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
|
|||
using Azure.Functions.PowerShell.Worker.Messaging;
|
||||
using Microsoft.PowerShell;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
|
||||
using System.Collections;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.Requests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Azure.Functions.PowerShellWorker.PowerShell;
|
||||
|
@ -33,10 +32,12 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
}
|
||||
StartupArguments startupArguments = StartupArguments.Parse(args);
|
||||
|
||||
// Initialize Rpc client, logger, and PowerShell
|
||||
s_client = new FunctionMessagingClient(startupArguments.Host, startupArguments.Port);
|
||||
s_Logger = new RpcLogger(s_client);
|
||||
InitPowerShell();
|
||||
|
||||
// Send StartStream message
|
||||
var streamingMessage = new StreamingMessage() {
|
||||
RequestId = startupArguments.RequestId,
|
||||
StartStream = new StartStream() { WorkerId = startupArguments.WorkerId }
|
||||
|
@ -49,28 +50,21 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
|
|||
|
||||
private static void InitPowerShell()
|
||||
{
|
||||
// var events = new StreamEvents(s_Logger);
|
||||
var host = new Host(s_Logger);
|
||||
var host = new AzureFunctionsHost(s_Logger);
|
||||
|
||||
s_runspace = RunspaceFactory.CreateRunspace(host);
|
||||
s_runspace.Open();
|
||||
s_ps = System.Management.Automation.PowerShell.Create(InitialSessionState.CreateDefault());
|
||||
s_ps.Runspace = s_runspace;
|
||||
|
||||
// Setup Stream event listeners
|
||||
// s_ps.Streams.Debug.DataAdded += events.DebugDataAdded;
|
||||
// s_ps.Streams.Error.DataAdded += events.ErrorDataAdded;
|
||||
// s_ps.Streams.Information.DataAdded += events.InformationDataAdded;
|
||||
// s_ps.Streams.Progress.DataAdded += events.ProgressDataAdded;
|
||||
// s_ps.Streams.Verbose.DataAdded += events.VerboseDataAdded;
|
||||
// s_ps.Streams.Warning.DataAdded += events.WarningDataAdded;
|
||||
|
||||
s_ps.AddScript("$PSHOME");
|
||||
//s_ps.AddCommand("Set-ExecutionPolicy").AddParameter("ExecutionPolicy", ExecutionPolicy.Unrestricted).AddParameter("Scope", ExecutionPolicyScope.Process);
|
||||
var result = s_ps.Invoke<string>();
|
||||
s_ps.Commands.Clear();
|
||||
s_ps.Invoke<string>();
|
||||
|
||||
Console.WriteLine(result[0]);
|
||||
// Add HttpResponseContext namespace so users can reference
|
||||
// HttpResponseContext without needing to specify the full namespace
|
||||
s_ps.AddScript($"using namespace {typeof(HttpResponseContext).Namespace}").Invoke();
|
||||
s_ps.Commands.Clear();
|
||||
}
|
||||
|
||||
private static async Task ProcessEvent()
|
||||
|
|
Загрузка…
Ссылка в новой задаче