Skip to content

Commit f767813

Browse files
committed
Add Start-EditorServices script from vscode-powershell repo
1 parent aa893b2 commit f767813

File tree

1 file changed

+353
-0
lines changed

1 file changed

+353
-0
lines changed

scripts/Start-EditorServices.ps1

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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+
19+
param(
20+
[Parameter(Mandatory=$true)]
21+
[ValidateNotNullOrEmpty()]
22+
[string]
23+
$EditorServicesVersion,
24+
25+
[Parameter(Mandatory=$true)]
26+
[ValidateNotNullOrEmpty()]
27+
[string]
28+
$HostName,
29+
30+
[Parameter(Mandatory=$true)]
31+
[ValidateNotNullOrEmpty()]
32+
[string]
33+
$HostProfileId,
34+
35+
[Parameter(Mandatory=$true)]
36+
[ValidateNotNullOrEmpty()]
37+
[string]
38+
$HostVersion,
39+
40+
[ValidateNotNullOrEmpty()]
41+
[string]
42+
$BundledModulesPath,
43+
44+
[ValidateNotNullOrEmpty()]
45+
$LogPath,
46+
47+
[ValidateSet("Diagnostic", "Normal", "Verbose", "Error", "Diagnostic")]
48+
$LogLevel,
49+
50+
[Parameter(Mandatory=$true)]
51+
[ValidateNotNullOrEmpty()]
52+
[string]
53+
$SessionDetailsPath,
54+
55+
[switch]
56+
$EnableConsoleRepl,
57+
58+
[switch]
59+
$DebugServiceOnly,
60+
61+
[string[]]
62+
$AdditionalModules,
63+
64+
[string[]]
65+
$FeatureFlags,
66+
67+
[switch]
68+
$WaitForDebugger,
69+
70+
[switch]
71+
$ConfirmInstall
72+
)
73+
74+
$minPortNumber = 10000
75+
$maxPortNumber = 30000
76+
77+
if ($LogLevel -eq "Diagnostic") {
78+
$VerbosePreference = 'Continue'
79+
Start-Transcript (Join-Path (Split-Path $LogPath -Parent) Start-EditorServices.log) -Force
80+
}
81+
82+
function LogSection([string]$msg) {
83+
Write-Verbose "`n#-- $msg $('-' * ([Math]::Max(0, 73 - $msg.Length)))"
84+
}
85+
86+
function Log([string[]]$msg) {
87+
$msg | Write-Verbose
88+
}
89+
90+
function ExitWithError($errorString) {
91+
Write-Host -ForegroundColor Red "`n`n$errorString"
92+
93+
# Sleep for a while to make sure the user has time to see and copy the
94+
# error message
95+
Start-Sleep -Seconds 300
96+
97+
exit 1;
98+
}
99+
100+
# Are we running in PowerShell 2 or earlier?
101+
if ($PSVersionTable.PSVersion.Major -le 2) {
102+
# No ConvertTo-Json on PSv2 and below, so write out the JSON manually
103+
"{`"status`": `"failed`", `"reason`": `"unsupported`", `"powerShellVersion`": `"$($PSVersionTable.PSVersion.ToString())`"}" |
104+
Set-Content -Force -Path "$SessionDetailsPath" -ErrorAction Stop
105+
106+
ExitWithError "Unsupported PowerShell version $($PSVersionTable.PSVersion), language features are disabled."
107+
}
108+
109+
function WriteSessionFile($sessionInfo) {
110+
$sessionInfoJson = ConvertTo-Json -InputObject $sessionInfo -Compress
111+
Log "Writing session file with contents:"
112+
Log $sessionInfoJson
113+
$sessionInfoJson | Set-Content -Force -Path "$SessionDetailsPath" -ErrorAction Stop
114+
}
115+
116+
if ($host.Runspace.LanguageMode -eq 'ConstrainedLanguage') {
117+
WriteSessionFile @{
118+
"status" = "failed"
119+
"reason" = "languageMode"
120+
"detail" = $host.Runspace.LanguageMode.ToString()
121+
}
122+
123+
ExitWithError "PowerShell is configured with an unsupported LanguageMode (ConstrainedLanguage), language features are disabled."
124+
}
125+
126+
# Are we running in PowerShell 5 or later?
127+
$isPS5orLater = $PSVersionTable.PSVersion.Major -ge 5
128+
129+
# If PSReadline is present in the session, remove it so that runspace
130+
# management is easier
131+
if ((Get-Module PSReadline).Count -gt 0) {
132+
LogSection "Removing PSReadLine module"
133+
Remove-Module PSReadline -ErrorAction SilentlyContinue
134+
}
135+
136+
# This variable will be assigned later to contain information about
137+
# what happened while attempting to launch the PowerShell Editor
138+
# Services host
139+
$resultDetails = $null;
140+
141+
function Test-ModuleAvailable($ModuleName, $ModuleVersion) {
142+
Log "Testing module availability $ModuleName $ModuleVersion"
143+
144+
$modules = Get-Module -ListAvailable $moduleName
145+
if ($modules -ne $null) {
146+
if ($ModuleVersion -ne $null) {
147+
foreach ($module in $modules) {
148+
if ($module.Version.Equals($moduleVersion)) {
149+
Log "$ModuleName $ModuleVersion found"
150+
return $true;
151+
}
152+
}
153+
}
154+
else {
155+
Log "$ModuleName $ModuleVersion found"
156+
return $true;
157+
}
158+
}
159+
160+
Log "$ModuleName $ModuleVersion NOT found"
161+
return $false;
162+
}
163+
164+
function Test-PortAvailability {
165+
param(
166+
[Parameter(Mandatory=$true)]
167+
[int]
168+
$PortNumber
169+
)
170+
171+
$portAvailable = $true
172+
173+
try {
174+
if ($isPS5orLater) {
175+
$ipAddresses = [System.Net.Dns]::GetHostEntryAsync("localhost").Result.AddressList
176+
}
177+
else {
178+
$ipAddresses = [System.Net.Dns]::GetHostEntry("localhost").AddressList
179+
}
180+
181+
foreach ($ipAddress in $ipAddresses)
182+
{
183+
Log "Testing availability of port ${PortNumber} at address ${ipAddress} / $($ipAddress.AddressFamily)"
184+
185+
$tcpListener = New-Object System.Net.Sockets.TcpListener @($ipAddress, $PortNumber)
186+
$tcpListener.Start()
187+
$tcpListener.Stop()
188+
}
189+
}
190+
catch [System.Net.Sockets.SocketException] {
191+
$portAvailable = $false
192+
193+
# Check the SocketErrorCode to see if it's the expected exception
194+
if ($_.Exception.SocketErrorCode -eq [System.Net.Sockets.SocketError]::AddressAlreadyInUse) {
195+
Log "Port $PortNumber is in use."
196+
}
197+
else {
198+
Log "SocketException on port ${PortNumber}: $($_.Exception)"
199+
}
200+
}
201+
202+
$portAvailable
203+
}
204+
205+
$portsInUse = @{}
206+
$rand = New-Object System.Random
207+
function Get-AvailablePort() {
208+
$triesRemaining = 10;
209+
210+
while ($triesRemaining -gt 0) {
211+
do {
212+
$port = $rand.Next($minPortNumber, $maxPortNumber)
213+
}
214+
while ($portsInUse.ContainsKey($port))
215+
216+
# Whether we succeed or fail, don't try this port again
217+
$portsInUse[$port] = 1
218+
219+
Log "Checking port: $port, attempts remaining $triesRemaining --------------------"
220+
if ((Test-PortAvailability -PortNumber $port) -eq $true) {
221+
Log "Port: $port is available"
222+
return $port
223+
}
224+
225+
Log "Port: $port is NOT available"
226+
$triesRemaining--;
227+
}
228+
229+
Log "Did not find any available ports!!"
230+
return $null
231+
}
232+
233+
# Add BundledModulesPath to $env:PSModulePath
234+
if ($BundledModulesPath) {
235+
$env:PSModulePath = $env:PSModulePath.TrimEnd([System.IO.Path]::PathSeparator) + [System.IO.Path]::PathSeparator + $BundledModulesPath
236+
LogSection "Updated PSModulePath to:"
237+
Log ($env:PSModulePath -split [System.IO.Path]::PathSeparator)
238+
}
239+
240+
LogSection "Check required modules available"
241+
# Check if PowerShellGet module is available
242+
if ((Test-ModuleAvailable "PowerShellGet") -eq $false) {
243+
Log "Failed to find PowerShellGet module"
244+
# TODO: WRITE ERROR
245+
}
246+
247+
# Check if the expected version of the PowerShell Editor Services
248+
# module is installed
249+
$parsedVersion = New-Object System.Version @($EditorServicesVersion)
250+
if ((Test-ModuleAvailable "PowerShellEditorServices" $parsedVersion) -eq $false) {
251+
if ($ConfirmInstall -and $isPS5orLater) {
252+
# TODO: Check for error and return failure if necessary
253+
LogSection "Install PowerShellEditorServices"
254+
Install-Module "PowerShellEditorServices" -RequiredVersion $parsedVersion -Confirm
255+
}
256+
else {
257+
# Indicate to the client that the PowerShellEditorServices module
258+
# needs to be installed
259+
Write-Output "needs_install"
260+
}
261+
}
262+
263+
try {
264+
LogSection "Start up PowerShellEditorServices"
265+
Log "Importing PowerShellEditorServices"
266+
267+
if ($isPS5orLater) {
268+
Import-Module PowerShellEditorServices -RequiredVersion $parsedVersion -ErrorAction Stop
269+
}
270+
else {
271+
Import-Module PowerShellEditorServices -Version $parsedVersion -ErrorAction Stop
272+
}
273+
274+
# Locate available port numbers for services
275+
Log "Searching for available socket port for the language service"
276+
$languageServicePort = Get-AvailablePort
277+
278+
Log "Searching for available socket port for the debug service"
279+
$debugServicePort = Get-AvailablePort
280+
281+
if (!$languageServicePort -or !$debugServicePort) {
282+
ExitWithError "Failed to find an open socket port for either the language or debug service."
283+
}
284+
285+
if ($EnableConsoleRepl) {
286+
Write-Host "PowerShell Integrated Console`n"
287+
}
288+
289+
# Create the Editor Services host
290+
Log "Invoking Start-EditorServicesHost"
291+
$editorServicesHost =
292+
Start-EditorServicesHost `
293+
-HostName $HostName `
294+
-HostProfileId $HostProfileId `
295+
-HostVersion $HostVersion `
296+
-LogPath $LogPath `
297+
-LogLevel $LogLevel `
298+
-AdditionalModules $AdditionalModules `
299+
-LanguageServicePort $languageServicePort `
300+
-DebugServicePort $debugServicePort `
301+
-BundledModulesPath $BundledModulesPath `
302+
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
303+
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
304+
-WaitForDebugger:$WaitForDebugger.IsPresent
305+
306+
# TODO: Verify that the service is started
307+
Log "Start-EditorServicesHost returned $editorServicesHost"
308+
309+
$resultDetails = @{
310+
"status" = "started";
311+
"channel" = "tcp";
312+
"languageServicePort" = $languageServicePort;
313+
"debugServicePort" = $debugServicePort;
314+
}
315+
316+
# Notify the client that the services have started
317+
WriteSessionFile $resultDetails
318+
319+
Log "Wrote out session file"
320+
}
321+
catch [System.Exception] {
322+
$e = $_.Exception;
323+
$errorString = ""
324+
325+
Log "ERRORS caught starting up EditorServicesHost"
326+
327+
while ($e -ne $null) {
328+
$errorString = $errorString + ($e.Message + "`r`n" + $e.StackTrace + "`r`n")
329+
$e = $e.InnerException;
330+
Log $errorString
331+
}
332+
333+
ExitWithError ("An error occurred while starting PowerShell Editor Services:`r`n`r`n" + $errorString)
334+
}
335+
336+
try {
337+
# Wait for the host to complete execution before exiting
338+
LogSection "Waiting for EditorServicesHost to complete execution"
339+
$editorServicesHost.WaitForCompletion()
340+
Log "EditorServicesHost has completed execution"
341+
}
342+
catch [System.Exception] {
343+
$e = $_.Exception;
344+
$errorString = ""
345+
346+
Log "ERRORS caught while waiting for EditorServicesHost to complete execution"
347+
348+
while ($e -ne $null) {
349+
$errorString = $errorString + ($e.Message + "`r`n" + $e.StackTrace + "`r`n")
350+
$e = $e.InnerException;
351+
Log $errorString
352+
}
353+
}

0 commit comments

Comments
 (0)