Requires .NET SDK 8.0+. Use dotnet directly for building and testing — it's faster and
requires no extra tooling. The Invoke-Build script requires the InvokeBuild and platyPS
PowerShell modules (platyPS is #Requires'd at the top, so the whole script fails without it),
and is mainly needed to assemble the full PowerShell module for release.
# Build (run both; Hosting depends on the core library)
dotnet publish src/PowerShellEditorServices/PowerShellEditorServices.csproj -f netstandard2.0
dotnet publish src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj -f net8.0
# Run all unit tests
dotnet test test/PowerShellEditorServices.Test/ --framework net8.0
# Run a single test by name
dotnet test test/PowerShellEditorServices.Test/ --framework net8.0 --filter "FullyQualifiedName~CompletesCommandInFile"
# Run tests by trait category
dotnet test test/PowerShellEditorServices.Test/ --framework net8.0 --filter "Category=Completions"
# Run E2E tests
dotnet test test/PowerShellEditorServices.Test.E2E/ --framework net8.0For assembling the full module or running the complete CI suite (including Windows PowerShell
5.1 targets), use Invoke-Build with the InvokeBuild and platyPS modules installed:
Invoke-Build Build # full build + module assembly + help generation
Invoke-Build TestPS74 # unit tests via build script
Invoke-Build TestE2EPwsh # E2E tests via build script
Invoke-Build Build -Configuration Release # required before PRs (enforces XML doc comments)src/PowerShellEditorServices.Hosting/BuildInfo.cs is auto-generated by the build script and
git-ignored for changes. Do not edit it manually.
PowerShell Editor Services (PSES) is a Language Server Protocol (LSP) and Debug Adapter Protocol (DAP) server for PowerShell, consumed by VS Code and other editors.
src/PowerShellEditorServices(netstandard2.0) — Core library containing all LSP/DAP handlers, services, and the PowerShell execution engine. Namespace:Microsoft.PowerShell.EditorServices.src/PowerShellEditorServices.Hosting(net8.0,net462) — Entry point layer that loads PSES into a PowerShell process viaStartEditorServicesCommand. Uses a customAssemblyLoadContext(PsesLoadContext) on .NET Core to isolate dependencies.module/PowerShellEditorServices/— The shipped PowerShell module. The build assembles compiled binaries intobin/Core/(net8.0) andbin/Desktop/(net462). The module manifest loads the appropriate DLL based on PowerShell edition.
PsesInternalHost— The central PowerShell execution host. Also implementsIRunspaceContextandIInternalPowerShellExecutionService.WorkspaceService— Manages open documents and workspace files.SymbolsService— Provides symbol navigation (go-to-definition, find references).AnalysisService— Integrates PSScriptAnalyzer for real-time diagnostics.ConfigurationService— Manages editor/client settings.ExtensionService— Supports the$psEditorAPI for editor extensions.
Handlers live under Services/<Feature>/Handlers/ and follow a consistent pattern:
- Class name:
Pses<Feature>Handler, markedinternal - Inherits from an OmniSharp base class (e.g.,
CompletionHandlerBase,HoverHandlerBase) - Dependencies injected via constructor (
ILoggerFactory, services) - Overrides
CreateRegistrationOptions()andHandle() - Uses
LspUtils.PowerShellDocumentSelectorfor document registration
PsesLanguageServer— Configures and runs the LSP server using OmniSharpPsesDebugServer— Configures and runs the DAP server- Both use
Microsoft.Extensions.DependencyInjectionfor service registration
- All files require the copyright header:
// Copyright (c) Microsoft Corporation./// Licensed under the MIT License. .editorconfigenforces many rules as errors, including unused variables, async/threading rules (VSTHRD*), and modern C# idioms (pattern matching, null checks, expression bodies).- Roslynator analyzers are enabled for formatting and code quality.
- Use
Microsoft.Extensions.Logging(ILogger<T>viaILoggerFactory) for all logging.
The public types under Microsoft.PowerShell.EditorServices are not consumed only
through PowerShell script. Downstream modules compile against the shipped PSES
assemblies and bind to that metadata at build time. The most prominent example is
SeeminglyScience/EditorServicesCommandSuite,
which references the extension API surface (FileContext, EditorContext, the
IFileRange/FilePosition types, ILspCurrentFileContext, IEditorScriptFile,
etc.) directly from C#.
Because of that, treat any change to an existing public member as potentially
binary breaking, and review for it explicitly before merging:
- Changing the type of a parameter (even widening a concrete class to an
interface it implements, e.g.
FileRange->IFileRange), the return type, the parameter count/order, or renaming/removing a public member is source-compatible at most but binary-breaking. The method's signature/metadata token changes, so a precompiled caller throwsMissingMethodExceptionat runtime even though it would recompile cleanly. - When you need a wider or different signature, add an overload instead of
editing the existing one. Keep the original signature in place and have it delegate
to the new implementation so existing binaries keep resolving. Cast as needed to
pick the new overload from the old body (e.g.
Foo((IFileRange)range)). - Add a regression test that binds to the old, concrete signature (declare the argument as the original parameter type, not the widened one) so overload resolution to the compatibility shim is actually exercised.
- If a breaking change is genuinely unavoidable, call it out in the PR description as a binary breaking change so maintainers can weigh it and coordinate a downstream release.
- Framework: xUnit with
Xunit.SkippableFactfor conditionally skipped tests. - Host setup: Use
PsesHostFactory.Create(loggerFactory)to get an isolatedPsesInternalHostfor testing. Tests implementIAsyncLifetimefor async setup/teardown. - Traits: Tests use
[Trait("Category", "...")]for filtering (e.g.,"Completions","Symbols"). - Fixtures: Test PowerShell scripts live in
test/PowerShellEditorServices.Test/Fixtures/. - E2E tests are in a separate project (
PowerShellEditorServices.Test.E2E) and test the full LSP client-server interaction.
The core library targets netstandard2.0 for compatibility with both .NET Core and .NET
Framework. The hosting project and tests dual-target net8.0 and net462 (Windows PowerShell
5.1). Non-Windows platforms skip net462 targets.
Every pull request must be labeled before it is opened so it is triaged correctly and lands in the right changelog section.
Each PR requires:
- At least one
Area-*label describing the part of the codebase it touches (e.g.Area-Debugging,Area-Language Server,Area-Workspaces,Area-Documentation). This is used for triage and filtering. - Exactly one
Issue-*label describing the kind of change:Issue-Bug— a bug fix.Issue-Enhancement— a new feature or changed behavior.Issue-Performance— a performance improvement.
The Issue-* label is not optional: GitHub's auto-generated release notes use it
to pick the changelog category. See .github/release.yml for the
authoritative mapping (Issue-Enhancement → "Enhancements & Features ✨",
Issue-Bug → "Squashed Bugs 🐛"). Any PR without an Issue-* label falls
through the "*" catch-all into "Other Changes 🙏" and is silently
miscategorized, so always set one correctly.
Additionally:
- Add the relevant
OS-*label (OS-Windows,OS-macOS,OS-Linux) when a change is platform-specific. - Use the
Ignorelabel only for changes that should be excluded from the release notes entirely (e.g. pure CI, test, or docs chores the maintainers do not want in the changelog)..github/release.ymlexcludes this label from the changelog.
The full, authoritative set of labels drifts over time — do not hard-code it
here. List the current labels with gh label list --repo PowerShell/PowerShellEditorServices
or the repo's Labels page
and pick the closest matches.