# Copyright 2026 Guildhouse Dev # SPDX-License-Identifier: Apache-2.0 """PowerShell connector — WinRM/PSRP sessions to Windows endpoints. Uses Kerberos credentials from the CredentialResolver (acquired via Entra cloud trust or on-prem KDC) to establish a PSRP (PowerShell Remoting Protocol) session over WinRM/HTTPS. Real integration: Library: ``pypsrp`` (well-maintained PSRP client) Protocol: PSRP over WinRM (HTTPS port 5986) Auth: Kerberos (from ``KerberosCredential``) or CredSSP Output: Structured PSObject results deserialized to dicts The transport will: 1. Create a ``pypsrp.wsman.WSMan`` connection with the Kerberos ticket from the credential. 2. Open a ``pypsrp.powershell.PowerShell`` runspace. 3. Execute commands via ``ps.add_script(command).invoke()``. 4. Return deserialized PSObject results as Python dicts. 5. Close the runspace and WSMan connection on disconnect. Stubbed in this sprint — transport returns placeholder results. """ from __future__ import annotations from typing import Any, Optional from gsap_broker.connectors.session import SessionConnector, SessionTransport from gsap_broker.credentials.resolver import Credential, CredentialResolver class PowerShellTransport(SessionTransport): """PSRP transport over WinRM/HTTPS. Stubbed — actual pypsrp integration in a future sprint. """ transport_id = "psrp" def __init__(self) -> None: self._target = "" self._connected = False async def connect(self, target: str, credential: Credential) -> None: # TODO: create pypsrp.wsman.WSMan with Kerberos auth from # credential.ticket. Target format: hostname:5986. self._target = target self._connected = True async def execute(self, command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]: # TODO: execute PowerShell command via PSRP. # ps = PowerShell(wsman); ps.add_script(command).invoke() # Return structured PSObject results as dicts. if not self._connected: raise RuntimeError("Not connected") return { "stub": True, "transport": self.transport_id, "target": self._target, "command": command, "params": params or {}, } async def disconnect(self) -> None: self._connected = False async def is_alive(self) -> bool: return self._connected class PowerShellConnector(SessionConnector): """Windows management via PowerShell Remoting.""" connector_id = "powershell" corpus_entry_cid = "sha256:powershell-connector-v1" credential_type = "kerberos" transport_class = PowerShellTransport capability_mask = 0x7 # READ | PROPOSE | MUTATE declared_endpoints = ["wsman://*:5986"] accord_template = "windows-management" gsap_required = True chronicle_enabled = True def __init__(self, credential_resolver: CredentialResolver): super().__init__(credential_resolver)