Automatically find the path of the python executable - c#

I'm doing a project that uses python as background script and C# as guy.
My problem is that I can't figure out how to cause my GUI to automatically search for the pythonw.exe file in order to run my python scripts.
Currently I'm using this path:
ProcessStartInfo pythonInfo = new ProcessStartInfo(#"C:\\Users\\Omri\\AppData\\Local\\Programs\\Python\\Python35-32\\pythonw.exe");
but I want it to auto detect the path of pythonw.exe (I need to submit the project and it won't run on others computers unless they change the code itself)
Any suggestions may be helpful.

Inspired by #Shashi Bhushan's answer I made this function for getting the Python path reliably;
private static string GetPythonPath(string requiredVersion = "", string maxVersion = "") {
string[] possiblePythonLocations = new string[3] {
#"HKLM\SOFTWARE\Python\PythonCore\",
#"HKCU\SOFTWARE\Python\PythonCore\",
#"HKLM\SOFTWARE\Wow6432Node\Python\PythonCore\"
};
//Version number, install path
Dictionary<string, string> pythonLocations = new Dictionary<string, string>();
foreach (string possibleLocation in possiblePythonLocations) {
string regKey = possibleLocation.Substring(0, 4), actualPath = possibleLocation.Substring(5);
RegistryKey theKey = (regKey == "HKLM" ? Registry.LocalMachine : Registry.CurrentUser);
RegistryKey theValue = theKey.OpenSubKey(actualPath);
foreach (var v in theValue.GetSubKeyNames()) {
RegistryKey productKey = theValue.OpenSubKey(v);
if (productKey != null) {
try {
string pythonExePath = productKey.OpenSubKey("InstallPath").GetValue("ExecutablePath").ToString();
// Comment this in to get (Default) value instead
// string pythonExePath = productKey.OpenSubKey("InstallPath").GetValue("").ToString();
if (pythonExePath != null && pythonExePath != "") {
//Console.WriteLine("Got python version; " + v + " at path; " + pythonExePath);
pythonLocations.Add(v.ToString(), pythonExePath);
}
} catch {
//Install path doesn't exist
}
}
}
}
if (pythonLocations.Count > 0) {
System.Version desiredVersion = new System.Version(requiredVersion == "" ? "0.0.1" : requiredVersion),
maxPVersion = new System.Version(maxVersion == "" ? "999.999.999" : maxVersion);
string highestVersion = "", highestVersionPath = "";
foreach (KeyValuePair<string, string> pVersion in pythonLocations) {
//TODO; if on 64-bit machine, prefer the 64 bit version over 32 and vice versa
int index = pVersion.Key.IndexOf("-"); //For x-32 and x-64 in version numbers
string formattedVersion = index > 0 ? pVersion.Key.Substring(0, index) : pVersion.Key;
System.Version thisVersion = new System.Version(formattedVersion);
int comparison = desiredVersion.CompareTo(thisVersion),
maxComparison = maxPVersion.CompareTo(thisVersion);
if (comparison <= 0) {
//Version is greater or equal
if (maxComparison >= 0) {
desiredVersion = thisVersion;
highestVersion = pVersion.Key;
highestVersionPath = pVersion.Value;
} else {
//Console.WriteLine("Version is too high; " + maxComparison.ToString());
}
} else {
//Console.WriteLine("Version (" + pVersion.Key + ") is not within the spectrum.");
}
}
//Console.WriteLine(highestVersion);
//Console.WriteLine(highestVersionPath);
return highestVersionPath;
}
return "";
}

You can find python installation path by lookup following keys on windows machine.
HKLM\SOFTWARE\Python\PythonCore\versionnumber\InstallPath
HKCU\SOFTWARE\Python\PythonCore\versionnumber\InstallPath
for win64 bit machine
HKLM\SOFTWARE\Wow6432Node\Python\PythonCore\versionnumber\InstallPath
You can refer this post for how to read registry using C#
How to read value of a registry key c#

Find the environment variable name in Windows, for that assembly and use Environment.GetEnvironmentVariable(variableName)
Check out How to add to the pythonpath in windows 7?

An example on how to search for Python within the PATH environment variable:
var entries = Environment.GetEnvironmentVariable("path").Split(';');
string python_location = null;
foreach (string entry in entries)
{
if (entry.ToLower().Contains("python"))
{
var breadcrumbs = entry.Split('\\');
foreach (string breadcrumb in breadcrumbs)
{
if (breadcrumb.ToLower().Contains("python"))
{
python_location += breadcrumb + '\\';
break;
}
python_location += breadcrumb + '\\';
}
break;
}
}

Just change the FileName to "python.exe" if you already set python env path
private void runPython(string cmd, string args)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = "python.exe";
start.Arguments = string.Format("{0} {1}", cmd, args);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.Write(result);
}
}
}

On my machine, with Python 3.11 installed, I can query it by defining this property:
public string PythonInstallPath
{
get => (string)Microsoft.Win32.Registry.GetValue(
#"HKEY_CURRENT_USER\SOFTWARE\Python\PythonCore\3.11\InstallPath",
"ExecutablePath", null);
}
Pythonw.exe is located in the same path, so you can do:
public string PythonWInstallPath
{
get => System.IO.Path.Combine(System.IO.Path.GetDirectoryName(PythonInstallPath),
"pythonw.exe");
}
There is also a way to look it up in the environment, check this out as an alternative.

Related

Mimic Linux which command in C#

I use a tool's binaries in a C# project called GraphViz.
The problem is I must include the binaries path as hard-coded and I don't want to do that.
IRenderer renderer = new Renderer("C:\\Program Files (x86)\\Graphviz2.38\\bin"); // todo: remove hardcoded GraphViz path
I want to mimic the linux which command.
Simply passing the binary name (e.g dot) and get the path.
GetBinaryPath("dot"); // return the above path
I'd appreciate any ideas or topics to start searching.
Note
Target OS: Windows
.NET version : 4
If you need to find path given only executable name (and installation directory is in your PATH environment variable)
Option 1:
Using where command with Process class. (test for exit code, parse the output)
Option 2:
You can get environment PATH environment variable, split it by ';' and test for your executable name existence.
First you need to find all directories where windows search for a exectuable file and that is from the environment variable %PATH%.
Then you need to find all extensions (.com, .exe, .bat etc) from %PATHEXT%.
Then you just check them like this:
internal class Program {
private static void Main(string[] args) {
if (args.Length != 1) {
Console.WriteLine("Incorrect usage!");
return;
}
var extensions = GetExecutableExtensions(args[0]);
var paths = GetPaths();
var exeFile = GetFirstExecutableFile(args[0], extensions, paths);
if (exeFile == null) {
Console.WriteLine("No file found!");
}
else {
Console.WriteLine(exeFile);
}
}
private static string GetFirstExecutableFile(string file, string[] extensions, string[] paths) {
foreach (var path in paths) {
var filename = Path.Combine(path, file);
if (extensions.Length == 0) {
if (File.Exists(filename)) {
return filename;
}
}
else {
foreach (var ext in extensions) {
filename = Path.Combine(path, file + ext);
if (File.Exists(filename)) {
return filename;
}
}
}
}
return null;
}
private static string[] GetExecutableExtensions(string file) {
var data = GetCmdOutput("echo %PATHEXT%");
var arr = data.TrimEnd('\n', '\r').Split(new [] {';'}, StringSplitOptions.RemoveEmptyEntries);
//If the command passed in ends with a executable extension then we dont need to test all extensions so set it to emtpy array
foreach (var ext in arr) {
if (file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) {
return new string[0];
}
}
return arr;
}
private static string[] GetPaths() {
var data = GetCmdOutput("echo %PATH%");
return data.TrimEnd('\n', '\r').Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
private static string GetCmdOutput(string cmd) {
using (var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "cmd.exe",
Arguments = "/c " + cmd,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
}) {
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
}
}

Process.Start doesn't open URL on Windows Server 2008 R2

I am using the following code to successfully open the requested URL in the system default browser on Windows 8.1:
public static void OpenUrlInDefaultBrowser(string url)
{
var httpKey = Registry.ClassesRoot.OpenSubKey(#"http\shell\open\command");
if (httpKey == null || httpKey.GetValue(string.Empty) == null)
return;
var cmd = httpKey.GetValue(string.Empty) as string;
if (cmd != null)
{
try
{
if (cmd.Length > 0)
{
string[] splitStr;
string fileName;
string args;
if (cmd.Substring(0, 1) == "\"")
{
splitStr = cmd.Split(new[] { "\" " }, StringSplitOptions.None);
fileName = splitStr[0] + "\"";
args = cmd.Substring(splitStr[0].Length + 2);
}
else
{
splitStr = cmd.Split(new[] { " " }, StringSplitOptions.None);
fileName = splitStr[0];
args = cmd.Substring(splitStr[0].Length + 1);
}
System.Diagnostics.Process.Start(fileName, args.Replace("%1", url));
}
}
catch (Exception)
{
// handle exception
}
}
httpKey.Close();
}
However, on my Windows Server 2008 R2 VM, this same code opens Internet Explorer (the default on that machine), but will only load the URL res://iesetup.dll/SoftAdmin.htm. IE is set to Enhanced Security Mode OFF. Chrome works as expected on this machine.
Simply invoking Process.Start(url) also fails to open the requested URL.
When I execute the following from the "Run..." menu, it works as expected: "C:\Program Files\Internet Explorer\iexplore.exe" http://example.com. The same goes for start-process http://example.com in PowerShell.
Did you try simply calling Process.Start("http://your_website_here"); ? It's not like you need to specify browser, if you want to run hyperlinks on default one.
#davidsbro - yes, i wouldn't have made it as answer otherwise :)
#mark - try http://blog.blksthl.com/2012/11/28/how-to-disable-ie-enhanced-security-in-windows-server-2012/ - that has something to do with server security settings and not application.
I wrote a modified version of your above method to start ie with command line options and is a little more detailed in figuring out which process to start.
Also, try changing the Process.Start calls in the code I'm posting to use a specific user and password.
class Program
{
static void Main(string[] args)
{
OpenUrlInBrowser("http://www.stackoverflow.com");
}
public static string GetDefaultBrowserCommand()
{
string command = null;
var httpKey = Registry.ClassesRoot.OpenSubKey(#"http\shell\open\command");
if (httpKey == null)
throw new Exception("No default browser is configured!");
else
{
command = (string)httpKey.GetValue("", "iexplore.exe");
int exeIndex = command.ToLower().IndexOf(".exe");
if (exeIndex > -1)
{
int endOfCommand = command.IndexOf('"', exeIndex);
int startOfCommand = command.LastIndexOf('"', exeIndex - 1);
if (startOfCommand > -1 && endOfCommand > -1)
{
command = command.Substring(startOfCommand + 1, endOfCommand - 1);
return command;
}
else
throw new Exception("Error: Default browser is not set in the registry correctly!");
}
else
throw new Exception("The default browser registry setting does not specify an executable!");
}
}
public static void OpenUrlInBrowser(string url)
{
string browserCommand = GetDefaultBrowserCommand();
FileInfo fi = new FileInfo(browserCommand);
ProcessStartInfo psi = null;
if (!fi.Exists)
Console.WriteLine("The default browser specified in the registry does not physical exist on disk!");
else
{
string commandFileName = Path.GetFileNameWithoutExtension(fi.FullName).ToLower();
switch (commandFileName)
{
case "iexplore":
psi = new ProcessStartInfo(browserCommand, string.Concat("\"", url, "\"", " ", "-extoff -new"));
break;
default:
psi = new ProcessStartInfo(browserCommand, string.Concat("\"", url, "\""));
break;
}
}
psi.UseShellExecute = true; //<- have to set this to make runas work
psi.Verb = "runas"; //<- instructs the process to runas administrator, which will give the user a UAC prompt if UAC is turned on.
Process.Start(psi);
}
}

How to check that a jre environment is 32 bit or 64 bit programmatically with C#?

I have a jre folder which is basically java runtime, it is not installed I have copied this folder from somewhere else, now I need to check this jre is 32 bit or 64 bit with manual inspection without writing any code and if code has to be written then it should be c#.
All the example tell system.getproperty("java...model") something for getting the target type of the installed jre, but I dont have this jre installed, rather I have just copied this jre.
so is there any way to know its target type is it 32 bit or 64 bit.
C# Code
// *** Main code
string output = RunExternalExe("java.exe -version");
// Parse output here...
// *** Helper methods
public string RunExternalExe(string filename, string arguments = null)
{
var process = new Process();
process.StartInfo.FileName = filename;
if (!string.IsNullOrEmpty(arguments))
{
process.StartInfo.Arguments = arguments;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
var stdOutput = new StringBuilder();
process.OutputDataReceived += (sender, args) => stdOutput.Append(args.Data);
string stdError = null;
try
{
process.Start();
process.BeginOutputReadLine();
stdError = process.StandardError.ReadToEnd();
process.WaitForExit();
}
catch (Exception e)
{
throw new Exception("OS error while executing " + Format(filename, arguments)+ ": " + e.Message, e);
}
if (process.ExitCode == 0)
{
return stdOutput.ToString();
}
else
{
var message = new StringBuilder();
if (!string.IsNullOrEmpty(stdError))
{
message.AppendLine(stdError);
}
if (stdOutput.Length != 0)
{
message.AppendLine("Std output:");
message.AppendLine(stdOutput.ToString());
}
throw new Exception(Format(filename, arguments) + " finished with exit code = " + process.ExitCode + ": " + message);
}
}
private string Format(string filename, string arguments)
{
return "'" + filename +
((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
"'";
}
COMMAND sample output
On my box I have a 64bit java version. Here is its output:
C:\Program Files\Java\jdk1.7.0_45\bin>java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
Note the 64-Bit mention. You may find this SO answer useful.
You could use the GNU file utility to check whether or not java.exe is a 64-bit-executable.
See this link for more information about that.
This way, you can avoid starting the unknown java.exe from your code, but instead your program has to start the file utility you have provided yourself.
I know I am late to this, but I wrote these methods in C# so that it can detect both 32/64-bit JRE and JDK because the provided answers don't suffice my need
private string CheckJreJdkVersion()
{
StringBuilder sb = new StringBuilder();
string jre = #"SOFTWARE\JavaSoft\Java Runtime Environment";
string jdk = #"SOFTWARE\JavaSoft\Java Development Kit";
string jreInstallDirValueName = "INSTALLDIR";
string jdkInstallDirValueName = "JavaHome";
// Check 32-bit JRE
GetJreJdkVersion(jre, RegistryView.Registry32, jreInstallDirValueName, ref sb, "JRE", "32");
// Check 64-bit JRE
GetJreJdkVersion(jre, RegistryView.Registry64, jreInstallDirValueName, ref sb, "JRE", "64");
// Check 32-bit JDK
GetJreJdkVersion(jdk, RegistryView.Registry32, jdkInstallDirValueName, ref sb, "JDK", "32");
// Check 64-bit JDK
GetJreJdkVersion(jdk, RegistryView.Registry64, jdkInstallDirValueName, ref sb, "JDK", "64");
string res = sb.ToString();
return res;
}
private void GetJreJdkVersion(string jreJdkPath, RegistryView registryView, string jreJdkInstallDirValueName, ref StringBuilder sb, string jreJdk, string bitVer)
{
RegistryKey key = GetRegistryKeyHKLM(jreJdkPath, registryView);
if (key == null)
return;
List<string> lstVersions = new List<string>();
foreach (var version in key.GetSubKeyNames())
{
lstVersions.Add(version);
}
IEnumerable<string> relevantVersions = GetRelevantVersions(lstVersions);
foreach (var relevantVersion in relevantVersions)
{
string regPath = string.Empty;
if (jreJdk == "JRE")
{
regPath = Path.Combine(jreJdkPath, Path.Combine(relevantVersion, "MSI"));
}
else
{
regPath = Path.Combine(jreJdkPath, relevantVersion);
}
string installDir = GetRegistryValueHKLM(regPath, jreJdkInstallDirValueName, registryView);
sb.Append("Detected " + bitVer + "-bit " + jreJdk + ", install directory: " + installDir + "\n");
}
}
private IEnumerable<string> GetRelevantVersions(List<string> lstVersions)
{
IEnumerable<string> versions = lstVersions.Where(version => version.Contains("_"));
return versions;
}
public RegistryKey GetRegistryKeyHKLM(string keyPath, RegistryView view)
{
RegistryKey localMachineRegistry
= RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
return string.IsNullOrEmpty(keyPath)
? localMachineRegistry
: localMachineRegistry.OpenSubKey(keyPath);
}
public string GetRegistryValueHKLM(string keyPath, string keyName, RegistryView view)
{
RegistryKey registry = GetRegistryKeyHKLM(keyPath, view);
if (registry == null) return null;
string value = string.Empty;
try
{
value = registry.GetValue(keyName).ToString();
}
catch (Exception)
{
}
return value;
}
Sample output:
Detected 32-bit JRE, install directory: C:\Program Files (x86)\Java\jre7\
Detected 32-bit JRE, install directory: C:\Program Files (x86)\Java\jre1.8.0_73\
Detected 32-bit JRE, install directory: C:\New folder\
Detected 64-bit JRE, install directory: C:\Program Files\Java\jre7\
Detected 32-bit JDK, install directory: C:\jdk fol

command line output validation in c#

My output in the actual command prompt looks like this:
Name: My Software
Version: 1.0.1
Installed location: c:\my folder
I am trying to get this output via c# code
System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + "my command to execute");
// The following commands are needed to redirect the standard output.
// This means that it will be redirected to the Process.StandardOutput StreamReader.
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now we create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
string result = proc.StandardOutput.ReadToEnd();
string[] lines = result.Split(new string[] { System.Environment.NewLine, }, System.StringSplitOptions.None);
foreach (string tmp in lines)
{
if (tmp.Contains("Version"))
{
isAvailable= true;
}
}
I don't want to just check a version tag, I am trying to get the version value and do a compare, for example if the value is 1.0.1, i would want that value and do a comparison with 2.0.0.
I can use indexof(like result.IndexOf("Version:");) - But that doesn't get me the value of the version
Any thoughts will be helpful.
You should use the .NET Version class and it's CompareTo(Object) method to do your comparison.
var input = new Regex(#"(?<=Version:)\s*(.*)").Matches(#"Name: My Software
Version: 1.0.1
Installed location: c:\my folder")[0].Value.Trim();
var a = new Version(input);
var b = new Version("2.0.0");
int comparison = a.CompareTo(b);
if(comparison > 0)
{
Console.WriteLine(a + " is a greater version.");
}
else if(comparison == 0)
{
Console.WriteLine(a + " and " + b +" are the same version.");
}
else
{
Console.WriteLine(b + " is a greater version.");
}
Try like below... it will help you...
Instead of Contains check the word by using IndexOf...
if (tmp.IndexOf("Version") != -1)
{
isAvailable = true;
string[] info = tmp.Split(':');
string version = info[1].Trim();
Console.WriteLine(version);
}
string versionText;
var stuff = tmp.Split(":");
if(stuff[0].Trim() == "Version")
{
isAvailable = true;
versionText = stuff[1].Trim();
}
if(versionText == expectedVersionText) // Do something specfic.
You might want to use Regular Expressions:
^Version:\s*(.*)$
should match the version number inside the parentheses.
string sought = "Version:";
foreach (string tmp in lines)
{
if (tmp.Contains(sought))
{
int position = tmp.IndexOf(sought) + sought.Length;
string version = tmp.Substring(tmp.IndexOf(sought) + sought.Length);
string[] versionParts = version.Split('.');
VersionCompare(versionParts, new string[]{"2", "0", "0"});
}
}
/// <summary>Returns 0 if the two versions are equal, else a negative number if the first is smaller, or a positive value if the first is larder and the second is smaller.</summary>
private int VersionCompare(string[] left, string[] right)
{
for(int i = 0; i < Math.Min(left.Length, right.Length); ++i)
{
int leftValue = int.Parse(left[i]), rightValue = int.Parse(right[i]);
if(leftValue != rightValue) return leftValue - rightValue;
}
return left.Length - right.Length;
}

Correct method to get installed application path by app. name in C#

I use this function to find any installed application.
But the parameter "InstallLocation" doesn't work at all.
Any clue?
Thanks!!
void FindApplication(string appName)
{
StringBuilder sbProductCode = new StringBuilder(39);
int iIdx = 0;
while (0 == MsiEnumProducts(iIdx++, sbProductCode))
{
Int32 productNameLen = 512;
StringBuilder sbProductName = new StringBuilder(productNameLen);
MsiGetProductInfo(sbProductCode.ToString(), "ProductName", sbProductName, ref productNameLen);
if (sbProductName.ToString().Contains(appName))
{
Int32 installDirLen = 2048;
StringBuilder sbInstallDir = new StringBuilder(installDirLen);
MsiGetProductInfo(sbProductCode.ToString(),"InstallLocation", sbInstallDir, ref installDirLen);
string result = string.Format("ProductName {0}: {1}", sbProductName, sbInstallDir);
}
}
}
I've visited the following links, and they don't appear to be outdated:
MsiGetProductInfo function
MsiGetProductInfoEx function
The only keys I see that can be used are these:
ARPINSTALLLOCATION
INSTALLDIR
INSTALLPROPERTY_INSTALLLOCATION
INSTALLLOCATION
I should point out that it appears MsiGetProductInfoEx (second link) should be used to gather information on published/installed products added by another user; and requires administrative privledges.
I found other solution and it works fine.
string FindPathByInstalledAppEXEName(string appEXEName)
{
string path = string.Empty;
try
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Installer\Assemblies");
string regfilepath = string.Empty;
if (key != null) // Make sure there are Assemblies
{
foreach (string Keyname in key.GetSubKeyNames())
{
if (Keyname.IndexOf(appEXEName) > 0)
{
regfilepath = Keyname;
break;
}
}
}
if (!string.IsNullOrEmpty(regfilepath))
{
string fullpath = "";
for (int a = 0; a < regfilepath.Length; a++)
{
if (regfilepath.IndexOf("|", a, 1) > 0)
fullpath += "\\";
else
fullpath += regfilepath.Substring(a, 1);
}
path = fullpath.Substring(0, fullpath.LastIndexOf("\\") + 1);
}
}
catch // (Exception ex)
{
}
return path;
}

Categories

Resources