mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
fix: Autodetect pwsh.exe & DLL path (Win/non-WSL) (#11044)
This commit is contained in:
@@ -5,6 +5,8 @@ way to manage PowerShell processes compared to using temporary script files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import RLock
|
||||
@@ -45,39 +47,121 @@ except Exception as coreclr_ex:
|
||||
logger.error(f'{error_msg} Details: {details}')
|
||||
raise DotNetMissingError(error_msg, details)
|
||||
|
||||
|
||||
def find_latest_pwsh_sdk_path(
|
||||
executable_name='pwsh.exe',
|
||||
dll_name='System.Management.Automation.dll',
|
||||
min_version=(7, 0, 0),
|
||||
env_var='PWSH_DIR',
|
||||
):
|
||||
"""
|
||||
Checks PWSH_DIR environment variable first to find pwsh and DLL.
|
||||
If not found or not suitable, scans all pwsh executables in PATH, runs --version to find latest >= min_version.
|
||||
Returns full DLL path if found, else None.
|
||||
"""
|
||||
|
||||
def parse_version(output):
|
||||
# Extract semantic version from pwsh --version output
|
||||
match = re.search(r'(\d+)\.(\d+)\.(\d+)', output)
|
||||
if match:
|
||||
return tuple(map(int, match.groups()))
|
||||
return None
|
||||
|
||||
# Try environment variable override first
|
||||
pwsh_dir = os.environ.get(env_var)
|
||||
if pwsh_dir:
|
||||
pwsh_path = Path(pwsh_dir) / executable_name
|
||||
dll_path = Path(pwsh_dir) / dll_name
|
||||
if pwsh_path.is_file() and dll_path.is_file():
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[str(pwsh_path), '--version'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if completed.returncode == 0:
|
||||
ver = parse_version(completed.stdout)
|
||||
if ver and ver >= min_version:
|
||||
logger.info(f'Found pwsh from env variable "{env_var}"')
|
||||
return str(dll_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Adjust executable_name for Windows if needed
|
||||
if os.name == 'nt' and not executable_name.lower().endswith('.exe'):
|
||||
executable_name += '.exe'
|
||||
|
||||
# Search PATH for all pwsh executables
|
||||
paths = os.environ.get('PATH', '').split(os.pathsep)
|
||||
candidates = []
|
||||
for p in paths:
|
||||
exe_path = Path(p) / executable_name
|
||||
if exe_path.is_file() and os.access(str(exe_path), os.X_OK):
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[str(exe_path), '--version'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if completed.returncode == 0:
|
||||
ver = parse_version(completed.stdout)
|
||||
if ver:
|
||||
candidates.append((ver, exe_path.resolve()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Sort candidates by version descending
|
||||
candidates.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
for ver, exe_path in candidates:
|
||||
if ver >= min_version:
|
||||
dll_path = exe_path.parent / dll_name
|
||||
if dll_path.is_file():
|
||||
return str(dll_path)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# Attempt to load the PowerShell SDK assembly only if clr and System loaded
|
||||
ps_sdk_path = None
|
||||
try:
|
||||
# Prioritize PowerShell 7+ if available (adjust path if necessary)
|
||||
pwsh7_path = (
|
||||
Path(os.environ.get('ProgramFiles', 'C:\\Program Files'))
|
||||
/ 'PowerShell'
|
||||
/ '7'
|
||||
/ 'System.Management.Automation.dll'
|
||||
)
|
||||
if pwsh7_path.exists():
|
||||
ps_sdk_path = str(pwsh7_path)
|
||||
# Attempt primary detection via helper function
|
||||
ps_sdk_path = find_latest_pwsh_sdk_path()
|
||||
if ps_sdk_path:
|
||||
clr.AddReference(ps_sdk_path)
|
||||
logger.info(f'Loaded PowerShell SDK (Core): {ps_sdk_path}')
|
||||
logger.info(f'Loaded PowerShell SDK dynamically detected: {ps_sdk_path}')
|
||||
else:
|
||||
# Fallback to Windows PowerShell 5.1 bundled with Windows
|
||||
winps_path = (
|
||||
Path(os.environ.get('SystemRoot', 'C:\\Windows'))
|
||||
/ 'System32'
|
||||
/ 'WindowsPowerShell'
|
||||
/ 'v1.0'
|
||||
pwsh7_path = (
|
||||
Path(os.environ.get('ProgramFiles', 'C:\\Program Files'))
|
||||
/ 'PowerShell'
|
||||
/ '7'
|
||||
/ 'System.Management.Automation.dll'
|
||||
)
|
||||
if winps_path.exists():
|
||||
ps_sdk_path = str(winps_path)
|
||||
if pwsh7_path.exists():
|
||||
ps_sdk_path = str(pwsh7_path)
|
||||
clr.AddReference(ps_sdk_path)
|
||||
logger.debug(f'Loaded PowerShell SDK (Desktop): {ps_sdk_path}')
|
||||
logger.info(f'Loaded PowerShell SDK (Core): {ps_sdk_path}')
|
||||
else:
|
||||
# Last resort: try loading by assembly name (might work if in GAC or path)
|
||||
clr.AddReference('System.Management.Automation')
|
||||
logger.info(
|
||||
'Attempted to load PowerShell SDK by name (System.Management.Automation)'
|
||||
# Fallback to Windows PowerShell 5.1 bundled with Windows
|
||||
winps_path = (
|
||||
Path(os.environ.get('SystemRoot', 'C:\\Windows'))
|
||||
/ 'System32'
|
||||
/ 'WindowsPowerShell'
|
||||
/ 'v1.0'
|
||||
/ 'System.Management.Automation.dll'
|
||||
)
|
||||
if winps_path.exists():
|
||||
ps_sdk_path = str(winps_path)
|
||||
clr.AddReference(ps_sdk_path)
|
||||
logger.debug(f'Loaded PowerShell SDK (Desktop): {ps_sdk_path}')
|
||||
else:
|
||||
# Last resort: try loading by assembly name (might work if in GAC or path)
|
||||
clr.AddReference('System.Management.Automation')
|
||||
logger.info(
|
||||
'Attempted to load PowerShell SDK by name (System.Management.Automation)'
|
||||
)
|
||||
|
||||
from System.Management.Automation import JobState, PowerShell
|
||||
from System.Management.Automation.Language import Parser
|
||||
|
||||
Reference in New Issue
Block a user