MAIN MENU
Devolutions Blog

Ankündigungen, Aktualisierungen und Einsichten von Devolutions

Devolutions PowerShell Universal VS Code debugger integration illustration for the blog.

Erweitertes PowerShell-Debugging in VS Code mit Universal Debugger-Integration

PowerShell Universal 4.2 und die VS Code-Erweiterung ergänzen Remote-Debugging über PowerShell Editor Services und das Debug Adapter Protocol, damit Sie sich aus dem Platform-Baum an Prozess und Runspace anhängen können.

PowerShell Universal (und sein Vorgänger Universal Dashboard) war schon immer knifflig zu debuggen. Fast die gesamte Funktionalität läuft über Hintergrund-Runspaces, daher erfordert das Einzelschritt-Debugging etwas Werkzeugakrobatik. Remote hat es außerdem nicht funktioniert: bis jetzt.

PowerShell Universal Debugger für VS Code

Mit Version 4.2 von PowerShell Universal und der Universal VS Code-Erweiterung fügen wir die Möglichkeit hinzu, Skripte, Apps und APIs direkt aus VS Code zu debuggen. Sie können Haltepunkte setzen (typischerweise über Wait-Debugger), den Code schrittweise durchlaufen und Variablen prüfen. Das funktioniert remote über eine WebSocket-Verbindung zum PowerShell Universal-Server.

Eine neue Processes-Ansicht unter der Platform-Struktur bietet eine Verbindung zu allen Runspaces auf dem Server, die PowerShell Universal verwaltet. Sie können einen Prozess auswählen und auf Attach klicken, um den Debugger anzuhängen.

PowerShell Universal VS Code Processes view listing managed runspaces ready to attach.
Prozesse und Runspaces in der PowerShell Universal VS Code-Erweiterung

Nach dem Anhängen haben Sie das volle PowerShell-Debugging in Visual Studio Code. Sie können Ihren Code anzeigen, schrittweise durchlaufen und Variablen prüfen.

Visual Studio Code debugger stepping through a PowerShell Universal script with variables.
Debuggen eines PowerShell Universal-Skripts in Visual Studio Code

So beeindruckend die Erfahrung ist, sie entsteht vor allem dadurch, dass eine Instanz von PowerShell Editor Services im PSU-Dienst gehostet wird und direkt per WebSocket kommuniziert. Die technischen Details folgen unten.

Debug Adapter Protocol

Das Debug Adapter Protocol ist eine Spezifikation zur Implementierung von Debuggern. Es ist in Visual Studio Code und Visual Studio integriert, sodass eine einzige Protokollimplementierung in beiden Editoren funktioniert. In der PowerShell-Welt ist das PowerShell Editor Services.

VS Code sendet JSON-formatierte Anfragen an den PowerShell Editor Services-Prozess, und PowerShell Editor Services antwortet mit JSON-formatierten Antworten. Statt einen Debugger komplett neu zu schreiben, können wir ihn nun nutzen, um Debugging-Dienste bereitzustellen.

PowerShell Editor Services

PowerShell Editor Services ist eine Serverimplementierung des Debug Adapter Protocol. Es wird praktisch als PowerShell-Modul ausgeliefert, das in einen PowerShell-Prozess geladen wird und dann per RPC kommuniziert. Es ist dieselbe Implementierung wie in der PowerShell-Erweiterung für Visual Studio Code. Um mit den Diensten zu arbeiten, starten Sie einen PowerShell-Prozess mit den passenden Editor-Services-Befehlszeilenargumenten.

Das sieht typischerweise so aus.

pwsh -NoLogo -NoProfile -Command "$PSES_BUNDLE_PATH/PowerShellEditorServices/Start-EditorServices.ps1 -BundledModulesPath $PSES_BUNDLE_PATH -LogPath $SESSION_TEMP_PATH/logs.log -SessionDetailsPath $SESSION_TEMP_PATH/session.json -FeatureFlags @() -AdditionalModules @() -HostName 'My Client' -HostProfileId 'myclient' -HostVersion 1.0.0 -LogLevel Normal"

Anschließend können wir die vom Dienst geschriebene session.json lesen. Standardmäßig enthält sie Named-Pipe-Namen für das Language Service Protocol und das Debug Adapter Protocol. Sobald eine Named Pipe verbunden ist, können wir JSON-Nachrichten senden und empfangen, um den Debugger zu steuern und den aktuellen Zustand des Runspace zu erhalten.

PowerShell Universal anbinden

Statt eine Named Pipe nach außen offenzulegen, ist es deutlich einfacher, einen WebSocket bereitzustellen, der Authentifizierung, Autorisierung und Port des Webservers mitnutzt. Wir haben einen neuen SignalR-Hub hinzugefügt, der Zeichenfolgen von einem Client empfängt und sie an den PowerShell Editor Services-Prozess weiterleitet.

Wir schleusen die Nachrichten im Wesentlichen durch. Es war etwas Arbeit nötig, um uns an das DAP-Protokoll und die Named-Pipe-Kommunikation anzupassen, aber der Aufwand war gering. Wir starten PSES in einem neuen Prozess und nur den Debugger-Dienst, nicht den Language Service.

Den VS Code-Debugger anbinden

Jetzt, da WebSocket und PowerShell Editor Services-Prozess laufen, können wir das in Tools wie VS Code integrieren. Praktisch haben wir einen eigenen Debug-Adapter implementiert. Das wäre normalerweise viel Arbeit, aber weil wir tatsächlich über den WebSocket mit PSES kommunizieren, können wir Anfragen und Antworten durchreichen, ohne das gesamte Protokoll zu implementieren.

export class UniversalDebugAdapter implements vscode.DebugAdapter {

    constructor(context: vscode.ExtensionContext) {
        const connectionName = context.globalState.get("universal.connection");
        const settings = load();

        var appToken = settings.appToken;
        var url = settings.url;
        var rejectUnauthorized = true;
        var windowsAuth = false;

        if (connectionName && connectionName !== 'Default') {
            const connection = settings.connections.find(m => m.name === connectionName);
            if (connection) {
                appToken = connection.appToken;
                url = connection.url;
                rejectUnauthorized = !connection.allowInvalidCertificate;
            }
        }

        this.hubConnection = new HubConnectionBuilder()
            .withUrl(`${url}/debuggerhub`, { accessTokenFactory: () => appToken })
            .configureLogging(LogLevel.Information)
            .build();

        this.hubConnection.on("message", (message: string) => {
            const protocolMessage = JSON.parse(message) as DebugProtocol.ProtocolMessage;
            this.handleMessage(protocolMessage);
        });

        this.hubConnection.onclose(() => {
            vscode.window.showInformationMessage("Disconnected from PowerShell Universal Debugger.");
        });
    }

    private hubConnection: HubConnection;
    private sendMessage = new vscode.EventEmitter<DebugProtocol.ProtocolMessage>();

    readonly onDidSendMessage: vscode.Event<DebugProtocol.ProtocolMessage> = this.sendMessage.event;

    handleMessage(message: DebugProtocol.ProtocolMessage): void {
        if (this.hubConnection.state === 'Disconnected') {
            this.hubConnection.start().then(() => {
                this.handleMessage(message);
            });
            return;
        }

        switch (message.type) {
            case 'request':
                this.hubConnection.send("message", JSON.stringify(message));
                break;
            case 'response':
                this.sendMessage.fire(message);
                break;
            case 'event':
                this.sendMessage.fire(message);
                break;
        }
    }

    dispose() {
        this.hubConnection.stop();
    }
}

Nach der Implementierung des Debug-Adapters haben wir eine Strukturansicht ergänzt, mit der sich Prozess und Runspace auswählen und anhängen lassen. Diese Information haben wir bereits in der PowerShell Universal Admin-Konsole angezeigt, daher war die Darstellung in der VS Code-Struktur ebenfalls einfach.

Wenn der Benutzer einen Prozess auswählt und auf Anhängen klickt, senden wir eine Anfrage an den Debug-Adapter, um den Prozess anzuhängen, und PSES übernimmt den Rest.

export const attachRunspace = async (runspace: RunspaceTreeItem, context: vscode.ExtensionContext) => {
    await vscode.debug.startDebugging(undefined, {
        name: "PowerShell Universal",
        type: "powershelluniversal",
        request: "attach",
        processId: runspace.runspace.processId,
        runspaceId: runspace.runspace.id
    });
};

Fazit

Diese neue Funktion wird für Benutzer, die mit beliebigen PowerShell-Skripten in Universal arbeiten, sehr hilfreich sein. Auch wenn die Nutzung etwas fortgeschritten ist, bietet sie eine deutlich bessere Debugging-Erfahrung als zuvor. Die Funktion wird als Plugin in PowerShell Universal v4.2 ausgeliefert. Die bestehende Debugging-Erfahrung im Browser werden wir als veraltet markieren und in einer späteren Version entfernen.

Es wäre möglich, diese Funktionalität über das Debuggen von PowerShell Universal-Skripten hinaus zu erweitern. Das könnte eine ganze Welt des Remote-Debuggings von PowerShell über WebSockets eröffnen.

Bereit zum Bauen? PowerShell Universal herunterladen.