PowerShell Universal (og forgængeren Universal Dashboard) har altid været vanskelige at fejlfinde. Næsten al funktionaliteten klares via runspaces i baggrunden, så trinvis kørsel med en debugger kræver, at du bøvler med flere værktøjer. Det virker heller ikke på afstand: indtil nu.
PowerShell Universal Debugger til VS Code
Med version 4.2 af PowerShell Universal og Universal-udvidelsen til VS Code tilføjer vi muligheden for at fejlfinde scripts, Apps og API’er direkte fra VS Code. Du kan sætte breakpoints (typisk via Wait-Debugger), gå trinvist gennem koden og inspicere variabler. Det virker på afstand over en WebSocket-forbindelse til PowerShell Universal-serveren.
En ny Processes-visning under trævisningen Platform giver forbindelse til alle de runspaces, der kører på serveren, og som styres af PowerShell Universal. Du kan vælge en proces og klikke på knappen Attach for at tilknytte debuggeren til processen.
Når du er tilknyttet, har du den fulde PowerShell-fejlfindingsoplevelse i Visual Studio Code. Du kan se din kode, gå trinvist gennem den og inspicere variabler.
Selv om oplevelsen er ret imponerende, skyldes den især, at en instans af PowerShell Editor Services kører inde i PSU-tjenesten, og at vi kommunikerer direkte med den via en WebSocket. Vi går ned i de tekniske detaljer nedenfor.
Debug Adapter Protocol
Debug Adapter Protocol er en specifikation til implementering af debuggere. Den er integreret i både Visual Studio Code og Visual Studio, så én enkelt implementering af protokollen kan fungere i begge editorer. I PowerShell-verdenen er det PowerShell Editor Services.
VS Code sender JSON-formaterede anmodninger til processen PowerShell Editor Services, og PowerShell Editor Services svarer med JSON-formaterede svar. I stedet for at skulle skrive en debugger helt om kan vi nu udnytte den til at levere fejlfindingstjenester.
PowerShell Editor Services
PowerShell Editor Services er en serverimplementering af Debug Adapter Protocol. Den leveres i praksis som et PowerShell-modul, der indlæses i en PowerShell-proces og derefter kommunikerer via RPC. Det er den samme implementering, som bruges i PowerShell-udvidelsen til Visual Studio Code. For at arbejde med tjenesterne starter du en PowerShell-proces med de rigtige kommandolinjeargumenter til Editor Services.
Det ser typisk nogenlunde sådan ud.
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"
Derfra kan vi læse filen session.json, som tjenesten har skrevet. Som standard indeholder den navne på named pipes, der kan bruges til Language Service Protocol og til Debug Adapter Protocol. Når en named pipe er forbundet, kan vi sende og modtage JSON-beskeder for at styre debuggeren og få oplysninger om runspacets aktuelle tilstand.
Sådan kobles PowerShell Universal på
I stedet for at eksponere en named pipe udadtil er det meget lettere at eksponere en WebSocket, der kan bruge den samme godkendelse, autorisation og port som webserveren. Vi har tilføjet en ny SignalR-hub, der modtager strenge fra en klient og sender dem videre til processen PowerShell Editor Services.
I praksis sender vi bare beskederne videre. Der skulle lidt tilpasning til for at få DAP-protokollen og kommunikationen via named pipes til at spille sammen, men det var begrænset. Vi starter PSES i en ny proces og starter kun debuggertjenesten, ikke sprogtjenesten.
Tilkobling af VS Code-debuggeren
Nu hvor vi har en WebSocket og en kørende PowerShell Editor Services-proces, kan vi integrere den i værktøjer som VS Code. I praksis har vi implementeret en brugerdefineret debugadapter. Det ville typisk være et stort stykke arbejde, men fordi vi faktisk kommunikerer med PSES over WebSocket, kan vi lede anmodninger og svar igennem uden at skulle implementere hele protokollen.
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();
}
}
Da vi havde implementeret debugadapteren, tilføjede vi en trævisning, hvor du kan vælge en proces og et runspace og derefter tilknytte den. Oplysningerne fandtes allerede i administrationskonsollen i PowerShell Universal, så det var enkelt også at vise dem i trævisningen i VS Code.
Når du vælger en proces og klikker på Attach, sender vi en anmodning til debugadapteren om at tilknytte processen, og PSES overtager resten.
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
});
};
Konklusion
Den nye funktion bliver yderst nyttig for dig, der arbejder med enhver type PowerShell-script i Universal. Det er ganske vist en lidt avanceret brug, men den giver en langt bedre fejlfindingsoplevelse end det, der tidligere var tilgængeligt. Funktionen leveres som et plugin i PowerShell Universal v4.2. Vi udfaser den eksisterende fejlfindingsoplevelse i browseren og fjerner den i en kommende version.
Det ville være muligt at udvide funktionaliteten ud over fejlfinding af scripts i PowerShell Universal. Den kunne åbne en hel verden af fjernfejlfinding af PowerShell over WebSockets.
Klar til at bygge? Download PowerShell Universal.

Adam Driscoll