C# System.Management.Automation.PowerShell Performance - c#

Why is performance of executing a script from PowerShell directly much quicker than when executing the script from the System.Management.Automation.PowerShell class? When I execute my script directly in PS, it takes less than a second, though when I execute it through the code below, the Invoke takes several minutes.
public bool Execute(string filename, out string result)
{
StringBuilder sb = new StringBuilder();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace.SessionStateProxy.SetVariable("filename", filename);
ps.AddScript(this.script);
Collection<PSObject> psOutput = ps.Invoke();
foreach (PSObject item in psOutput)
{
Console.WriteLine(item);
}
PSDataCollection<ErrorRecord> errors = ps.Streams.Error;
foreach (ErrorRecord err in errors)
{
Console.WriteLine(err);
}
result = sb.ToString();
return errors.Count == 0;
}
}
Script text:
[regex]$r = "\$\%\$##(.+)\$\%\$##";
(Get-Content $filename) |
Foreach-Object {
$line = $_;
$find = $r.matches($line);
if ($find[0].Success) {
foreach ($match in $find) {
$found = $match.value
$replace = $found -replace "[^A-Za-z0-9\s]", "";
$line.Replace($found, $replace);
}
}
else
{
$line;
}
} |
Set-Content $filename
I tested the execution of the script against this method in c#, and this method takes less than 2 seconds to execute the script.
public bool Execute(string filename, out string result)
{
StringBuilder standardOutput = new StringBuilder();
string currentPath = Path.GetDirectoryName(filename);
string psFile = Path.Combine(currentPath, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".ps1");
this.script = this.script.Replace("$filename", "\"" + filename + "\"");
File.WriteAllText(psFile, this.script);
using (Process process = new Process())
{
process.StartInfo = new ProcessStartInfo("powershell.exe", String.Format("-executionpolicy unrestricted \"{0}\"", psFile))
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
while (!process.HasExited)
{
standardOutput.Append(process.StandardOutput.ReadToEnd());
}
process.WaitForExit();
standardOutput.Append(process.StandardOutput.ReadToEnd());
result = standardOutput.ToString();
return process.ExitCode == 0;
}
}

Related

how to execute a .rmd file from c# and get the execution response - Dot Net

I am having a .rmd file [ R mark down file], it will do data validation for some specific scenario, i wanted to execute the rmd file from console application using c#, can some one help me doing the execution.
Finally I have done the above requirement as below,posting here since it can help some one on implementation
- created a R script and invoked Rmd script from the R script
- used command line code for invoking R script from C#.
R Script Sample:
rmarkdown::render(
input = rmdfilepath,
output_file = outputfilepath,
output_format = "all", output_options = list())
c# Sample:
private static string RunRScript(string rpath, string scriptpath, string inputfilepath,
string outputfilepath)
{
try
{
var info = new ProcessStartInfo
{
FileName = rpath,
WorkingDirectory = Path.GetDirectoryName(rpath),
Arguments = scriptpath + " " + inputfilepath + " " + outputfilepath,
RedirectStandardOutput = true,
CreateNoWindow = true,
UseShellExecute = false
};
using (var proc = new Process { StartInfo = info })
{
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return string.Empty;
}

Vbs script working fine with CMD but not in Code

When I execute the script via code it's going in an infinite loop. But same script working in CMD
tried with CMD and It's working but not working in code. Take any VB Script as a sample and validate.
private string excecuteScript(string retValue)
{
try
{
string filetoexecute = Path.Combine(Application.StartupPath, Path.GetExtension(ScriptName).EndsWith(".vbs",StringComparison.InvariantCultureIgnoreCase) ? "AA_MyVB.vbs" : "AA_MyJS.js");
filetoexecute = string.Format("\"" + filetoexecute + "\"");
string arguments = " " + filetoexecute + " " + ScriptInputPara + " \"" + ScriptName + "\" ";
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = Path.Combine(Environment.SystemDirectory, "WScript.exe"),
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var myProcess = CommonMethods.InvokeProcess(startInfo);
StreamReader errorStream = myProcess.StandardError;
StreamReader outputStream = myProcess.StandardOutput;
string theOutput = string.Empty;
bool stopWhile = false;
while (!stopWhile)
{
stopWhile = myProcess.HasExited;
if (CmdTask.IsTaskTimedout)
{
stopWhile = true;
myProcess.Kill();
}
}
theOutput += outputStream.ReadLine();
TheError += errorStream.ReadToEnd();
if (myProcess.ExitCode != 0)
{
TheError = "Error occurred while executing script file";
}
if (IsScriptTask && !string.IsNullOrEmpty(theOutput) && !string.IsNullOrEmpty(ScriptOutputPara))
CmdTask.SetVariableValue(ScriptOutputPara, theOutput);
}
catch (Exception ex)
{
retValue = (ex.Message);
}
return retValue;
}
Actual: myProcess.HasExited returns false
Expected: myProcess.HasExited returns true
if I execute theOutput += outputStream.ReadLine(); before while loop then myProcess.HasExited returns true...

how do I execute cmd.exe process only once and in foreach pass commandText?

I want to execute process of cmd.exe only once outside foreach and inside foreach want to send parameters to this process.
I am currently doing this:
var msbuildPath = (string) regKey.GetValue("MSBuildToolsPath");
foreach (var item in listBox1.Items)
{
var FilePath = item.ToString();
var startInfo = new ProcessStartInfo()
{
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = String.Format("\"{0}\" /nologo ", FilePath),
FileName = Path.Combine(msbuildPath, "msbuild.exe")
};
var proc = Process.Start(startInfo);
proc.WaitForExit();
}
If I get you right, it should look something like this:
var msbuildPath = (string)regKey.GetValue("MSBuildToolsPath");
StringBuilder sb = new StringBuilder();
foreach (var item in listBox1.Items)
{
sb.AppendFormat("\"{0}\" ", item);
}
var startInfo = new ProcessStartInfo()
{
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = sb.ToString() + " /nologo",
FileName = Path.Combine(msbuildPath, "msbuild.exe")
};
var proc = Process.Start(startInfo);
proc.WaitForExit();

Trying to run same command in command prompt not working

I am making a program that seeks out secured PDFs in a folder and converting them to PNG files using ImageMagick. Below is my code.
string WorkDir = #"C:\Users\rwong\Desktop\TestFiles";
Directory.SetCurrentDirectory(WorkDir);
String[] SubWorkDir = Directory.GetDirectories(WorkDir);
foreach (string subdir in SubWorkDir)
{
string[] filelist = Directory.GetFiles(subdir);
for(int f = 0; f < filelist.Length; f++)
{
if (filelist[f].ToLower().EndsWith(".pdf") || filelist[f].EndsWith(".PDF"))
{
PDFReader reader = new Pdfreader(filelist[f]);
bool PDFCheck = reader.IsOpenedWithFullPermissions;
reader.CLose();
if(PDFCheck)
{
//do nothing
}
else
{
string PNGPath = Path.ChangeExtension(filelistf], ".png");
string PDFfile = '"' + filelist[f] + '"';
string PNGfile = '"' + PNGPath + '"';
string arguments = string.Format("{0} {1}", PDFfile, PNGfile);
ProcessStartInfo startInfo = new ProcessStartInfo(#"C:\Program Files\ImageMagick-6.9.2-Q16\convert.exe");
startInfo.Arguments = arguments;
Process.Start(startInfo);
}
}
}
I have ran the raw command in command prompt and it worked so the command isn't the issue. Sample command below
"C:\Program Files\ImageMagick-6.9.2-Q16\convert.exe" "C:\Users\rwong\Desktop\TestFiles\Test_File File_10.PDF" "C:\Users\rwong\Desktop\TestFiles\Test_File File_10.png"
I looked around SO and there has been hints that spaces in my variable can cause an issue, but most of those threads talk about hardcoding the argument names and they only talk about 1 argument. I thought adding double quotes to each variable would solve the issue but it didn't. I also read that using ProcessStartInfo would have helped but again, no dice. I'm going to guess it is the way I formatted the 2 arguments and how I call the command, or I am using ProcessStartInto wrong. Any thoughts?
EDIT: Based on the comments below I did the extra testing testing by waiting for the command window to exit and I found the following error.
Side note: I wouldn't want to use GhostScript just yet because I feel like I am really close to an answer using ImageMagick.
Solution:
string PNGPath = Path.ChangeExtension(Loan_list[f], ".png");
string PDFfile = PNGPath.Replace("png", "pdf");
string PNGfile = PNGPath;
Process process = new Process();
process.StartInfo.FileName = #"C:\Program Files\ImageMagick-6.9.2 Q16\convert.exe";
process.StartInfo.Arguments = "\"" + PDFfile + "\"" +" \"" + PNGPath +"\""; // Note the /c command (*)
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
//* Read the output (or the error)
string output = process.StandardOutput.ReadToEnd();
Console.WriteLine(output);
string err = process.StandardError.ReadToEnd();
Console.WriteLine(err);
process.WaitForExit();
It didn't like the way I was formatting the argument string.
This would help you to run you command in c# and also you can get the result of the Console in your C#.
string WorkDir = #"C:\Users\rwong\Desktop\TestFiles";
Directory.SetCurrentDirectory(WorkDir);
String[] SubWorkDir = Directory.GetDirectories(WorkDir);
foreach (string subdir in SubWorkDir)
{
string[] filelist = Directory.GetFiles(subdir);
for(int f = 0; f < filelist.Length; f++)
{
if (filelist[f].ToLower().EndsWith(".pdf") || filelist[f].EndsWith(".PDF"))
{
PDFReader reader = new Pdfreader(filelist[f]);
bool PDFCheck = reader.IsOpenedWithFullPermissions;
reader.CLose()l
if(!PDFCheck)
{
string PNGPath = Path.ChangeExtension(filelistf], ".png");
string PDFfile = '"' + filelist[f] + '"';
string PNGfile = '"' + PNGPath + '"';
string arguments = string.Format("{0} {1}", PDFfile, PNGfile);
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.startInfo.FileName = "C:\Program Files\ImageMagick-6.9.2-Q16\convert.exe";
p.startInfo.Arguments = arguments;
p.OutputDataReceived += new DataReceivedEventHandler(Process_OutputDataReceived);
//You can receive the output provided by the Command prompt in Process_OutputDataReceived
p.Start();
}
}
}
private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
string s = e.Data.ToString();
s = s.Replace("\0", string.Empty);
//Show s
Console.WriteLine(s);
}
}

Open and print PDF files

I'd like to open and print all PDF files located in a given folder. The files are named according to the following pattern:
NameOfThePrinter_Timestamp.pdf
Now I want to print those files using the corresponding printer:
static void Main(string[] args)
{
string pdf = #"C:\PathToFolder";
if (Directory.GetFiles(pdf).Length > 0)
{
string[] files = Directory.GetFiles(pdf);
var adobe = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Microsoft").OpenSubKey("Windows").OpenSubKey("CurrentVersion").OpenSubKey("App Paths").OpenSubKey("AcroRd32.exe");
var path = adobe.GetValue("");
string acrobat = path.ToString();
for (int i = 0; i < files.Length; i++)
{
Process process = new Process();
process.StartInfo.FileName = acrobat;
process.StartInfo.Verb = "printto";
process.StartInfo.Arguments = "/p /s /h \"" + files[i] + "\"";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
DateTime start = DateTime.Now;
IntPtr handle = IntPtr.Zero;
while (handle == IntPtr.Zero && DateTime.Now - start <= TimeSpan.FromSeconds(2))
{
try
{
System.Threading.Thread.Sleep(50);
handle = process.MainWindowHandle;
} catch (Exception) { }
}
foreach (String verb in process.StartInfo.Verbs)
{
// Display the possible verbs.
Console.WriteLine(" {0}. {1}", i.ToString(), verb);
i++;
}
System.Threading.Thread.Sleep(10000);
Console.Out.WriteLine("File: " + files[i] + " is printing!");
process.Kill();
}
foreach (string str in files)
{
File.Delete(str);
}
Console.Out.WriteLine("Files are deleted!");
}
}
My question is: How can I pass the printer name as parameter?
Here I've tried something, but it either throws and error or prints to the default printer:
process.StartInfo.Arguments = "/p /s /h \"" + files[i] + "\"";
You can use Ghostscript to send the PDF document to the printer.
Here you can find a sample how to send the PDF document to printer: How to print PDF on default network printer using GhostScript (gswin32c.exe) shell command
And here you can find Ghostscript wrapper for .NET if you want to control Ghostscript directly without calling .exe file: http://ghostscriptnet.codeplex.com
function printDisclosureDocument() {
var doc = document.getElementById('pdfDocument');
if (doc == 'undefined' || doc == null) {
var pdfbox = document.createElement('embed');
pdfbox.type = 'application/pdf';
pdfbox.src = 'ShowPDF.aspx?refid=' + $('#MainContent_hdnRefId').val();
pdfbox.width = '1';
pdfbox.height = '1';
pdfbox.id = 'pdfDocument';
document.body.appendChild(pdfbox);
}
if (doc != null && doc != 'undefined') {
//Wait until PDF is ready to print
if (typeof doc.print === 'undefined') {
setTimeout(function () { printDisclosureDocument(); }, 500);
} else {
doc.print();
}
}
else {
setTimeout(function () { printDisclosureDocument(); }, 500);
}
}

Categories