1+ # PowerShell Editor Services Bootstrapper Script
2+ # ----------------------------------------------
3+ # This script contains startup logic for the PowerShell Editor Services
4+ # module when launched by an editor. It handles the following tasks:
5+ #
6+ # - Verifying the existence of dependencies like PowerShellGet
7+ # - Verifying that the expected version of the PowerShellEditorServices module is installed
8+ # - Installing the PowerShellEditorServices module if confirmed by the user
9+ # - Finding unused TCP port numbers for the language and debug services to use
10+ # - Starting the language and debug services from the PowerShellEditorServices module
11+ #
12+ # NOTE: If editor integration authors make modifications to this
13+ # script, please consider contributing changes back to the
14+ # canonical version of this script at the PowerShell Editor
15+ # Services GitHub repository:
16+ #
17+ # https://github.com/PowerShell/PowerShellEditorServices/blob/master/module/Start-EditorServices.ps1
18+
119param (
220 [Parameter (Mandatory = $true )]
321 [ValidateNotNullOrEmpty ()]
@@ -19,16 +37,6 @@ param(
1937 [string ]
2038 $HostVersion ,
2139
22- [Parameter (Mandatory = $true )]
23- [ValidateNotNullOrEmpty ()]
24- [string ]
25- $LanguageServicePipeName ,
26-
27- [Parameter (Mandatory = $true )]
28- [ValidateNotNullOrEmpty ()]
29- [string ]
30- $DebugServicePipeName ,
31-
3240 [ValidateNotNullOrEmpty ()]
3341 [string ]
3442 $BundledModulesPath ,
@@ -40,28 +48,141 @@ param(
4048 $LogLevel ,
4149
4250 [switch ]
43- $WaitForCompletion ,
51+ $WaitForDebugger ,
4452
4553 [switch ]
46- $WaitForDebugger
54+ $ConfirmInstall
4755)
4856
57+ # This variable will be assigned later to contain information about
58+ # what happened while attempting to launch the PowerShell Editor
59+ # Services host
60+ $resultDetails = $null ;
61+
62+ function Test-ModuleAvailable ($ModuleName , $ModuleVersion ) {
63+ $modules = Get-Module - ListAvailable $moduleName
64+ if ($modules -ne $null ) {
65+ if ($ModuleVersion -ne $null ) {
66+ foreach ($module in $modules ) {
67+ if ($module.Version.Equals ($moduleVersion )) {
68+ return $true ;
69+ }
70+ }
71+ }
72+ else {
73+ return $true ;
74+ }
75+ }
76+
77+ return $false ;
78+ }
79+
80+ function Test-PortAvailability ($PortNumber ) {
81+ $portAvailable = $true ;
82+
83+ try {
84+ $ipAddress = [System.Net.Dns ]::GetHostEntryAsync(" localhost" ).Result.AddressList[0 ];
85+ $tcpListener = [System.Net.Sockets.TcpListener ]::new($ipAddress , $portNumber );
86+ $tcpListener.Start ();
87+ $tcpListener.Stop ();
88+
89+ }
90+ catch [System.Net.Sockets.SocketException ] {
91+ # Check the SocketErrorCode to see if it's the expected exception
92+ if ($error [0 ].Exception.InnerException.SocketErrorCode -eq [System.Net.Sockets.SocketError ]::AddressAlreadyInUse) {
93+ $portAvailable = $false ;
94+ }
95+ else {
96+ Write-Output (" Error code: " + $error [0 ].SocketErrorCode)
97+ }
98+ }
99+
100+ return $portAvailable ;
101+ }
102+
103+ $rand = [System.Random ]::new()
104+ function Get-AvailablePort {
105+ $triesRemaining = 10 ;
106+
107+ while ($triesRemaining -gt 0 ) {
108+ $port = $rand.Next (10000 , 30000 )
109+ if ((Test-PortAvailability - PortAvailability $port ) -eq $true ) {
110+ return $port
111+ }
112+
113+ $triesRemaining -- ;
114+ }
115+
116+ return $null
117+ }
118+
49119# Add BundledModulesPath to $env:PSModulePath
50120if ($BundledModulesPath ) {
51- $env: PSModulePath = $BundledModulesPath + " ; " + $env: PSModulePath
121+ $env: PSMODULEPATH = $BundledModulesPath + [ System.IO.Path ]::PathSeparator + $env: PSMODULEPATH
52122}
53123
124+ # Check if PowerShellGet module is available
125+ if ((Test-ModuleAvailable " PowerShellGet" ) -eq $false ) {
126+ # TODO: WRITE ERROR
127+ }
128+
129+ # Check if the expected version of the PowerShell Editor Services
130+ # module is installed
54131$parsedVersion = [System.Version ]::new($EditorServicesVersion )
132+ if ((Test-ModuleAvailable " PowerShellEditorServices" - RequiredVersion $parsedVersion ) -eq $false ) {
133+ if ($ConfirmInstall ) {
134+ # TODO: Check for error and return failure if necessary
135+ Install-Module " PowerShellEditorServices" - RequiredVersion $parsedVersion - Confirm
136+ }
137+ else {
138+ # Indicate to the client that the PowerShellEditorServices module
139+ # needs to be installed
140+ Write-Output " needs_install"
141+ }
142+ }
143+
55144Import-Module PowerShellEditorServices - RequiredVersion $parsedVersion - ErrorAction Stop
56145
57- Start-EditorServicesHost `
58- - HostName $HostName `
59- - HostProfileId $HostProfileId `
60- - HostVersion $HostVersion `
61- - LogPath $LogPath `
62- - LogLevel $LogLevel `
63- - LanguageServicePipeName $LanguageServicePipeName `
64- - DebugServicePipeName $DebugServicePipeName `
65- - BundledModulesPath $BundledModulesPath `
66- - WaitForCompletion:$WaitForCompletion.IsPresent `
67- - WaitForDebugger:$WaitForDebugger.IsPresent
146+ # Locate available port numbers for services
147+ $languageServicePort = Get-AvailablePort
148+ $debugServicePort = Get-AvailablePort
149+
150+ $editorServicesHost =
151+ Start-EditorServicesHost `
152+ - HostName $HostName `
153+ - HostProfileId $HostProfileId `
154+ - HostVersion $HostVersion `
155+ - LogPath $LogPath `
156+ - LogLevel $LogLevel `
157+ - LanguageServicePort $languageServicePort `
158+ - DebugServicePort $debugServicePort `
159+ - BundledModulesPath $BundledModulesPath `
160+ - WaitForDebugger:$WaitForDebugger.IsPresent
161+
162+ # TODO: Verify that the service is started
163+
164+ $resultDetails = @ {
165+ " status" = " started" ;
166+ " channel" = " tcp" ;
167+ " languageServicePort" = $languageServicePort ;
168+ " debugServicePort" = $debugServicePort ;
169+ };
170+
171+ # Notify the client that the services have started
172+ Write-Output (ConvertTo-Json - InputObject $resultDetails - Compress)
173+
174+ try {
175+ # Wait for the host to complete execution before exiting
176+ $editorServicesHost.WaitForCompletion ()
177+ }
178+ catch [System.Exception ] {
179+ $e = $_.Exception ; # .InnerException;
180+ $errorString = " "
181+
182+ while ($e -ne $null ) {
183+ $errorString = $errorString + ($e.Message + " `r`n " + $e.StackTrace + " `r`n " )
184+ $e = $e.InnerException ;
185+ }
186+
187+ Write-Error (" `r`n Caught error while waiting for EditorServicesHost to complete:`r`n " + $errorString )
188+ }
0 commit comments