Run Python script with imports in C# - c#

Lets say I have this super Python script that needs to run cv2 in the future...
import cv2
def method():
print("Hello")
parameter = "l"
return "OOPS"
method()
And in C# something like this.
Process p = new Process();
p.StartInfo = new ProcessStartInfo(#"D:\Programming\Python\python.exe", fileName)
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
p.Start();
string output = p.StandardOutput.ReadToEnd();
But this does throw an error "ImportError: DLL load failed". Alright seems like it is lookin in wrong directories for libraries since I have about 4 Python interpreters. Follows quick fix.
string path = #"D:\Programming\Python;" + Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONHOME", #"D:\Programming\Python;", EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONPATH ", #"D:\Programming\Python\Lib; D:\Programming\Python\DLLs", EnvironmentVariableTarget.Process);
string fileName = #"..\Python\hello.py";
Process p = new Process();
p.StartInfo = new ProcessStartInfo(#"D:\Programming\Python\python.exe", fileName)
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
p.Start();
string output = p.StandardOutput.ReadToEnd();
Import DLL is fixed now, but another wild bug appeared named,
Fatal Python error: initfsencoding: unable to load the file system codec
ModuleNotFoundError: No module named 'encodings'
At this point I am lost and dont know what should I do next... any ideas are welcome, have a nice day.
UPDATE:
Deleted all other python interpretors aside from anaconda and one virtual env and tried following:
Run Python script from Visual Studio Code with given interpretor, works fine.
Run it from Anaconda prompt, aswell.
Added manually to system environment variables
PATH=D:\Programming\Python
PYTHONHOME=D:\Programming\Python
PYTHONPATH=D:\Programming\Python\Lib;D:\Programming\Python\DLLs;D:\Programming\Python\Lib\site-packages
So now I can successfully call "python" from cmd, like that and check version, the virtual env is python 3.6 and this is the right one.
Python is correct
But this is where all the fun begins you would expect "hello" in your console...
hell incarnate

Did not find correct answer to this problem, but discovered workaround in p2exe or pyinstaller.
Simply call pyinstaller.py --onefile xx.py and create exe file and pass that into process.

Related

Run a Powershell script inside of a C# project

So I want to run a powershell script inside of a c# project. The powershell file would be located in a folder inside the project. I am just not sure how to reference that file from c# to run it.
Please do a google search before you ask here
https://learn.microsoft.com/da-dk/archive/blogs/kebab/executing-powershell-scripts-from-c
https://duanenewman.net/blog/post/running-powershell-scripts-from-csharp/
var ps1File = #"C:\my script folder\script.ps1";
var startInfo = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy unrestricted -file \"{ps1File}\"",
UseShellExecute = false
};
Process.Start(startInfo);

Pass Image object as a parameter from C# to Python

I want to pass an image object from my c# project to my python script however from my understanding whatever there is in the arguments it is considered as string and also when I try type(passedImage) in python it identifies it as a string even if I try to put a number instead of the image variable.
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Python\Python36\python.exe";
start.Arguments = string.Format("{0} {1}", #"C:\OCRonImage2.py", image );
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.CreateNoWindow = true;
using (Process process = Process.Start(start))
{
}
When executing OCRonImage2.py manually, is it an image file location that you would pass as an argument? I would be surprise if you would pass in a stream from the command line. It is no surprise that attempting to put the entire image's bytes into an argument would create a string too long. But with the error you reported, I would also believe that the python script was expecting a file path to the image. However, if you look at that python code, I wouldn't be surprised if you find it using the filepath argument to open the file, probably using Image.open(filepath,mode=r). Mode is optional, r is the default.
You are in luck however, Image.open also takes a stream. If you are willing to modify the python code there are two options:
Try converting the argument to a stream object, since the argument is a string maybe use io.StringIO()
Use input() instead of the argument passed, then you could redirect the input of the process and stream the file into your python.
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Python\Python36\python.exe";
start.Arguments = string.Format("{0}", #"C:\OCRonImage2.py");
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.RedirectStandardInput = true;
start.CreateNoWindow = true;
using (Process process = Process.Start(start))
{
StreamWriter streamWriter = process.StandardInput;
streamWriter.Write({imageString});
// ...
}
Be sure the way you encode imageString the same as the decoding is performed in the python script.
Hopefully one of these solutions will work for you.
As I work with the Anaconda distribution of Python, in my tests on an isolated conda environment, the OCR is successful with pytesseract through a Python script, on a test image.
Prerequisites to test:
install Anaconda and create an env called py3.7.4: conda create --name py3.7.4
activate the env with conda activate py3.7.4
install pytesseract with conda install -c conda-forge pytesseract
create a folder called Test and place a jpg file called ocr.jpg with the following sample image:
in the same Test folder also place a Python script called ocr_test.py with the following code:
import pytesseract
from PIL import Image
import argparse
parser = argparse.ArgumentParser(
description='perform OCR on image')
parser.add_argument("--path", "-p", help="path for image")
args = parser.parse_args()
print(pytesseract.image_to_string(Image.open(args.path)))
print("done")
The above snippet accepts the image path as a command line argument. The --path flag must be specified in order to pass the image path as an arg.
Now, in the C# code snippet below, we will:
launch the cmd shell
navigate to the workingDirectory Test folder by specifying the WorkingDirectory arg for the process.start() method.
activate Anaconda with the anaconda.bat file(replace the file path as per its location on your computer)
activate the above conda environment
call the Python script passing the imageFileName as an arg.
C# snippet:
using System.Diagnostics;
using System.Threading;
namespace PyTest
{
class Program
{
static void Main(string[] args)
{
string workingDirectory = #"C:\Test";
string imageFileName = "ocr.JPG";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
RedirectStandardInput = true,
UseShellExecute = false,
RedirectStandardOutput = false,
WorkingDirectory = workingDirectory
}
};
process.Start();
using (var sw = process.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
// Vital to activate Anaconda
sw.WriteLine(#"C:\Users\xxxxxxx\Anaconda3\Scripts\activate.bat");
Thread.Sleep(500);
// Activate your environment
sw.WriteLine("conda activate py3.7.4");
Thread.Sleep(500);
sw.WriteLine($"python ocr_test.py --path {imageFileName}");
Thread.Sleep(50000);
}
}
}
}
}
If you have followed the above steps, you should receive the following output on executing the C# snippet in Visual Studio:
Output:
Microsoft Windows [Version 10.0.18362.535]
(c) 2019 Microsoft Corporation. All rights reserved.
C:\xxxxxxx\Projects\Scripts>C:\Users\xxxxx\Anaconda3\Scripts\activate.bat
(base) C:\xxxxxx\Projects\Scripts>conda activate py3.7.4
(py3.7.4) C:\xxxxxxx\Projects\Scripts>python ocr_test.py --path ocr.JPG
Introduction
This is a test to see accuracy of Tesseract OCR
Test 1
Test 2
done
Note: I am unable to test with a standalone Python distro but I believe it should work just fine with that too. The key is to pass the image file path as an argument to the Python script too. That way, the image file path passed as argument from C# is treated similarly by Python too. Also, using Image.open() does the following(from the docs):
Opens and identifies the given image file. This is a lazy operation;
this function identifies the file, but the file remains open and the
actual image data is not read from the file until you try to process
the data
You can save the image as a file somewhere on your local machine and give the python program the path to read it.
That's the easiest way I think you can do.
Edited: You can use a temporary file to make sure the file can be deleted in the future
http://www.java2s.com/Tutorial/CSharp/0300__File-Directory-Stream/Createatempfileanddeleteit.htm
http://www.vcskicks.com/temporary-file.php
I think this will be bad to pass IMAGE as Argument.
Good options to go with:
Stdin on your python example, and RedirectStandardInput on your c#.
TCP Communication. Using TCP Services (No Internet Needed)
Sharing Memory. (More Info, ReadyLib)

How to execute Git commands using cmd.exe being called from another application?

I have an application that generates some files. Once the files are generated, i want to then perform some Git commands to start a local repository. I have spent a few days on multiple solutions and endless googling but i can't get this to work as expected.
If i manually kick off the .exe, locally or on a server, it works perfectly. However, when the application(MVC) calls the .exe it doesn't work locally or on a server. I will provide the code that calls the .exe itself and then what the .exe code is doing.
MVC call to run .exe
//Call git exe
var processInfo = new ProcessStartInfo()
{
Arguments = Arg1 + " " + Arg2,
UseShellExecute = false,
FileName = #"C:\Foo.exe"
};
var process = new Process()
{
StartInfo = processInfo
};
process.Start();
process.WaitForExit();
This .exe gets launched without issue so this part is working i think. I checked it via task manager.
Git command execution
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
RedirectStandardInput = true,
UseShellExecute = false,
WorkingDirectory = Properties.Settings.Default.OutPutDirectory
},
};
process.Start();
using (var writer = process.StandardInput)
{
writer.WriteLine("git init");
writer.WriteLine("git add --all");
writer.WriteLine("git commit --author=\"" + ParseUserNameFromEmail() + " <" + _userEmailAddress + ">\" -m \"Initial Commit\"");
}
If i execute this part manually from the cmd prompt it works perfect on both local and server. But as soon as this .exe is called from an MVC app, it runs but the git repo isn't created as expected. I am at a total lost. I have tried running the process with my creds and a service account. I have also tried capturing the output in hope to shed light on what the issue could be but it's just empty on error and output redirects :(.

Executing batch file in WCF

I am trying to execute a batch file server-side in IIS to add a printer using the printuientry call.
The problem I am facing is that I am using the Copy To Output Directory - Copy Always and the following code:
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
var processInfo = new ProcessStartInfo(Path.Combine(path, "AddPrinter.bat"))
{
CreateNoWindow = true,
UseShellExecute = false,
WorkingDirectory = path,
Arguments = ipAddress,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
};
var process = Process.Start(processInfo);
process.WaitForExit(10000);
process.Close();
Now when deugging, I have checked the values of path and its set to
file:\C:\_Projects\PrinterServerV2\bin
and I have checked to see if the file and directory exist which they do.
But I get the exception:
System.ComponentModel.Win32Exception (0x80004005): The directory name is invalid
Any ideas please??
Check if the user you had set in the iis configuration does have all privileges to run, access, write and read what you wanna do with your batch file.
Also try to change your ProcessStartInfo like the process will be cmd.exe and your batch file the argument.
I had a similar issue How to execute multiples .BAT files in C#
try AppDomain.CurrentDomain.BaseDirectory as path.

Process.StandardOutput.ReadToEnd() and extraneous newline chars

I am trying to understand why, when I call the above function, I am getting hex 0D0A every 80th column on the output I am reading.
I have a powershell script, for testing that has two lines in it for brevity's sake:
$xmlSpew = "<IISAppPoolConfig><DefaultWebSite><ApplicationPoolName>DefaultAppPool</ApplicationPoolName></DefaultWebSite><AuthN>Basic</AuthN></IISAppPoolConfig>"
Write-Output $xmlSpew
I am calling the script using the Process object with ProcessStartInfo as follows:
var psi = new ProcessStartInfo
{
WorkingDirectory = Path.GetDirectoryName(FileToRun),
FileName = FileToRun,
Arguments = Arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
};
var process = new Process
{
StartInfo = psi,
EnableRaisingEvents = true,
};
FileToRun value is:
C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
Arguments value is:
-File "C:\Program Files\MyTest\MyProgInputs\read.d\IISAppPoolConfig.ps1"
The script runs fine and I get back exit code 0, but I have this mysterious (to me) 0D0A newline every 80th char in standard out that I capture using:
var Stdout = new List<string>;
...
Stdout.Add(process.StandardOutput.ReadToEnd());
This is wreaking havoc on my XML efforts once I have standard out stored in a string var. I expected to get exactly what I write to the stdout in the ps1 script, not the extra newline.
What am I missing? I've looked for others with this issue, but I have not found an answer. Hopefully it is not me being search-challenged.
Follow this P/Invoke method and set dwXCountChars to a very large value. Don't forget to include STARTF_USECOUNTCHARS in the flags as well.
Final and tested resolution for now (because I need to ship something), is to have output from powershell come from the Write-Host cmdlet instead of Write-Output. The process for obtaining stdout remained the same as in my original post. Hope this helps others. Thanks for all the inputs.

Categories

Resources