0

I want to run .cmd file using azure function. I want to run this in background process instead of main process of azure function.

I have already saved the cmd file on azure function platform using Kudu Editor. I can run this locally but after deploying its not working at all (I am also not getting any error).

      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());
        }

For testing there is curl command in cmd file. The curl will trigger on local machine using azure function as I can see the request coming (https://webhook.site) but after deplying nothing happens.

Rajat Sharma
  • 47
  • 1
  • 15

1 Answers1

1

Here is an easy way of getting any .exe/.cmd running as a web service. You just specify the input parameters to your exe/cmd in a configuration file. You can use binary files as inputs to the exe by specifying a URL to download it from.

Here's what the configuration file looks like

{
    "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"
        }
    }
}

Here is the AZURE FUNCTION CODE for the same:

#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);
    }
}

Here you can find more on this. Hope it helps.

Mohit Verma
  • 5,140
  • 2
  • 12
  • 27
  • I'm using BlobTriggered Function app which calls the cmd script via function app, and it's working fine for the first ever blob, but for the second blob cmd file is not getting executed it seems. I am logging messages in Kudos output file, it's showing only logs from first call. Two blobs are coming simultaneously with milli seconds of differences. Any help? – shary.sharath May 17 '21 at 10:19