I want to run bat file on using System.Diagnostics and I did. But I have a problem.
public static void RunMiningProgram(string appPath, string batFilePath)
{
for (int i = 0; i < 2; i++)
{
using (Process p = new Process())
{
p.StartInfo.FileName = $"{appPath}\\{batFilePath}";
p.StartInfo.WorkingDirectory = appPath;
p.Start();
}
}
}
var path = #"D:\PROJECTS\ies-disk\asp-net-core\IesDisk.ApiProcess\wwwroot\C.bat";
var appath = #"D:\PROJECTS\ies-disk\asp-net-core\IesDisk.ApiProcess\wwwroot";
RunMiningProgram(apppath, path);
When we run the program, the results of the bat files appear on the console screen that Kestrel opens. How can I open two different cmd applications instead of showing them in the Kestrel console.
if i visualize my question i want two different cmd screens to open.
public static void RunMiningProgram(string appPath, string batFilePath)
{
for (int i = 0; i < 2; i++)
{
Process.Start("cmd.exe", $"/c start /D {appPath} {appPath}{batFilePath}");
}
}
static void Main(string[] args)
{
RunMiningProgram("C:\\", "hello.bat");
}
Related
I have an issue with my (visual studio) pre-build action calling an executable, which requires a console input.
The code looks like:
using System;
using System.Diagnostics;
using System.Text;
namespace MyMinumumExample
{
internal class MinimumExample
{
private StringBuilder stdOutput = null;
private readonly string assemblyFilePath;
private readonly Process gitProcess = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "git",
RedirectStandardOutput = true,
UseShellExecute = false,
},
};
public MinimumExample(string path)
{
assemblyFilePath = path;
}
private static int Main(string[] args)
{
string path = args[0];
var program = new MinimumExample(path);
if (program.CheckIfFileIsDirty(path))
{
Console.WriteLine("Changes will be discarded after compiling. Do you want to proceed?");
while (true)
{
Console.Write("[y/n]: ");
ConsoleKeyInfo key = Console.ReadKey();
if (key.KeyChar == 'y')
break;
if (key.KeyChar == 'n')
return 1;
Console.WriteLine();
}
}
return 0;
}
private bool CheckIfFileIsDirty(string path)
{
string gitCmdArgs = string.Format("diff --shortstat -- {0}", path);
stdOutput = new StringBuilder();
gitProcess.StartInfo.Arguments = gitCmdArgs;
gitProcess.Start();
gitProcess.BeginOutputReadLine();
gitProcess.OutputDataReceived += GitProcessOutputHandler;
gitProcess.WaitForExit();
gitProcess.OutputDataReceived -= GitProcessOutputHandler;
if (gitProcess.ExitCode != 0) throw new Exception(string.Format("Process 'git {0}' failed with code {1}", gitCmdArgs, gitProcess.ExitCode));
return !string.IsNullOrWhiteSpace(stdOutput.ToString());
}
private void GitProcessOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!string.IsNullOrWhiteSpace(outLine.Data))
{
stdOutput.Append(outLine.Data);
}
}
}
}
If I run this from cmd-shell or from a batch file everything works fine. If I put it into the pre build action, I get an exeption in line 38:
ConsoleKeyInfo key = Console.ReadKey();
The pre build action looks like:
call $(SolutionDir)MinimumExample.exe $(ProjectDir)dirtyExampleFile.cs
IF %ERRORLEVEL% GTR 0 (
EXIT 1
)
If I call the executable with start <exe> the example works, but I don't get the exit code of the exe (but probably of the new cmd shell).
Either, I can get the exit code of cmd shell to verify that the executable did exit cleanly, or I find a way to read keyboard input from the build event console.
Does anyone has an idea, how to solve this?
Best regards and thanks in advance!
I found the solution to get the exit code:
Using start with /wait option solved my problem.
PreBuildEvent is now:
start "Update version of $(ProjectName)" /wait $(SolutionDir)MinimumExample.exe $(ProjectDir)dirtyExampleFile.cs
I'm trying to take a screenshot using Chrome headless from an ASP.Net MVC app, here's the code:
public string TakeScreenshot(ScreenshotRequest request)
{
var pathToScreenshotFile = Path.Combine(Path.GetTempPath(), $"{request.FileName}.png");
var arguments = $#" --headless --hide-scrollbars --disable-gpu --screenshot=""{pathToScreenshotFile}"" --window-size={request.Width},{request.Height} https://google.com";
var psi = new ProcessStartInfo(pathToBrowser) { UseShellExecute = false, Verb = "runas" };
using (Process process = Process.Start(psi))
{
Thread.Sleep(1000);
var image = string.Empty;
var executionCount = 0;
while(image == string.Empty && executionCount < 5)
{
if (File.Exists(pathToScreenshotFile))
{
image = Convert.ToBase64String(File.ReadAllBytes(pathToScreenshotFile));
}
else
{
Thread.Sleep(1000);
}
}
return image;
}
}
The pathToBrowser variable points to the chrome executable: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
For some reason, the file does not get created, but if I open a terminal and run the following command it works:
E:\sources\chromium\bin\chrome.exe" --headless --hide-scrollbars --disable-gpu --screenshot="C:\Windows\TEMP\5353e1ab-783c-442a-8d72-54d030529e68a.png" --window-size=1920,874 https://google.com
Any ideas? I thought it needed to run as admin hence the "runas", but that didn't help.
Edit:
I think it's something related to permissions because the same code works when I run it from a console application. Right now I have the folder containing Chrome with Full Control to Everyone. I don't know what else I'm missing.
It worked great for me. I did have to include the arguments in the ProcessStartInfo.
private void Form1_Load(object sender, EventArgs e)
{
var output = TakeScreenshot(#"C:\Windows\TEMP\5353e1ab-783c-442a-8d72-54d030529e68a.png");
}
public string TakeScreenshot(string request)
{
var pathToBrowser = #"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe";
var pathToScreenshotFile = Path.Combine(Path.GetTempPath(), $"{request}.png");
var arguments = $#" --headless --hide-scrollbars --disable-gpu --screenshot=""{pathToScreenshotFile}"" --window-size={1920},{874} https://google.com";
var psi = new ProcessStartInfo(pathToBrowser,arguments) { UseShellExecute = false, Verb = "runas" };
using (Process process = Process.Start(psi))
{
Thread.Sleep(1000);
var image = string.Empty;
var executionCount = 0;
while (image == string.Empty && executionCount < 5)
{
if (File.Exists(pathToScreenshotFile))
{
image = Convert.ToBase64String(File.ReadAllBytes(pathToScreenshotFile));
}
else
{
Thread.Sleep(1000);
}
}
return image;
}
}
I am interested in how to inforce a single instance policy for dotnetcore console apps. To my surprise it seems like there isn't much out there on the topic. I found this one stacko, How to restrict a program to a single instance, but it doesnt seem to work for me on dotnetcore with ubuntu. Anyone here do this before?
Variation of #MusuNaji's solution at: How to restrict a program to a single instance
private static bool AlreadyRunning()
{
Process[] processes = Process.GetProcesses();
Process currentProc = Process.GetCurrentProcess();
logger.LogDebug("Current proccess: {0}", currentProc.ProcessName);
foreach (Process process in processes)
{
if (currentProc.ProcessName == process.ProcessName && currentProc.Id != process.Id)
{
logger.LogInformation("Another instance of this process is already running: {pid}", process.Id);
return true;
}
}
return false;
}
This is a little more difficult on .NET core than it should be, due to the problem of mutex checking on Linux/MacOS (as reported above). Also Theyouthis's solution isn't helpful as all .NET core apps are run via the CLI which has a process name of 'dotnet' which if you are running multiple .NET core apps on the same machine the duplicate instance check will trigger incorrectly.
A simple way to do this that is also multi-platform robust is to open a file for write when the application starts, and close it at the end. If the file fails to open it is due to another instance running concurrently and you can handle that in the try/catch. Using FileStream to open the file will also create it if it doesn't first exist.
try
{
lockFile = File.OpenWrite("SingleInstance.lck");
}
catch (Exception)
{
Console.WriteLine("ERROR - Server is already running. End that instance before re-running. Exiting in 5 seconds...");
System.Threading.Thread.Sleep(5000);
return;
}
Here is my implementation using Named pipes. It supports passing arguments from the second instance.
Note: I did not test on Linux or Mac but it should work in theory.
Usage
public static int Main(string[] args)
{
instanceManager = new SingleInstanceManager("8A3B7DE2-6AB4-4983-BBC0-DF985AB56703");
if (!instanceManager.Start())
{
return 0; // exit, if same app is running
}
instanceManager.SecondInstanceLaunched += InstanceManager_SecondInstanceLaunched;
// Initialize app. Below is an example in WPF.
app = new App();
app.InitializeComponent();
return app.Run();
}
private static void InstanceManager_SecondInstanceLaunched(object sender, SecondInstanceLaunchedEventArgs e)
{
app.Dispatcher.Invoke(() => new MainWindow().Show());
}
Your Copy-and-paste code
public class SingleInstanceManager
{
private readonly string applicationId;
public SingleInstanceManager(string applicationId)
{
this.applicationId = applicationId;
}
/// <summary>
/// Detect if this is the first instance. If it is, start a named pipe server to listen for subsequent instances. Otherwise, send <see cref="Environment.GetCommandLineArgs()"/> to the first instance.
/// </summary>
/// <returns>True if this is tthe first instance. Otherwise, false.</returns>
public bool Start()
{
using var client = new NamedPipeClientStream(applicationId);
try
{
client.Connect(0);
}
catch (TimeoutException)
{
Task.Run(() => StartListeningServer());
return true;
}
var args = Environment.GetCommandLineArgs();
using (var writer = new BinaryWriter(client, Encoding.UTF8))
{
writer.Write(args.Length);
for (int i = 0; i < args.Length; i++)
{
writer.Write(args[i]);
}
}
return false;
}
private void StartListeningServer()
{
var server = new NamedPipeServerStream(applicationId);
server.WaitForConnection();
using (var reader = new BinaryReader(server, Encoding.UTF8))
{
var argc = reader.ReadInt32();
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = reader.ReadString();
}
SecondInstanceLaunched?.Invoke(this, new SecondInstanceLaunchedEventArgs { Arguments = args });
}
StartListeningServer();
}
public event EventHandler<SecondInstanceLaunchedEventArgs> SecondInstanceLaunched;
}
public class SecondInstanceLaunchedEventArgs
{
public string[] Arguments { get; set; }
}
Unit test
[TestClass]
public class SingleInstanceManagerTests
{
[TestMethod]
public void SingleInstanceManagerTest()
{
var id = Guid.NewGuid().ToString();
var manager = new SingleInstanceManager(id);
string[] receivedArguments = null;
var correctArgCount = Environment.GetCommandLineArgs().Length;
manager.SecondInstanceLaunched += (sender, e) => receivedArguments = e.Arguments;
var instance1 = manager.Start();
Thread.Sleep(200);
var manager2 = new SingleInstanceManager(id);
Assert.IsFalse(manager2.Start());
Thread.Sleep(200);
Assert.IsTrue(instance1);
Assert.IsNotNull(receivedArguments);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
var receivedArguments2 = receivedArguments;
var manager3 = new SingleInstanceManager(id);
Thread.Sleep(200);
Assert.IsFalse(manager3.Start());
Assert.AreNotSame(receivedArguments, receivedArguments2);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
}
}
The downside of deandob's solution is that one can launch the application from another path. So you may prefer some static path or a tmp path for all users.
Here is my attempt:
//second instance launch guard
var tempPath = Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine)
??
Path.GetTempPath();
var lockPath = Path.Combine(tempPath, "SingleInstance.lock");
await using var lockFile = File.OpenWrite(lockPath);
here I'm trying to get TEMP system variable at the scope of machine (not the user TEMP) and if its empty - fallback to the user's temp folder on windows or shared /tmp on some linuxes.
I am trying to write a console application that acts as a "job manager" by running processes in the background. These processes would be running JScript files with arguments passed in. This console application will be distributed across many machines, and will pull from a centralized source (ie. database) to get jobs. The purpose of this application is to eliminate the need for individualized batch files on all of these machines.
I am having trouble keeping the application alive. In the code that I included, you can see in my main function that I am making an initial call to the JobManger's StartNewJobs() method. After this initial call to this method, I'd like my application to then be event-driven, only waking up and running when a process has exited, allowing me to start a new process. The problem I am running into is that once the main() function finishes (when the initial StartNewJobs() method finishes) the console closes and the program ends.
My question is what is the proper way to keep my console application alive and allow it to be event-driven rather than procedural? I know I can probably throw in a while(true) at the end of the main function, but that seems sloppy and incorrect.
Batch file we are trying to replace:
C:\Windows\SysWOW64\cscript.exe c:\temp\somejscriptfile.js 49f1bdd8-5e6b-40cc-92bc-eb20c237a959
C:\Windows\SysWOW64\cscript.exe c:\temp\somejscriptfile.js 654e3783-a1b6-43be-8027-c7d060bf131f
...
Program.cs:
using DistributedJobs.Data;
using DistributedJobs.Logging;
using DistributedJobs.Models;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using System;
namespace DistributedJobs
{
class Program
{
static void Main(string[] args)
{
//Get intial objects/settings
ILogger logger = new Logger(Properties.Settings.Default.LoggingLevel, EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>());
IDataProvider dataProvider = new SQLDataProvider();
DMSPollingJobType availableJobTypes = DMSPollingJobType.FlatFile;
if (Properties.Settings.Default.SupportsVPN)
{
availableJobTypes |= DMSPollingJobType.VPN;
}
String executableLocation = Properties.Settings.Default.ExecutableLocation;
String jsLocation = Properties.Settings.Default.JSLocation;
Int32 maxProcesses = Properties.Settings.Default.MaxProcesses;
//Create job manager and start new processes/jobs
DMSJobManager jobManager = new DMSJobManager(logger, dataProvider, availableJobTypes, executableLocation, jsLocation, maxProcesses);
jobManager.StartNewJobs();
}
}
}
JobManager.cs:
using DistributedJobs.Models;
using System.Diagnostics;
using System;
using System.Collections.Generic;
using DistributedJobs.Logging;
namespace DistributedJobs.Data
{
public class JobManager
{
private IDataProvider DataProvider;
private ILogger Logger;
private Dictionary<Job, Process> RunningProcesses;
private JobType AvailableJobTypes;
private String ExecutableLocation;
private String JSLocation;
private Int32 MaxProcesses;
public Boolean CanStartNewJob
{
get
{
Boolean canStartNewJob = false;
if (RunningProcesses.Count < MaxProcesses)
{
canStartNewJob = true;
}
foreach (KeyValuePair<Job, Process> entry in RunningProcesses)
{
if (entry.Key.JobType != JobType.FlatFile)
{
canStartNewJob = false;
break;
}
}
return canStartNewJob;
}
}
public JobManager(ILogger logger, IDataProvider dataProvider, JobType availableJobTypes, String executableLocation, String jsLocation, Int32 maxProcesses)
{
Logger = logger;
DataProvider = dataProvider;
RunningProcesses = new Dictionary<Job, Process>();
AvailableJobTypes = availableJobTypes;
ExecutableLocation = executableLocation;
JSLocation = jsLocation;
MaxProcesses = maxProcesses;
}
public void StartNewJobs()
{
while (CanStartNewJob)
{
Job newJob = DataProvider.GetNextScheduledJob(AvailableJobTypes);
if (newJob != null)
{
Process newProcess = CreateNewProcess(newJob);
RunningProcesses.Add(newJob, newProcess);
newProcess.Start();
}
}
}
public Process CreateNewProcess(Job job)
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = ExecutableLocation;
startInfo.Arguments = JSLocation + " " + job.JobID.ToString();
startInfo.UseShellExecute = false;
Process retProcess = new Process()
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
retProcess.Exited += new EventHandler(JobFinished);
return retProcess;
}
public void JobFinished(object sender, EventArgs e)
{
Job finishedJob = null;
foreach (KeyValuePair<Job, Process> entry in RunningProcesses)
{
if ((Process)sender == entry.Value)
{
finishedJob = entry.Key;
break;
}
}
if (finishedJob != null)
{
RunningProcesses.Remove(finishedJob);
StartNewJobs();
}
}
}
}
You could try using Application.Run()(System.Windows.Forms). This will start a standard message loop.
So at the end of your Main method just add a Application.Run():
static void Main(string[] args)
{
//Get intial objects/settings
ILogger logger = new Logger(Properties.Settings.Default.LoggingLevel, EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>());
IDataProvider dataProvider = new SQLDataProvider();
DMSPollingJobType availableJobTypes = DMSPollingJobType.FlatFile;
if (Properties.Settings.Default.SupportsVPN)
{
availableJobTypes |= DMSPollingJobType.VPN;
}
String executableLocation = Properties.Settings.Default.ExecutableLocation;
String jsLocation = Properties.Settings.Default.JSLocation;
Int32 maxProcesses = Properties.Settings.Default.MaxProcesses;
//Create job manager and start new processes/jobs
DMSJobManager jobManager = new DMSJobManager(logger, dataProvider, availableJobTypes, executableLocation, jsLocation, maxProcesses);
jobManager.StartNewJobs();
// start message loop
Application.Run();
}
The code looks lengthy but it's a simple program.
I have built a console app (TakeScreenshots) that will take website screenshots from firefox, chrome & ie in that order & save them in a folder. When I manually run TakeScreenshots.exe, all 3 screenshots are saved.
Now, I have built another console app (MyApp) that will execute TakeScreenshots.exe. But in this way, only the firefox screenshot is saved and not of the other 2. There are no exceptions. It just says "Process Complete". I guess, MyApp is not waiting for the TakeScreenshots to complete.
How can I fix this.
[TakeScreenshots will later be placed in few remote computers & run by MyApp]
TakeScreenshots code:
private static string[] WebDriversList = ["firefox","chrome","internetexplorer"];
private static void TakeAPic()
{
string url = "http://www.google.com";
string fileNamePrefix = "Test";
string snapSavePath = "D:\\Pics\\";
foreach (string wd in WebDriversList)
{
IWebDriver NewDriver = null;
switch (wd.ToLower())
{
case "firefox":
using (NewDriver = new FirefoxDriver())
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
case "chrome":
using (NewDriver = new ChromeDriver(WebDriversPath))
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
case "internetexplorer":
using (NewDriver = new InternetExplorerDriver(WebDriversPath))
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
}
if (NewDriver != null)
{
NewDriver.Quit();
}
}
}
private static void CaptureScreenshot(IWebDriver driver,string url,string fileNamePrefix,
string snapSavePath)
{
driver.Navigate().GoToUrl(url);
Screenshot ss = ((ITakesScreenshot)driver).GetScreenshot();
ICapabilities capabilities = ((RemoteWebDriver)driver).Capabilities;
ss.SaveAsFile(snapSavePath + fileNamePrefix + "_" + capabilities.BrowserName + ".png",
ImageFormat.Png);
}
MyApp code:
static void Main(string[] args)
{
ExecuteTakeScreenshot();
Console.WriteLine("PROCESS COMPLETE");
Console.ReadKey();
}
private static void ExecuteTakeScreenshot()
{
ProcessStartInfo Psi = new ProcessStartInfo("D:\\PsTools\\");
Psi.FileName = "D:\\PsTools\\PsExec.exe";
Psi.Arguments = "/C \\DESK101 D:\\Release\\TakeScreenshots.exe";
Psi.UseShellExecute = false;
Psi.RedirectStandardOutput = true;
Psi.RedirectStandardInput = true;
Process.Start(Psi).WaitForExit();
}
Update:
It was my mistake. Initially WebDriversPath was assigned "WebDrivers/". When I changed it to the actual path "D:\WebDrivers\", it worked. But I still dont understand how it worked when TakeScreenshots.exe was run manually and it doesn't when run from another console
In similar problems I have had success with waiting for input idle first. Like this:
Process process = Process.Start(Psi);
process.WaitForInputIdle();
process.WaitForExit();
You could try this. For me it was needed to print a pdf using Adobe Reader and not close it to early afterwards.
Example:
Process process = new Process();
process.StartInfo.FileName = DestinationFile;
process.StartInfo.Verb = "print";
process.Start();
// In case of Adobe Reader the following statement is needed:
process.WaitForInputIdle();
process.WaitForExit(2000);
process.WaitForInputIdle();
process.Kill();