C# Create symlink through bash does nothing - c#

I found this function to run any command through bash from C#. So I tried passing it a command to create a symlink, but it's not doing anything. If I run that same command directly in a Terminal, it works. I tried running other commands like "ls" and that printed the proper items. Why isn't the symlink command working?
string output = ExecuteBashCommand("ln -s \"/Users/tim/academy-v2/Shared/Client Code\" \"/Users/tim/academy-v2/Shared Assets/Assets/Shared Code/Client Code\"");
UnityEngine.Debug.Log(output); // Prints "" like it worked, but didn't actually do anything.
...
static string ExecuteBashCommand(string command)
{
command = command.Replace("\"", "\"\"");
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = "-c \"" + command + "\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
proc.WaitForExit();
return proc.StandardOutput.ReadToEnd();
}

Replacing the command with this seems to work, though not sure why.
"ln -s /Users/tim/academy-v2/Shared/Client\\ Code /Users/tim/academy-v2/Shared\\ Assets/Assets/Shared\\ Code/Client\\ Code"

Related

Execution of a command via Process.Start fails, but works when I execute it directly in the shell

I am trying to execute the following command on Debian 10 system with dotnet 5:
bash -c 'cd /home/test/script1 && nohup pipenv run python main.py' > /dev/null 2>&1 & echo $! > script1.pid
This works flawlessly if the command is executed directly on the shell but not if it is executed via Process.Start():
var fileName = "bash";
var arguments = "-c 'cd /home/test/script1 && nohup pipenv run python main.py' > /dev/null 2>&1 & echo $! > script1.pid";
using var process = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
ErrorDialog = false
}
};
process.Start()
Unfortunately, I do not get an error message. The command is simply not executed and is terminated with the exit code 1. Does anyone have an idea what could be wrong here?

C# Create Detached Process on Linux

I am writing a software update process on Linux. Application is .NET 5 RC1 (Sept 15 2020 release). When a certain packet is received by my application, it downloads the software update to a sub-folder then spawns off the executable to perform the software update.
Unfortunately, using Process.Start and ProcessStartInfo seems to create a process that is attached to the main process. Since the software update must stop the process in order to update it, it also gets stopped because it is a child of the process, having been spawned via Process.Start.
How do I create a detached process on Linux? On Windows I am using PInvoke and the CreateProcess API with the DETACHED_PROCESS flag, see the following:
var processInformation = new ProcessUtility.PROCESS_INFORMATION();
var startupInfo = new ProcessUtility.STARTUPINFO();
var sa = new ProcessUtility.SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
CreateProcess(null, "\"" + fileName + "\" " + arguments, ref sa, ref sa, false, DETACHED_PROCESS, IntPtr.Zero, Path.GetDirectoryName(fileName), ref startupInfo, out processInformation);
Here is my code for Linux. I had read that appending & to a process on Linux creates it detached, but that does not appear to be the case.
ProcessStartInfo info = new ProcessStartInfo
{
// Linux uses " &" to detach the process
Arguments = arguments + " &",
CreateNoWindow = true,
FileName = fileName,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
Process.Start(info);
I was unable to get nohup or disown to work from C#. Killing the parent process always resulted in the child process being terminated as well.
I ended up using at, which can be installed via sudo apt install at. The atd service is installed and will stay running even when rebooted.
Here is the C# code that I used:
// the following assumes `sudo apt install at` has been run.
string fileName = "[your process to execute]";
string arguments = "[your command line arguments for fileName]";
string argumentsEscaped = arguments.Replace("\"", "\\\"");
string fullArgs = $"-c \"echo sudo \\\"{fileName}\\\" {argumentsEscaped} | at now\"";
ProcessStartInfo info = new()
{
Arguments = fullArgs,
CreateNoWindow = true,
FileName = "/bin/bash",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
using var process = Process.Start(info);
process.WaitForExit();
// make sure to check process.ExitCode == 0
For me, setsid in combination with & makes a spawned child process out-living its parent process when invoked via sh -c.
Example:
var command = $"dotnet \"PathToDll\" param1 param2";
process.StartInfo = new ProcessStartInfo
{
Arguments = $"-c \"setsid {command.Replace("\"", "\\\"")} &\"",
CreateNoWindow = true,
FileName = "/bin/sh",
};
process.Start();
This was tested on Debian and Ubuntu.
Slightly refactored version of #jjxtra solution, so it's easier to understand what's going on in the arguments.
Btw, the echo is not an example, but the way of executing at command.
string command = $"actual command to run";
string atdCommand = $#"echo \""{command}\"" | at now";
string bashCommand = $#"-c ""{atdCommand}"" ";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = bashCommand,
...
};

Not getting correct error code for process

I´m trying to get the return code of a batch script, which intern calls a couple other batch and exe files. When I execute the script in a CMD window and print the errorlevel, I get the correct error code, however when I do the same in C# with a process, I always get 0 as the error code.
This is my C# code
private Process ExecuteBatchFile(string batchFile)
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = true,
FileName = "CMD.exe",
WorkingDirectory = Constants.ToolsPath,
Arguments = $"/c \"{batchFile} & pause\""
}
};
process.Start();
return process;
}
batchFile = $"testScript.bat -tns {Project.TnsName} & echo Error: %errorlevel%";
The output I get for the echo is Error: 0 and the process.ExitCode value is 0
If I open a CMD window and enter
cmd.exe /c "testScript.bat -tns MYTNS & echo Error: %errorlevel% & pause"
I get the correct errorlevel value.
I´m guessing it has something to do with the batch script but I don´t understand why it works in a CMD window but not in a C# Process, especially since I´m using the same method to connect a network drive and to execute an exe file.
Edit: Code without using CMD:
private Process ExecuteBatchFile(string batchFile, string args)
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = true,
FileName = batchfile,
WorkingDirectory = Constants.ToolsPath,
Arguments = args
}
};
process.Start();
return process;
}
batchFile = "testScript.bat";
args = $"-tns {Project.TnsName}";
I suspect your .bat file has a line somewhere like exit /b 1. When running this .bat file from a cmd instance, the /b flag allows the .bat file to exit without closing the parent cmd process (great for running manually!).
Unfortunately, that means that when you do Process.Start("testScript.bat");, under the hood C# uses cmd to run your bat. The .bat file exits with the /b flag, signaling to the parent cmd process it shouldn't crash and therefore it exits "successfully" (exit code 0).
You have two options:
Remove the /b flag (which will correctly return the exit code .bat > cmd > process.ExitCode) - though this will mean that executing the .bat manually via a cmd instance will terminate the parent cmd upon exit
Add to your C# code to write a wrapper .bat that will pipe all args to your bat file and properly return %exitcode%:
private int ExecuteBatWithWrapper(string batFile, string args){
string runnerPath = Path.Combine(Path.GetDirectoryName(batFile), "runner.bat");
File.WriteAllText(runnerPath, $"call {batFile} %*\nexit %ERRORLEVEL%");
Process process = Process.Start(runnerPath, args); //Alternatively construct with ProcessStartInfo
File.Delete(runnerPath);
return process.ExitCode;
}
(of course, if this is used in any sort of important environment, you should incorporate that into some sort of using statement that upon disposal deletes the runner, but this is just proof-of-concept)
See https://bytes.com/topic/c-sharp/answers/511381-system-diagnostics-process-bat-file-always-returns-exit-code-0-a#post1989782 for a similar post

Retrieve cmd prompt output from PsExec command?

Use case: I am checking certain credentials on a remote system by running commands via PsExec (i.e. for this example, I am trying to retrieve the KB articles currently installed on the remote system).
I have the following to retrieve command output:
public string GetCmDOutput(string cmd)
ProcessStartInfo startInfo = new ProcessStartInfo("control", cmd)
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
string output = string.Empty;
Process process = Process.Start(startInfo);
process.OutputDataReceived += (sender, e) => output = string.Concat(output, e.Data);
process.BeginOutputReadLine();
process.Start();
process.WaitForExit();
Delay.Milliseconds(1500) //API-specific delay
return output;
}
Whenever I use GetCmdOutput() to run a command locally it works like a charm, but if I try to run a command with PsExec, my output is empty.
For instance, I ran the following:
string cmd = #"\psexec.exe \\remoteComputerName -u username -p password -c cmd /c wmic qfe";
GetCmdOutput(cmd);
Report.Info(cmd); //API-specific reporting
And an empty string was returned.
After playing around with this for a couple of hours, I feel I may need a second set of eyes. What might be causing this issue?
I have run into this same problem. My solution was to run cmd and have it call psexec. I have psexec's output saved to a temp file for further manipulation. My code is returning a List.
public List<string> ExecutePSExec(string hostname)
{
List<string> recordNames = new List<string>();
string command = #"\\path\to\psexec.exe /accepteula \\" + hostname + ". exe-to-run-remotely";
try
{
string location = AppDomain.CurrentDirectory.BaseDirectory;
string cmdWithFileOutput = string.Format("{0} >{1}temp.log", command, location);
procStartInfo.UseShellExecute = true;
procStartInfo.CreateNoWindow = true;
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process proc = new Process();
proc.StartInfo = procStartInfo;
proc.Start();
proc.WaitForExit();
// Read file contents, manipulate data and then delete temp file here
}
catch (Exception e)
{
Console.WriteLine("Failure to run psexec: {0}", e.Message);
}
return recordNames;
}
NOTE: I ran into another problem and found out that running psexec this way requires the remote hostname (not IP Address) in the command to end in a period \\" + hostname + ".
This code assumes you can run psexec on the remote machine as your current user.

To run cmd as administrator along with command?

Here is my code:
try
{
ProcessStartInfo procStartInfo = new ProcessStartInfo(
"cmd.exe",
"/c " + command);
procStartInfo.UseShellExecute = true;
procStartInfo.CreateNoWindow = true;
procStartInfo.Verb = "runas";
procStartInfo.Arguments = "/env /user:" + "Administrator" + " cmd" + command;
///command contains the command to be executed in cmd
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
I want to keep
procStartInfo.UseShellExecute = true
procStartInfo.RedirectStandardInput = false;
Is it possible to execute the command without using process.standardinput?
I try to execute command I've passed in argument but the command does not executes.
As #mtijn said you've got a lot going on that you're also overriding later. You also need to make sure that you're escaping things correctly.
Let's say that you want to run the following command elevated:
dir c:\
First, if you just ran this command through Process.Start() a window would pop open and close right away because there's nothing to keep the window open. It processes the command and exits. To keep the window open we can wrap the command in separate command window and use the /K switch to keep it running:
cmd /K "dir c:\"
To run that command elevated we can use runas.exe just as you were except that we need to escape things a little more. Per the help docs (runas /?) any quotes in the command that we pass to runas need to be escaped with a backslash. Unfortunately doing that with the above command gives us a double backslash that confused the cmd parser so that needs to be escaped, too. So the above command will end up being:
cmd /K \"dir c:\\\"
Finally, using the syntax that you provided we can wrap everything up into a runas command and enclose our above command in a further set of quotes:
runas /env /user:Administrator "cmd /K \"dir c:\\\""
Run the above command from a command prompt to make sure that its working as expected.
Given all that the final code becomes easier to assemble:
//Assuming that we want to run the following command:
//dir c:\
//The command that we want to run
string subCommand = #"dir";
//The arguments to the command that we want to run
string subCommandArgs = #"c:\";
//I am wrapping everything in a CMD /K command so that I can see the output and so that it stays up after executing
//Note: arguments in the sub command need to have their backslashes escaped which is taken care of below
string subCommandFinal = #"cmd /K \""" + subCommand.Replace(#"\", #"\\") + " " + subCommandArgs.Replace(#"\", #"\\") + #"\""";
//Run the runas command directly
ProcessStartInfo procStartInfo = new ProcessStartInfo("runas.exe");
procStartInfo.UseShellExecute = true;
procStartInfo.CreateNoWindow = true;
//Create our arguments
string finalArgs = #"/env /user:Administrator """ + subCommandFinal + #"""";
procStartInfo.Arguments = finalArgs;
//command contains the command to be executed in cmd
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = procStartInfo;
proc.Start();
}
why are you initializing the process object with arguments and then later on override those Arguments? and btw: the last bit where you set Arguments you concatenate 'command' right upto 'cmd', that doesn't make much sense and might be where it fails (looks like you're missing a space).
Also, you are currently using the standard command line, you might want to look into using the runas tool instead. you can also call runas from command line.
Also, why are you running 'command' from the command line? why not start it directly from Process.Start with admin privileges supplied then and there? here's a bit of pseudocode:
Process p = Process.Start(new ProcessStartInfo()
{
FileName = <your executable>,
Arguments = <any arguments>,
UserName = "Administrator",
Password = <password>,
UseShellExecute = false,
WorkingDirectory = <directory of your executable>
});

Categories