Search code examples
pythonc#registrypath-variables

How can I use C# to check if a user has a minimum Python version installed and accessible to my code?


I am working on a C# program that has to run some Python code for one particular task. (The Python code is complex and was developed by another team. Reproducing its functionality in C# is not an option.)

I am trying to update my program's installer file to account for this: I want it to check if the user (who is installing my program) has Python installed and that it meets my minimum-version requirement. If not, I will prompt the user to download and install the latest version of Python.

However, I cannot figure out how to perform this check.

My own computer has both Python 3.9 and Python 3.10 installed, but when I type "python -V" in a command prompt, I can see that 3.10 is being used. When I debug my C# solution locally, all I need to do to run the Python code is use "python.exe" (I don't need to provide the full path). I assume this is all because my %PATH% environment variable contains C:\Python310. But how can my installer ask if the user's computer knows where to find python.exe and, if it can find it, what version it is?

If the answer is to check Windows registry keys, I'm not sure what to do there...

My registry has entries for both 3.10 and 3.9 in HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\, but I don't see anything there that is indicating that 3.10 will be used by default.

I found another Stack Overflow question where someone said to use the key at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Python.exe, but my registry does not have a value there at all, so this is definitely not a reliable approach.

Also, I know that I could just make my installer try to run "python.exe" the same way my code does, but run it with the "--version" argument and just check the version number that gets spit out, but I keep seeing warnings that this will only work if Python is in the user's %PATH% environment variable, and it's not clear to me whether installing Python would cause it to be added there by default or not.

So now I'm worried that I will need to do something very complicated, like... (pseudo-code)

try to run python.exe with --version

if a version is returned that is too low
   prompt to install
else if error or nothing returned
   if the registry path HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\ exists
      if version there is too low
         prompt to install
      else
         prompt to reinstall or prompt to add to PATH variable (this will be another question)
   else
      prompt to install

Is all of that really necessary? Is there a cleaner or faster way?


Solution

  • In the end, we decided to just try to run python.exe - without providing a particular path - to get the version number, and then handling whatever resulted from that appropriately. Error? Needs Python. No output? Needs Python. Output? Dig into the output to determine version number, and if it's too low... needs Python.

    Here is the call to get the version number:

    public static string GetPythonVersion()
    {
        ProcessStartInfo pycheck = new ProcessStartInfo();
        pycheck.FileName = @"python.exe";
        pycheck.Arguments = "--version";
        pycheck.UseShellExecute = false;
        pycheck.RedirectStandardOutput = true;
        pycheck.RedirectStandardError = true;
        pycheck.CreateNoWindow = true;
    
        using (Process process = Process.Start(pycheck) ?? throw new InvalidOperationException("Python process failed to start."))
        {
            // Read the output (or the error)
            string result = process.StandardOutput.ReadToEnd() ?? "";
            string error = process.StandardError.ReadToEnd() ?? "";
    
            // Wait for the process to exit
            process.WaitForExit();
    
            // Return the results
            if (error != null && error != "")
            {
                throw new InvalidOperationException("Unable to determine Python version. " + error);
            }
    
            return result;
        }
    }
    

    And here is where we handle it (note: I know it would be simpler to default needsPython to false; I have done it this way just for clarity and to assume the positive case):

    public static bool CheckForPython()
    {
        string pythonVers = "";
        bool needsPython = false;
    
        try
        {
            pythonVers = GetPythonVersion();
    
            // check if a value was returned
            if (pythonVers != null && pythonVers != "")
            {
                pythonVers = pythonVers.Replace("Python ", "");
                string[] versionDigits = pythonVers.Split(".");
    
                // check if the value contains a version number
                if (versionDigits.Length >= 2 && int.TryParse(versionDigits[0], out _) && int.TryParse(versionDigits[1], out _))
                {
                    int v0;
                    int v1;
                    int.TryParse(versionDigits[0], out v0);
                    int.TryParse(versionDigits[1], out v1);
    
                    // check if the version number is high enough
                    if (v0 < 3 || (v0 == 3 && v1 < 9))
                    {
                        needsPython = true;
                    }
                }
                else
                {
                    needsPython = true;
                }
            }
            else
            {
                needsPython = true;
            }
        }
        catch
        {
            needsPython = true;
        }
    
    
        if (needsPython)
        {
            // [omitted code: show dialog with message and link to download Python]
            return false; // do not proceed with installation of my program
        }
    
        return true;
    }