How can I programmatically get the path of "Python.exe" used by ArcMap
Instead of looking for the Python executable, this help topic suggests shelling out to cmd.exe
and running python.exe
without qualifying its location. Note however, that this should work because the ArcGIS Desktop installer sets up (edit: recently tested at 10.1, it doesn't) relies upon the path to python.exe
being added to the user's PATH
environment variable.
Another approach is to create a script tool and execute it from ArcObjects.
If you are really after the path to ArcGIS's version of python.exe
, by extension of the ArcObjects + script tool approach, you could create a Python script tool whose only output is the value of sys.exec_prefix
. This is the path of the folder containing ArcGIS's version of Python, e.g. C:\Python27\ArcGIS10.1
.
Side note: sys.executable
returns the path to ArcMap.exe
and NOT python.exe
when run in-process, which is why I do not suggest using that variable.
Call the script tool from ArcObjects and get the output from the returned IGeoProcessorResult
object.
Update: Here is a sample ArcMap add-in project (VS2010, .NET 3.5) that uses a script tool packaged within the add-in that simply displays the path to the python.exe
used by ArcMap: http://wfurl.com/cbd5091
It's just a button you click and it pops up a messagebox with the path:
The interesting bits of code:
Python script:
import sys import os import arcpy def getPythonPath(): pydir = sys.exec_prefix pyexe = os.path.join(pydir, "python.exe") if os.path.exists(pyexe): return pyexe else: raise RuntimeError("No python.exe found in {0}".format(pydir)) if __name__ == "__main__": pyexe = getPythonPath() arcpy.AddMessage("Python Path: {0}".format(pyexe)) arcpy.SetParameterAsText(0, pyexe)
C# function:
public string GetPythonPath() { // Build the path to the PythonPathToolbox string toolboxPath = Path.Combine(Path.GetDirectoryName(this.GetType().Assembly.Location), "PythonPath.tbx"); // Initialize the geoprocessor. IGeoProcessor2 gp = new ESRI.ArcGIS.Geoprocessing.GeoProcessorClass(); // Add the PythonPath toolbox. gp.AddToolbox(toolboxPath); // Need an empty array even though we have no input parameters IVariantArray parameters = new VarArrayClass(); // Execute the model tool by name. var result = gp.Execute("GetPythonPath", parameters, null); return result.GetOutput(0).GetAsText(); }
Will you have access to the registry?
When installing ArcMap it will install Python if it can't find it. It looks in the registry to see if Python is already installed. I believe the standard registry location for this is: computer\HKEY_LOCAL_MACHINE\SOFTWARE\PYTHON\PythonCore\2.7\InstallPath With a default Key of the Path location (2.7 being 10.1, 2.6 being 10.0)
I cant think of a reason when/why the value of this key would be incorrect, but you could always go this way: Inside the Esri\Desktop hive of the registry is a Python location. Its the simple path which you could get and then build up further paths to ensure there is a Python.exe. For example, the key on a 64bit machine gets installed to: computer\HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ESRI\Python10.1 With a PythonDir key and associated Path value
But I like @blah238 answer. Just open a prompt from your program and run it there. I can't see a reason why this wouldnt work.
[Edit] While executing set
programmatically (struck out, below) did what I wanted, it can be accomplished more easily and with cleaner code using Environment.GetEnvironmentVariables().
One option would be to scan every Environment Variable on the system and attempt to prove the following:
1) Is the Environment Variable value a directory? (and if so..)
2) Does that directory contain python.exe
?
I was able to do this programmatically by executing the set
command through the .Net Process API. The set
command, when used without a parameter, returns ALL the Environment Variables in use by the system. So I could parase, then organize the STDOUT results emitted from set
, and screen them to see if anything whatsoever (and I mean ANYTHING) available through the system envionment ultimately pointed to python.exe
.
From this page discussing the set
command:
Type SET without parameters to display all the current environment variables.
To illustrate I wrote a combination of methods (and a helper class) that does what I discussed above. These can be optimized, and they could use some bullet-proofing (Try..Catch, etc.), but if the computer has ANY Environment Variable pointing to python.exe
, this approach should find it! I don't care if the var is called PATH
, ABBRACADABBRA
, or whatever.. if it points to python.exe
, this should find it.
// C#, you'll need these using statements:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
Here terms
is an Array of strings you pass-in to the routine to look for in either the environment variable's name, or its n
values (i.e. PATH
can have several values, but most other vars will have just one). Make sure all the strings in terms
are UPPERCASE!
(When I tested this, I used simply "PYTHON", which found C:\Python27\python.exe
on my home system. But you could easily extend it to include another string[] of terms if you wanted to further inspect the path of any python.exe
candidates returned---for example, to see if they were in the ArcGIS bin, etc.)
// Top-level method that organizes everything below..
private void scrapeEnvironmentVariables(string[] terms)
{
// !! ValueObject !! This is a Helper Class, find it at the bottom..
List<ValueObject> voList = buildListOfEnvironmentObjects();
foreach (ValueObject vo in voList)
{
bool candidateFound = ObjectMatchesSearchTerms(vo, terms);
if (candidateFound)
{
string exeCandidate = "";
foreach (string unlikelyPath in vo.values)
{
if (Directory.Exists(unlikelyPath))
{
string unlikelyExe = unlikelyPath + "\\python.exe";
if(File.Exists(unlikelyExe))
exeCandidate = unlikelyExe;
}
if (exeCandidate != "")
{
break;
// At this point, exeCandidate is a fully-qualified
// path to python.exe..
}
}
// If you only want the first hit, break here..
// As-is, the code will look for even more matches.
//if (breakOnFirstHit)
// break;
}
}
}
// Execute Environment.GetEnvironmentVariables() and organize the
// key..value pairs into 1:n ValueObjects (see Helper Class below).
private List<ValueObject> buildListOfEnvironmentObjects()
{
// Return a List of 1:n key..value objects.
List<ValueObject> voList = new List<ValueObject>();
IDictionary variableDictionary = Environment.GetEnvironmentVariables();
foreach (DictionaryEntry entry in variableDictionary)
{
// Explode multi-values into a List of values (n).
List<string> values = new List<string>();
string[] rawValues = ((string)entry.Value).Split(';');
foreach (string value in rawValues)
if (value != "") values.Add(value.ToUpper());
ValueObject valueObject = new ValueObject();
valueObject.key = ((string)entry.Key).ToUpper();
valueObject.values = values.ToArray();
voList.Add(valueObject);
}
return voList;
}
// Compare the key and any value(s) in a ValueObject with all the
// terms submitted to the top-level method. If **ALL** the terms
// match (against any combination of key..value), it returns true.
private bool ObjectMatchesSearchTerms(ValueObject vo, string[] terms)
{
int matchCount = 0;
foreach (string term in terms)
{
if (vo.key.Contains(term)) // screen the key
matchCount++;
foreach (string value in vo.values) // screen N values
{
if (value.Contains(term))
matchCount++;
}
}
// Test against >= because it's possible the match count could
// exceed the terms length, like if a match occurred in both the
// key and the value(s). So >= avoids omiting that possibility.
return (matchCount >= terms.Length) ? true : false;
}
And at the bottom of my main class, I included the following Helper Class:
class ValueObject : Object
{
public ValueObject() { } // default constructor
public string key;
public string[] values;
}