1

I am trying to run a bash script from inside a windows forms application. The code I have looks like this:

            // Command String
            string flashNVS =    "c:\\msys32\\usr\\bin\\env MSYSTEM=MINGW32 " +
                                 "c:\\msys32\\usr\\bin\\bash -l -c " +
                                 "\"cd " + unixProdPath + 
                                 " && ./provision-device-secure " + deviceId + " \"";

            cmdProcess.ExecuteCommandSync(flashNVS);

With the cmdProcess code:

public static void ExecuteCommandSync(object command)
        {
            flashOK = 0;
            try
            {
                // create the ProcessStartInfo using "cmd" as the program to be run, and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows, and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
                // The following commands are needed to redirect the standard output. 
                //This means that it will be redirected to the Process.StandardOutput StreamReader.
                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.RedirectStandardError = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                //resultString = result;
                if ( (result.IndexOf("Hard resetting via RTS pin") > 0) ||  // Success FW Flash OR NVS Flash!
                     (result.IndexOf("Hash of data verified") > 0) )   // Success NVS Flash
                {
                    Console.WriteLine("SUCCESS : FlashOK = 1");
                    flashOK = 1;
                }

                // Display the command output.
                Console.WriteLine(result);
            }
            catch (Exception objException)
            {
                // Log the exception
                Console.WriteLine("ExecuteCommandSync failed" + objException.Message);
            }
        }

The file ./provision-device-secure script does a lot of stuff. And it works fine except for one line:

$IDF_PATH/components/esptool_py/esptool/esptool.py -p $ESPPORT --before default_reset --after no_reset write_flash 0x3E0000 mfg_config_encrypted.bin 0x3E8000 nvs_keys.bin

The completely baffling thing is that if I run the script directly from inside git bash,it works fine. But from inside the windows forms application, that line doesn't complete properly... I need it to run as part of a windows app I'm writing. Any suggestions of how to tweak my code to get this to run? And an explanation of why it doesn't?

Just to describe the failure. In the git bash, it executes with this output:

Created encryption keys: ===>  C:\Users\thebi\esp\esp32_repo\11_SN_WI_003_WiFi\prod\keys\nvs_keys.bin

Creating NVS binary with version: V2 - Multipage Blob Support Enabled

Created NVS binary: ===> C:\Users\thebi\esp\esp32_repo\11_SN_WI_003_WiFi\prod\mfg_config_encrypted.bin

but in the windows forms it only does this:

Created encryption keys: ===>  C:/Users/thebi/esp/esp32_repo/11_SN_WI_003_WiFi/prod/keys/nvs_keys.bin

Creating NVS binary with version: V2 - Multipage Blob Support Enabled

And the output file (mfg_config_encrypted.bin) is zero length...

3
  • I'm surprised that on the one hand, you build together a complex command to be executed, on the other hand you never print out the actual content for this command (for logging/debugging). How can you be sure that flashNVS contains the correct command? Commented Dec 14, 2021 at 11:12
  • @user1934428 Added Console.WriteLine(flashNVS); -> c:\msys32\usr\bin\env MSYSTEM=MINGW32 c:\msys32\usr\bin\bash -l -c "cd C:/Users/thebi/esp/esp32_repo/11_SN_WI_003_WiFi/prod/ && ./provision-device-secure test-dev " Commented Dec 14, 2021 at 22:14
  • I've never worked with msys32, but my guess is that if you envoke bash via env, you would have to use forward slashes to specify the bash path, because env probably can't deal with the backslashes. Commented Dec 15, 2021 at 8:15

2 Answers 2

3

First of all you should quote your parameters as they have spaces inside. Second of all... I will just give you class that I have written:

public delegate void ProcessMessageDelegate(object sender, string msg);

public class ProcessManager
{
    public event ProcessMessageDelegate OnOutputData;
    public event ProcessMessageDelegate OnErrorData;
    public event EventHandler OnProcessEnd;

    Process process;
    List<string> outputBuffer = new List<string>();

    public string GetProcessOutput(string cmdName, List<string> args)
    {
        InitProcess(cmdName, args);
        string result = "";

        if (process.Start())
        {
            try
            {
                process.BeginOutputReadLine();
                process.StandardError.ReadToEnd();
                process.WaitForExit();
                return OutputBufferToStr();
            }
            catch (Exception ex)
            {
                if (string.IsNullOrWhiteSpace(result))
                {
                    OnErrorData?.Invoke(this, ex.Message);
                    return null;
                }
                else
                    return result;
            }

        }

        return result;
    }

    public void RunProcessAsync(string cmdName, List<string> args)
    {
        InitProcess(cmdName, args);

        if (process.Start())
        {
            process.BeginOutputReadLine();
            process.StandardError.ReadToEnd();
        }

    }

    void InitProcess(string cmdName, List<string> args)
    {
        ProcessStartInfo psi = new ProcessStartInfo();

        psi.FileName = cmdName;
        List<string> escapedArgs = new List<string>();
        foreach (var arg in args)
        {
            string validArg = arg;
            if (validArg.Contains(" "))
                validArg = $"\"{validArg}\"";

            escapedArgs.Add(validArg);

        }

        psi.Arguments = string.Join(" ", escapedArgs);

        psi.ErrorDialog = true;
        psi.UseShellExecute = false;
        psi.RedirectStandardError = true;
        psi.RedirectStandardOutput = true;
        psi.WindowStyle = ProcessWindowStyle.Hidden;
        psi.CreateNoWindow = true;

        process = new Process();
        process.StartInfo = psi;
        process.OutputDataReceived += Process_OutputDataReceived;
        process.Exited += Process_Exited;
        process.Disposed += Process_Disposed;
        process.EnableRaisingEvents = true;

        outputBuffer.Clear();
    }

    

    string OutputBufferToStr()
    {
        return string.Join(Environment.NewLine, outputBuffer);
    }

    

    private void Process_Disposed(object sender, EventArgs e)
    {
        OnProcessEnd?.Invoke(this, EventArgs.Empty);
    }

    private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        outputBuffer.Add(e.Data);
        OnErrorData?.Invoke(this, e.Data);
    }

    private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        outputBuffer.Add(e.Data);
        OnOutputData?.Invoke(this, e.Data);
    }

    private void Process_Exited(object sender, EventArgs e)
    {
        if (process != null)
            process.Dispose();
        OnProcessEnd?.Invoke(this, EventArgs.Empty);
    }


}


Feel free to use it. Now, you have here two main methods: GetProcessOutput that runs process synchronuosly and returns whole output to you. You just pass path to the process and list of parameters.

The second method is RunProcessAsync - it runs the process asynchrouosly. You call it the same way, BUT you have to connect to events: OnOutputData - it's called everyrtime that something is written to output OnErrorData - when error occurs OnProcessEnd - when the process ends.

I hope you'll like it.

Sign up to request clarification or add additional context in comments.

1 Comment

While it didn't fix the immediate problem, that is a very useful piece of code. Thanks for sharing.
0

Just going to throw this out. I'm on very shaky ground here as I don't really understand this stuff in any detail. But what eventually worked was to change the command to select git bash, not MINW32. Like this:

 string flashNVS = "\"C:\\Program Files\\Git\\git-bash.exe\" -c \"cd " + unixProdPath + 
            " && ./provision-device-secure test-dev > unixLog.txt \"'";

 Console.WriteLine(flashNVS);

I'm not going to accept my own answer though, because it doesn't explain the why part of this. If anyone can fill in the blanks, that would be great.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.