fix: Autodetect pwsh.exe & DLL path (Win/non-WSL) (#11044)

This commit is contained in:
Aphix
2025-11-03 08:27:30 -05:00
committed by GitHub
parent 7ef1720b5d
commit b85cc0c716

View File

@@ -5,6 +5,8 @@ way to manage PowerShell processes compared to using temporary script files.
""" """
import os import os
import re
import subprocess
import time import time
from pathlib import Path from pathlib import Path
from threading import RLock from threading import RLock
@@ -45,39 +47,121 @@ except Exception as coreclr_ex:
logger.error(f'{error_msg} Details: {details}') logger.error(f'{error_msg} Details: {details}')
raise DotNetMissingError(error_msg, 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 # Attempt to load the PowerShell SDK assembly only if clr and System loaded
ps_sdk_path = None ps_sdk_path = None
try: try:
# Prioritize PowerShell 7+ if available (adjust path if necessary) # Attempt primary detection via helper function
pwsh7_path = ( ps_sdk_path = find_latest_pwsh_sdk_path()
Path(os.environ.get('ProgramFiles', 'C:\\Program Files')) if ps_sdk_path:
/ 'PowerShell'
/ '7'
/ 'System.Management.Automation.dll'
)
if pwsh7_path.exists():
ps_sdk_path = str(pwsh7_path)
clr.AddReference(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: else:
# Fallback to Windows PowerShell 5.1 bundled with Windows pwsh7_path = (
winps_path = ( Path(os.environ.get('ProgramFiles', 'C:\\Program Files'))
Path(os.environ.get('SystemRoot', 'C:\\Windows')) / 'PowerShell'
/ 'System32' / '7'
/ 'WindowsPowerShell'
/ 'v1.0'
/ 'System.Management.Automation.dll' / 'System.Management.Automation.dll'
) )
if winps_path.exists(): if pwsh7_path.exists():
ps_sdk_path = str(winps_path) ps_sdk_path = str(pwsh7_path)
clr.AddReference(ps_sdk_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: else:
# Last resort: try loading by assembly name (might work if in GAC or path) # Fallback to Windows PowerShell 5.1 bundled with Windows
clr.AddReference('System.Management.Automation') winps_path = (
logger.info( Path(os.environ.get('SystemRoot', 'C:\\Windows'))
'Attempted to load PowerShell SDK by name (System.Management.Automation)' / '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 import JobState, PowerShell
from System.Management.Automation.Language import Parser from System.Management.Automation.Language import Parser