first commit
This commit is contained in:
107
proxy_chain_manager/win_compat.py
Normal file
107
proxy_chain_manager/win_compat.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Windows version / PowerShell capability probe.
|
||||
|
||||
Used by privacy features to log a single clear reason when a function silently
|
||||
no-ops on older Windows Server hosts (Server 2008 R2 ships PowerShell 2.0 which
|
||||
does not have ``Get-NetAdapter`` / ``Set-NetAdapter`` / ``Disable-NetAdapterBinding``
|
||||
/ ``Get-DnsClientServerAddress`` / ``Rename-Computer``).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WinCompat:
|
||||
release: str # e.g. "10", "2012ServerR2"
|
||||
build: int # major build number
|
||||
powershell_major: int # 0 = PowerShell not detected
|
||||
has_net_cmdlets: bool # Get-NetAdapter etc. (PS 3.0+ on Server 2012+)
|
||||
has_defender: bool # Add-MpPreference cmdlet exists
|
||||
|
||||
def summary(self) -> str:
|
||||
if sys.platform != "win32":
|
||||
return (
|
||||
f"{platform.system()} release={self.release} "
|
||||
f"PowerShell={self.powershell_major}.x "
|
||||
"Windows-only privacy controls disabled"
|
||||
)
|
||||
return (
|
||||
f"Windows release={self.release} build={self.build} "
|
||||
f"PowerShell={self.powershell_major}.x "
|
||||
f"NetAdapter cmdlets={'yes' if self.has_net_cmdlets else 'no'} "
|
||||
f"Defender={'yes' if self.has_defender else 'no'}"
|
||||
)
|
||||
|
||||
|
||||
def _powershell_major() -> int:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command",
|
||||
"$PSVersionTable.PSVersion.Major"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=12,
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||
)
|
||||
out = (r.stdout or "").strip().splitlines()[-1] if r.stdout else ""
|
||||
return int(out) if out.isdigit() else 0
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def _has_cmdlet(name: str) -> bool:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command",
|
||||
f"if (Get-Command {name} -ErrorAction SilentlyContinue) "
|
||||
f"{{ 'yes' }} else {{ 'no' }}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||
)
|
||||
return "yes" in (r.stdout or "").lower()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def probe() -> WinCompat:
|
||||
"""Cached host probe. Cheap on subsequent calls."""
|
||||
if sys.platform != "win32":
|
||||
info = WinCompat(
|
||||
release=platform.release(),
|
||||
build=0,
|
||||
powershell_major=0,
|
||||
has_net_cmdlets=False,
|
||||
has_defender=False,
|
||||
)
|
||||
log.info("Compat probe: %s", info.summary())
|
||||
return info
|
||||
release = platform.release()
|
||||
try:
|
||||
build = int(platform.version().split(".")[-1])
|
||||
except Exception:
|
||||
build = 0
|
||||
ps = _powershell_major()
|
||||
# PowerShell >= 3.0 is the gate for the modern Net* cmdlets that all the
|
||||
# privacy features rely on. Anything older (Server 2008 R2 RTM) only has
|
||||
# PS 2.0 and needs WMI / netsh / ipconfig fallbacks.
|
||||
has_net = ps >= 3 and _has_cmdlet("Get-NetAdapter")
|
||||
has_def = _has_cmdlet("Add-MpPreference")
|
||||
info = WinCompat(
|
||||
release=release,
|
||||
build=build,
|
||||
powershell_major=ps,
|
||||
has_net_cmdlets=has_net,
|
||||
has_defender=has_def,
|
||||
)
|
||||
log.info("WinCompat probe: %s", info.summary())
|
||||
return info
|
||||
Reference in New Issue
Block a user