我如何从HTTP触发Azure函数运行.cmd文件

时间:2019-05-28 13:44:14

标签: c# azure azure-functions

我想使用azure函数运行.cmd文件。我想在后台进程而不是azure函数的主进程中运行它。

我已经使用Kudu编辑器将cmd文件保存在azure功能平台上。我可以在本地运行它,但是在部署后根本无法运行(我也没有收到任何错误)。

      string cmdFileLocation = @"D:\home\jobs.cmd";
       Process proc = new Process();
       ProcessStartInfo info = new ProcessStartInfo();
       try
       {
           info.FileName = cmdFileLocation;
           info.Arguments = name;
           info.WindowStyle = ProcessWindowStyle.Minimized;
           info.UseShellExecute = false;
           proc.StartInfo = info;
           info.RedirectStandardOutput = true;
           info.RedirectStandardError = true;
           proc.Start();
           proc.WaitForExit();
        }
        catch (Exception ex)
        {
            log.LogInformation("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
        }

为进行测试,cmd文件中包含curl命令。卷曲将在使用azure函数的本地计算机上触发,因为我可以看到请求即将到来(https://webhook.site),但在执行任何操作后都没有任何反应。

1 个答案:

答案 0 :(得分:1)

这是使任何.exe / .cmd作为Web服务运行的简便方法。您只需在配置文件中为exe / cmd指定输入参数。您可以通过指定要下载的URL将二进制文件用作exe的输入。

配置文件如下所示

{
    "name": "consoleAppToFunctions",
    "input": {
        "command": "ffmpeg.exe -i {inputFile} {output1}",
        "arguments": {
            "inputFile": {
                "url": "https://1drv.ms/v/<link-to-file>"
                //binary file input
            },
            "output1": {
                "localfile": "out.mp3"
                //stored in a temp folder
            }
        }
    },
    "output": {
        "folder": "outputFolder",
        "binaryFile": {
            "returnFile": "*.mp3",
            "returnFileName": "yourFile.mp3"
        }
    }
}

以下是相同的AZURE功能代码:

#r "Newtonsoft.Json" 
using System.Net;
using Newtonsoft.Json;
using System.IO;
using System.Diagnostics;

//Code from https://github.com/Azure-Samples/functions-dotnet-migrating-console-apps/edit/master/code/run.csx
//Written by Ambrose http://github.com/efficientHacks and Murali http://github.com/muralivp

public class ExeArg
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Value { get; set; }
}

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    string localPath = req.RequestUri.LocalPath;
    string functionName = localPath.Substring(localPath.LastIndexOf('/')+1);

    var json = File.ReadAllText(string.Format(@"D:\home\site\wwwroot\{0}\FunctionConfig.json",functionName));

    var config = JsonConvert.DeserializeObject<dynamic>(json);

    var functionArguments = config.input.arguments;
    var localOutputFolder = Path.Combine(@"d:\home\data", config.output.folder.Value, Path.GetFileNameWithoutExtension(Path.GetTempFileName()));
    Directory.CreateDirectory(localOutputFolder);
    var workingDirectory = Path.Combine(@"d:\home\site\wwwroot", functionName + "\\bin");
    Directory.SetCurrentDirectory(workingDirectory);//fun fact - the default working directory is d:\windows\system32

    var command = config.input.command.Value;

    var argList = new List<ExeArg>();

    //Parse the config file's arguments
    //handle file system, local file etc. and construct the input params for the actual calling of the .exe
    foreach (var arg in functionArguments)
    {
        var exeArg = new ExeArg();
        exeArg.Name = arg.Name;
        var value = (Newtonsoft.Json.Linq.JObject)arg.Value;
        var property = (Newtonsoft.Json.Linq.JProperty)value.First;
        exeArg.Type = property.Name;
        exeArg.Value = property.Value.ToString();

        var valueFromQueryString = await getValueFromQuery(req, exeArg.Name);

        log.Info("valueFromQueryString name=" + exeArg.Name);
        log.Info("valueFromQueryString val=" + valueFromQueryString);
        if(!string.IsNullOrEmpty(valueFromQueryString))
        {
            exeArg.Value = valueFromQueryString;
            log.Info(exeArg.Name + " " + valueFromQueryString);
        }

        if(exeArg.Type.ToLower() == "localfile" || exeArg.Type.ToLower() == "localfolder")
        {
            exeArg.Value = Path.Combine(localOutputFolder, exeArg.Value);
            exeArg.Type = "string";
        }
        if(string.IsNullOrEmpty(exeArg.Value))
        {
            //throw exception here
        }
        argList.Add(exeArg);
    }

    //call the exe
    Dictionary<string, string> paramList = ProcessParameters(argList, localOutputFolder);
    foreach (string parameter in paramList.Keys)
    {
        command = command.Replace(parameter, paramList[parameter]);
    }
    string commandName = command.Split(' ')[0];
    string arguments = command.Split(new char[] { ' ' }, 2)[1];
    log.Info("the command is " + command);
    log.Info("the working dir is " + workingDirectory);
    Process p = new Process();
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.FileName = commandName; 
    p.StartInfo.Arguments = arguments;
    p.Start();
    string output = p.StandardOutput.ReadToEnd();
    p.WaitForExit();
    File.WriteAllText(localOutputFolder+"\\out.txt",output);

    //handle return file
    log.Info("handling return file localOutputFolder=" + localOutputFolder);
    string outputFile = config.output.binaryFile.returnFile.Value;
    string outputFileName = config.output.binaryFile.returnFileName.Value;
    var path = Directory.GetFiles(localOutputFolder, outputFile)[0];

    log.Info("returning this file " + path);
    var result = new FileHttpResponseMessage(localOutputFolder);
    var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = outputFileName
    };

    return result;
}

private static Dictionary<string, string> ProcessParameters(List<ExeArg> arguments, string outputFolder)
{
    Dictionary<string, string> paramList = new Dictionary<string, string>();
    foreach (var arg in arguments)
    {
        switch (arg.Type)
        {
            case "url":
                string downloadedFileName = ProcessUrlType((string)arg.Value, outputFolder);
                paramList.Add("{" + arg.Name + "}", downloadedFileName);
                break;
            case "string":
                paramList.Add("{" + arg.Name + "}", arg.Value.ToString());
                break;
            default:
                break;
        }
    }
    return paramList;
}

//you can modify this method to handle different URLs if necessary
private static string ProcessUrlType(string url, string outputFolder)
{
    Directory.CreateDirectory(outputFolder);
    string downloadedFile = Path.Combine(outputFolder, Path.GetFileName(Path.GetTempFileName()));
    //for oneDrive links 
    HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
    webRequest.AllowAutoRedirect = false;
    WebResponse webResp = webRequest.GetResponse();
    webRequest = (HttpWebRequest)HttpWebRequest.Create(webResp.Headers["Location"].Replace("redir", "download"));
    webResp = webRequest.GetResponse();
    string fileUrl = webResp.Headers["Content-Location"];

    WebClient webClient = new WebClient();
    webClient.DownloadFile(fileUrl, downloadedFile);
    return downloadedFile;
}

private async static Task<string> getValueFromQuery(HttpRequestMessage req, string name)
{
    // parse query parameter
    string value = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, name, true) == 0)
        .Value;

    //if not found in query string, look for it in the body (json)
    // Get request body
    dynamic data = await req.Content.ReadAsAsync<object>();

    // Set name to query string or body data
    value = value ?? data?[name];
    return value;
}

//this is to delete the folder after the response
//thanks to: https://stackoverflow.com/a/30522890/2205372
public class FileHttpResponseMessage : HttpResponseMessage
{
    private string filePath;

    public FileHttpResponseMessage(string filePath)
    {
        this.filePath = filePath;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        Content.Dispose();
        Directory.Delete(filePath,true);
    }
}

在这里您可以找到更多关于this的信息。希望对您有所帮助。