Taking a screenshot using Chrome headless and C# - c#

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

Related

How to open cmd everytime using C# Kestrel

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

Trouble using ffmpeg in c# how to correctly format string to upscale videos?

So I am writing an app in c# to upscale videos to a certain resolution. It uses ffmpeg to do this. What happens is after selecting the video file, and clicking 1080p it creates the directory folder but does not actually write the upscaled video to it.
I think I must have a string format issue:
private void HD_Click(object sender, EventArgs e)
{
if (textBox1.Text == null)
{
MessageBox.Show("You've not selected your video file yet. Please do so before continuing, cheers.");
}
else
{
var originFilePath = textBox1.Text;
string name = Path.GetFileName(originFilePath);
byte[] bytes = null;
using (FileStream fileStream = new FileStream(originFilePath, FileMode.Open, FileAccess.Read))
{
using (MemoryStream ms = new MemoryStream())
{
fileStream.CopyTo(ms);
bytes = ms.ToArray();
}
var localStoragePath = Path.Combine(Path.GetTempPath(), name);
var directoryPath = Path.GetDirectoryName(localStoragePath);
Directory.CreateDirectory(directoryPath);
File.WriteAllBytes(localStoragePath, bytes);
Console.WriteLine($"File copy successful: {File.Exists(localStoragePath)}");
var readBack = File.ReadAllBytes(localStoragePath);
Console.WriteLine($"Read file Back: {readBack.Length}, {localStoragePath}");
var resizedFolderPath = #"C:\upscaledvideohere";
Directory.CreateDirectory(resizedFolderPath);
var resizedFiePath = Path.Combine(resizedFolderPath, Path.GetFileName(localStoragePath));
var psi = new ProcessStartInfo();
psi.FileName = #"C:\ffmpeg-2020-12-27-git-bff6fbead8-full_build\binffmpeg.exe";
psi.Arguments = $"-i \"{localStoragePath}\" -vf scale=1080 \"{resizedFiePath}\"";
psi.RedirectStandardOutput = false;
psi.RedirectStandardError = false;
psi.UseShellExecute = true;
Console.WriteLine($"Args: {psi.Arguments}");
try
{
using (Process exeProcess = Process.Start(psi))
{
Console.WriteLine($"process started with processId: {exeProcess.Id}");
exeProcess.WaitForExit();
Console.WriteLine($"Exit Code: {exeProcess.ExitCode}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace.ToString());
Console.WriteLine(ex.Message.ToString());
return;
}
Console.WriteLine($"process completed");
Console.WriteLine($"Temp Out Exists: {File.Exists(resizedFiePath)}");
}
Console.ReadLine();
}
}
I am wondering where the string format error could be? Thank you.
As far as I am aware, the scale command of ffmpeg takes two dimensions. If you want scaling to remain proportional you specify one of the options as -1. As 1080 is your height, your scale command would be scale=-1:1080
To debug things like this, put a breakpoint after your arguments line, point to the variable(or use the locals window) so the tooltip appears and then right click it and copy the value. Paste the value into a command prompt and see whether ffmpeg does what you expect. Fix the arguments in the command prompt if it doesn't, and carry the changes back into your code

Get data from program that launched from vscode

I have a vscode extension that launches an executable, i know how to pass data from vscode to my program but not the other way around.
// class that launches the exe
class Execute {
constructor(private _extensionPath: string) {}
public Launch() {
console.log('213');
Process.exec(
`WpfApp1.exe true`,
{ cwd: this._extensionPath },
(error: Error, stdout: string, stderr: string) => {
if (stdout.length === 0) {
return;
}
}
);
}
}
// calling the class
let exe = new Execute(
vscode.extensions.getExtension('author.extension').extensionPath
);
exe.Launch();
c# receiving data
void App_Startup(object sender, StartupEventArgs e)
{
try
{
test_p = e.Args[0];
if (test_p == "true")
{
}
}
catch { MessageBox.Show("fail"); }
}
how can i send data from the c# application to the vscode extension?
calling a function in vscode would be even better.
you can also run a executable with c#:
public static string[] Cmd(bool xWaitForExecution, params string[] xCommands)
{
//PROCESS CMD
if (xCommands == null || xCommands.Length == 0) return null;
ProcessStartInfo info = new ProcessStartInfo("cmd.exe");
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardInput = true; //STD INPUT
info.RedirectStandardOutput = true; //STD OUTPUT
info.RedirectStandardError = true; //STD ERROR
Process process = Process.Start(info);
//WRITE COMMANDS
using (StreamWriter sw = process.StandardInput)
if (sw.BaseStream.CanWrite)
foreach (string cmd in xCommands)
sw.WriteLine(cmd);
//GET OUTPUT & ERROR
if (!xWaitForExecution) return null;
string output = process.StandardOutput.ReadToEnd(); //OUTPUT
string error = process.StandardError.ReadToEnd(); //ERROR
string exit = process.ExitCode.ToString(); //EXIT CODE
process.Close();
return new string[] { output, error, exit };
}
The function runs cmd64.exe and it should use like:
//Call Cmd, true means the c# application will wait for the complete execute of your executable (needed to obtain output values)
string[] ret = Cmd(true, "\\mypath\\my.exe -Argument1 -Argument2"); //Passing arguments depends on your executable
string output = ret[0];
Console.WriteLine(ret[0]) //printed arguments from your executable (for instance python: print("Argument1"))
It is not completly clear why you need the VS Code extension to execute an executable. This is a working alternative to run executables on windows from c#.
Send Data:
private void AnyEvent(object sender, MouseEventArgs e)
{
const String output = "testOutput,testOutput2";
Console.Write(output);
}
Receive Data:
import * as Process from 'child_process';
// put the code below in a class or function.
Process.exec(
`app.exe input`,
{ cwd: "Path to folder of the executable" },
(error, stdout: string, stderr: string) => {
const output = stdout.split(','); //it only outputs data after the exe is closed!?
if (output[0] === 'testOutput') {
console.log('Output: == "testOutput"');
}
}
);
Example Project should work after running npm i if not open an issue or comment below.

How to wait for the remote exe to complete - c#

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

C# Arguments for a specific process, open browser with url

I am writing an application that is supposed to open a certain process on the click of a button. However, the user has the ability to add new buttons. I'm using the following code for the action that occurs that starts the process on button click:
private void StartProcess(string path)
{
ProcessStartInfo StartInformation = new ProcessStartInfo();
StartInformation.FileName = path;
Process process = Process.Start(StartInformation);
process.EnableRaisingEvents = true;
}
private void ClickFunc(object sender, RoutedEventArgs e)
{
if (File.Exists(ProgramPath))
{
StartProcess(ProgramPath);
}
else
{
MessageBox.Show("Specified path does not exist, please try again.", "Bad File Path Error", MessageBoxButton.OK);
}
}
What I'm trying to accomplish is, when the user creates a button for a webpage, it opens the browser, then the webpage. Any ideas?
Thank you in advance!
To start a process to open the browser with a specific url you can try this:
string url = "http://www.stackoverflow.com";
var process = System.Diagnostics.Process.Start(url);
But sometimes if you have problems with the path of your browser, it cannot work properly. The function bellow gives you the path of the browser in the machine.
public static string GetDefaultBrowserPath()
{
string urlAssociation = #"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http";
string browserPathKey = #"$BROWSER$\shell\open\command";
RegistryKey userChoiceKey = null;
string browserPath = “”;
try
{
//Read default browser path from userChoiceLKey
userChoiceKey = Registry.CurrentUser.OpenSubKey(urlAssociation + #"\UserChoice", false);
//If user choice was not found, try machine default
if (userChoiceKey == null)
{
//Read default browser path from Win XP registry key
var browserKey = Registry.ClassesRoot.OpenSubKey(#"HTTP\shell\open\command", false);
//If browser path wasn’t found, try Win Vista (and newer) registry key
if (browserKey == null)
{
browserKey =
Registry.CurrentUser.OpenSubKey(
urlAssociation, false);
}
var path = CleanifyBrowserPath(browserKey.GetValue(null) as string);
browserKey.Close();
return path;
}
else
{
// user defined browser choice was found
string progId = (userChoiceKey.GetValue("ProgId").ToString());
userChoiceKey.Close();
// now look up the path of the executable
string concreteBrowserKey = browserPathKey.Replace(“$BROWSER$”, progId);
var kp = Registry.ClassesRoot.OpenSubKey(concreteBrowserKey, false);
browserPath = CleanifyBrowserPath(kp.GetValue(null) as string);
kp.Close();
return browserPath;
}
}
catch(Exception ex)
{
return "";
}
}
And you can use the path of the browser and the url of website, for sample:
string url = "http://www.stackoverflow.com";
var process = System.Diagnostics.Process.Start(GetDefaultBrowserPath(), url);
In the url string you can pass the webpage link. It will open the browser with the url.
See more:
http://www.seirer.net/blog/2014/6/10/solved-how-to-open-a-url-in-the-default-browser-in-csharp

Categories