Close
0%
0%

Source Code Analysis: PowerShellEditorServices

Source Code Breakdown of PowerShellEditorServices (at the time of v3.5.4)

Similar projects worth following
398 views
0 followers
In pursuit of a syntax highlighter for my systray-based command palette for powershell, I stumbled across this PowerShell Language Server.

In order to help others understand what this is, and how to use it, I am dedicating this project as a blog for my progress in learning it.

Git commit for v3.5.4:
https://github.com/PowerShell/PowerShellEditorServices/tree/40fb5157a6f76f1403da1bbc35800d5a5658013f
  • Argument Trace: BundledModulesPath

    Nicholas Jackson11/05/2022 at 08:14 0 comments

    Tracing BundledModulesPath parameter

    [#]

    - (on the Start-EditorServices cmdlet)


      This is an attempt at comprehensively tracing the BundledModulesPath parameter. In this log, all uses of the BundledModulesPath parameter will be documented. This will support my unofficial documentation on the Start-EditorServices cmdlet


    Unofficial Documentation:

    [#]

    [syntax section] - (syntax/usage)

    [-BundledModulesPath
      < string, not null or empty >]
    

    PSES/module/PSES/Start-EditorServices.ps1:

    [#]

    [line 44] - (cmdlet-script powershell param block)

    [ValidateNotNullOrEmpty()]
    [string]
      $BundledModulesPath
    

    [#]

    [line 116] - (cmdlet-script C# cmdlet call)

    Import-Module -Name "$PSScriptRoot/PowerShellEditorServices.psd1"
    Start-EditorServices @PSBoundParameters
    

    PSES/module/PSES/PowerShellEditorServices.psd1:

    [#]

    [line 11] - (module manifest - root module field)

    # Script module or binary module file associated with this manifest.
    RootModule = if ($PSEdition -eq 'Core')
        { 'bin/Core/Microsoft.PowerShell.EditorServices.Hosting.dll' }
        else
        { 'bin/Desktop/Microsoft.PowerShell.EditorServices.Hosting.dll' }
    
    - I believe this is a call to the Microsoft.PowerShell.EditorServices.Hosting namespace - still uncertain about this.

    PSES/src/PSES.Hosting/Commands/StartEditorServicesCommand.cs:

    [#]

    [line 24] - (cmdlet definition)

    /// <summary>
    /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services.
    /// </summary>
    ...
    [Cmdlet(
      VerbsLifecycle.Start, "EditorServices",
      DefaultParameterSetName = "NamedPipe"
    )]
    public sealed class StartEditorServicesCommand : PSCmdlet
    { ... }
    

  • Project Logo

    Nicholas Jackson11/01/2022 at 00:07 0 comments

    Chosen Logo:

    PSES doesn't have a logo of their own, so I'm using the preview icon pulled from vscode-powershell:

    I might create an Unsplash or CreativeCommons image later...

    My current idea is to remove the ">_" and replace it with "LSP" to represent that it is the language server

  • Bloat Code Tracker

    Nicholas Jackson10/31/2022 at 23:04 0 comments

    This repository is ridiculously bloated

    This log will be used to track all known instances of bloatcode in the repo

    Unneccessary profilepath processor code

    The code that follows this line can be reduced to this:

    PSMemberInfoCollection<PSPropertyInfo> profiles = profile.Properties
    ProfilePaths = new ProfilePathConfig
    {
      AllUsersAllHosts = (string)profiles.AllUsersAllHosts.Value,
      AllUsersCurrentHost = Path.Combine(
        Path.GetDirectoryName( (string)profiles.AllUsersCurrentHost.Value ),
        $"{HostProfileId}_profile.ps1" ),
      CurrentUserAllHosts = (string)profiles.CurrentUserAllHosts.Value,
      CurrentUserCurrentHost = Path.Combine(
        Path.GetDirectoryName( (string)profiles.CurrentUserCurrentHost.Value ),
        $"{HostProfileId}_profile.ps1" ),
    },

     All referenced Enums and Methods for this generation can then be deleted

    Unused EditorServicesLoader.Create() signature

    According to this search, this signature never gets used.

    Literally any instance of Object.Create()

    We don't need to reinvent the "new" keyword. Named constructors should only be used where verbose names make the constructor look like more than just a constructor. For example Int32.Parse() is a good named constructor; Int32.Create() is not

    ModulePath Modifying functions could probably be merged

    EditorServicesLoader.UpdatePSModulePath() should probably include the code from the CreateConfigObject method on the cmdlet

    Usages of PowerShell translated to C#, instead of just using C#

    instead of using System.Text.Json to serialize to Json, they use the PowerShell command ConvertTo-Json to do it instead, but from C#. This is one of the most hilarious instances of bloat code I have ever seen

    ProfilePaths is literally reinvinted just to be used as a parameter. No additions, just cloned into a new object.

    This code doesn't need to be here.

    Modifications to the BundledModulesPath variable

    this doesn't need to be done in 5 different spots across the utility. Jesus Christ

  • Argument Trace: HostVersion

    Nicholas Jackson10/31/2022 at 18:14 0 comments

    Tracing HostVersion parameter to Start-EditorServices

    This is my trace of the HostVersion parameter. The purpose of this log is to identify all uses for the HostVersion parameter. These uses will be documented in my unofficial documentation on the Start-EditorServices command

    1. Unofficial Docs - Syntax
      1. [-HostVersion <
            string,
            mandatory,
            ="" not="" null="" or="" empty=""
        >] 
    2. PSES/module/PSES/Start-EditorServices.ps1
      1. [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $HostVersion
    3.  PSES/module/PSES/Start-EditorServices.ps1
      1. Import-Module -Name "$PSScriptRoot/PowerShellEditorServices.psd1"
        Start-EditorServices @PSBoundParameters
    4. PSES/module/PSES/PowerShellEditorServices.psd1
      1. # Script module or binary module file associated with this manifest.
        RootModule = if ($PSEdition -eq 'Core')
            {
                'bin/Core/Microsoft.PowerShell.EditorServices.Hosting.dll'
            }
            else
            {
                'bin/Desktop/Microsoft.PowerShell.EditorServices.Hosting.dll'
            }
      2. I believe this is a call to the Microsoft.PowerShell.EditorServices.Hosting namespace - still uncertain about this.
    5. PSES/src/PSES.Hosting/Commands/StartEditorServicesCommand.cs
      1. /// <summary>
        /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance",
          "CA1819:Properties should not return arrays",
          Justification = "Cmdlet parameters can be arrays")]
        [Cmdlet(VerbsLifecycle.Start, "EditorServices", 
          DefaultParameterSetName = "NamedPipe")]
        public sealed class StartEditorServicesCommand : PSCmdlet
        { ... }
      2. The parameter from the NamedPipe parameter set:
        1. /// <summary>
          /// The version to report for the host.
          /// </summary>
          [Parameter(Mandatory = true)]
          [ValidateNotNullOrEmpty]
          public Version HostVersion { get; set; }
      3. The only use for HostVersion in the entire CMDLethopefully this will be an easy argument trace :D
        1. HostInfo hostInfo = new(HostName, HostProfileId, HostVersion);
        2. HostInfo is just a data structure that stores strings and semvers - no need to trace into this. will need to trace the hostinfo object tho :(

    Tracing HostInfo/EditorServicesConfig

    From my previous experiences with these objects, this is gonna be a long trace :(

    1. PSES/src/PSES.Hosting/Commands/StartEditorServicesCommand.cs (CreateConfigObject Private Method)
      1. The only use of HostVersion from above is in the private method CreateConfigObject from the StartEditorServicesCommand (The C# object representing the Start-EditorServices Cmdlet)
      2. This is a longer function, so I won't list the source code here, but I'll summarize it:
        1. Logs diagnostic message "Creating host configuration"
        2. Expands BundledModulesPath (cmdlet parameter) if it is a relative path
        3. Gets the $profile object from PowerShell
        4. Creates the HostInfo object
        5. Pulls in some kind of session state stuff from the runspace object - I have no idea what this is or if it is even useful :/
        6. Instantiates an EditorServicesConfig object with
          1. the HostInfo object,
          2. the PSHost object from the PWSH runtime the CMDlet will run in
          3. the SessionDetailsPath cmdlet parameter
          4. the Expanded BundledModulesPath path-string
          5. the LogPath cmdlet parameter
        7. Sets these properties on the new EditorServicesConfig object:
          1. FeatureFlags with the FeatureFlags cmdlet parameter
          2. LogLevel with the LogLevel cmdlet parameter
          3. ConsoleRepl with GetReplKind private method
            1. REPL is short for Read-Eval-Print-Loop
            2. This is the function that determines which readline Powershell is using by pulling from an Enum<out int>
            3. The 3 options are:
              1. None - no interactive console
              2. LeagacyReadLine - the readline implementation before MicroSoft ported in the PSReadLine repository
              3. PSReadLine - the latest readline implementation
          4. AdditionalModules with the AdditionalModules cmdlet parameter
          5. Service Transport Configs with:
            1. these private methods:
              1. GetLanguageServiceTransport
              2. GetDebugServiceTransport
            2. these return a config representing the type of pipe used for each service (named-pipe/split-named-pipes/stdio-pipe)
          6. InitialSessionState...
    Read more »

  • Moving Generated Docs to Github Gists

    Nicholas Jackson10/31/2022 at 17:39 0 comments

    I find it hard to navigate through the project logs, so I'm moving the Docs to Github Gists. I may move them into their own repository later.

  • Brief Pause in Logs

    Nicholas Jackson10/29/2022 at 22:13 0 comments

    Haven't given this up. Just had to take some time off from logs, so that I could learn about Dependency Injection in C#.

    This takes some time, because I need to specifically cover topics like IServiceCollection, and cover broad topics like inversion of control.

    There isn't a whole lot of articles that demystify these topics very well. If I get asked, I might produce my own, but for now I don't feel like summing up the 30-some-odd pages I have had to read through to get a feel for the topic.

    A Good Article to Start Working With:

    https://www.stevejgordon.co.uk/aspnet-core-dependency-injection-what-is-the-iservicecollection

  • Library Trace: LanguageServer

    Nicholas Jackson10/25/2022 at 01:23 0 comments

    According these 2 lines:

    I believe that PSES is using the OmniSharp C# Language Server to create an LSP server

    These source code files seem to be too large and broad to be scraped to get every detail over how PSES uses it, but it does look rewriteable in PowerShell :D...

  • Argument Trace: HostName

    Nicholas Jackson10/22/2022 at 00:49 9 comments

    Start-EditorServices.cs:

    HostName:

    hostInfo:

    CreateConfigObject():

    EditorServicesLoader psesLoader:

    EditorServicesLoader.Create():

    psesLoader.LoadAndRunEditorServicesAsync(): - NOTE: psesLoader should call on an EditorServicesRunner object

    • declaration
    • calls in order:
      • LogHostInformation
      • CheckNetFxVersion
        • declaration
        • Checks .NETFramework version against .NET 4.6.2. Doesn't throw error, but dumps a warning to console
      • UpdatePSModulePath
        • declaration
        • adds BundledModulePath to $env:PSModulePath
      • ValidateConfiguration
        • declaration
        • ensures that Debug and Language Service Transport are not both set to STDIO
        • Ensures there is "PSHost" object (this is the MonadShell/PowerShell (MSH) Runspace)
      • LoadEditorService
        • declaration
        • Wrapper for EditorServicesLoading.LoadEditorServicesForHost()
          • Which is a "no-op" that just ensures that Microsoft.PowerShell.EditorServices.Hosting assembly is loaded
            • This triggers an AssemblyResolve event
      • _editorServicesRunner = new EditorServicesRunner(_logger, _hostConfig, _sessionFileWriter, _loggersToUnsubscribe);
        • declaration
        • EditorServicesRunner.RunUntilShutdown()
          • Wraps CreateEditorServicesAndRunUntilShutdown()
            • declaration
            • Wraps a Try...Final() block
            • calls CreateHostStartupInfo();
            • if debug, sets debug server to RunTempDebugSessionAsync(hostStartupInfo).ConfigureAwait(false);
            • unsubscribes from host logger (I assume from bloating it? I don't remember this bloating the host logger??)
            • calls WriteStartupBanner(); 
            • sets languages server to PsesLanguageServer languageServer = await CreateLanguageServerAsync(hostStartupInfo).ConfigureAwait(false);
              • declaration (PsesLanguageServer)
              • hostdetails gets processed in StartAsync() as an argument to AddPsesLanguageServices()
                • declaration for AddPsesLanguageServices()
                • this is done in a callback passed to options.WithServices
                  • options is a parameter for a callback passed to LanguageServer.From()
                    • this call has the this signature
                    • the definition assoc'd with that sig is a wrapper for From(Action<LanguageServerOptions> optionsAction,outerServiceProvider,CancellationToken) where optionsAction is the callback
                      • the def
                      • this function is also a wrapper for From(LanguageServerOptions options, ...)
                        • The LanguageServerOptions type is an implementation of LanguageProtocolRpcOptionsBase<LanguageServerOptions>
                          • which is an implementation of JsonRpcServerOptionsBase<T>
                            • which has a method called WithServices(Action<IServiceCollection> servicesAction)
                              • declaration
                              • servicesAction is a callback with the signature: servicesAction(
            • calls languageServer.StartAsync();
            • starts debug server
            • calls languageServer.WaitForShutdown()
            • then final() resubs the host logger
          • Writes Starting State to Session File
      • return Task.Run(_editorServicesRunner.RunUntilShutdown);
      • and a whole bunch of calls to _logger


    In order to further understand how PSES processes hostname, we need to dig into languageServer

  • Vague Parameter: HostProfileId

    Nicholas Jackson10/18/2022 at 04:44 0 comments

    So HostProfileId is a filename prefix used to reference a custom powershell profile that exists in the same directory as the current powershell profile

    This is the PowerShell equivalent of Line 393:

    function GetProfilePathFromProfileObject( $profile, $user, $host ){
      $profilePath = $profile."$user$host"
      Join-Path -Path (Split-Path $profilePath) -ChildPath "$($HostProfileId)_profile.ps1"
    }

    We need to test this functionality

  • Start-EditorServices Unofficial Documentation

    Nicholas Jackson10/18/2022 at 04:41 1 comment

    Syntax:

    (Summarized from the bootstrapper source code)

    Start-EditorServices
          #Common to Every Parameter Set:
          [-HostName <String, Mandatory, Not Null or Empty>]
          [-HostProfileId <String, Mandatory, Not Null or Empty>]
          [-HostVersion <String, Mandatory, Not Null or Empty>]
          [-BundledModulesPath <String, Not Null or Empty>]
          [-LogPath <Not Null or Empty>]
          [-LogLevel <String: "Diagnostic", "Verbose", "Normal", "Warning", "Error"]
          [-SessionDetailsPath <String, Mandatory, Not Null or Empty>]
          [-EnableConsoleRepl <Switch>]
          [-UseLegacyReadLine <Switch>]
          [-DebugServiceOnly <Switch>]
          [-LanguageServiceOnly <Switch>]
          [-AdditionalModules <String[]>]
          [-FeatureFlags <String[]>]
          [-WaitForDebugger <Switch>]
          [-ConfirmInstall <Switch>]
    
          #NamedPipe Parameter Set (Default):
          [-LanguageServicePipeName = $null <String>]
          [-DebugServicePipeName = $null <String>]
    
          #Stdio Parameter Set:
          [-Stdio <Switch>]
    
          #NamedPipeSimplex Parameter Set:
          [-SplitInOutPipes <Switch>]
          [-LanguageServiceInPipeName <String>]
          [-LanguageServiceOutPipeName <String>]
          [-DebugServiceInPipeName = $null <String>]
          [-DebugServiceOutPipeName = $null <String>]

    Details:

    Synopsis (Also from the bootstrapper source code):

    Starts the language and debug services from the PowerShellEditorServices module.

    Description (Again, bootstrapper):

    PowerShell Editor Services Bootstrapper Script:

    This script contains startup logic for the PowerShell Editor Services module when launched by an editor. It handles the following tasks:

    • Verifying the existence of dependencies like PowerShellGet
    • Verifying that the expected version of the PowerShellEditorServices module is installed
    • Installing the PowerShellEditorServices module if confirmed by the user
    • Creating named pipes for the language and debug services to use (if using named pipes)
    • Starting the language and debug services from the PowerShellEditorServices module

    Developer/Contributor Details:

    Assembly (Loaded by PowerShellEditorServices.psd1, the PSES module)

    • When run by PowerShell Core, this is loaded:
      • 'bin/Core/Microsoft.PowerShell.EditorServices.Hosting.dll'
    • On any other PowerShell:
      • 'bin/Desktop/Microsoft.PowerShell.EditorServices.Hosting.dll'

    Assembly Source Code:

    Omnisharp Source Code:

    Parameters:

    HostName (Breakdown):

    • Hostname is used primarily to generate Runspaces using a C# PShost object. This object abstractly represents the PowerShell host. The PShost.name field (Hostnameis a reader-friendly string, which can be used identify the PSHost object. PSHost objects can be used to list runspaces under the PowerShell host and identify the host's appdomain
      • Default Value: "PowerShell Editor Services Host" (set by HostStartupInfo then pulled into PSHost.name during PSInternalHost construction)
    • It's secondary purpose is for logging

    HostProfileId (Breakdown):

    • HostProfileId is a filename prefix used to identify the profile.ps1 file used by PowerShell Editor Services.
      • Default Value: Microsoft.PowerShellEditorServices
      • Can be found in:
        • AllUsersAllHosts: "$( $profile.AllUsersAllHosts )/$( $HostProfileId )_profile.ps1"
        • AllUsersCurrentHost: "$( $profile.AllUsersCurrentHost )/$( $HostProfileId )_profile.ps1"
        • CurrentUserAllHosts: "$( $profile.CurrentUserAllHosts )/$( $HostProfileId )_profile.ps1"
        • CurrentUserCurrentHost: "$( $profile.CurrentUserCurrentHost )/$( $HostProfileId )_profile.ps1"

View all 19 project logs

  • 1
    Start-EditorServices Unofficial Documentation

View all instructions

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates