Skip to content

Commit 781f573

Browse files
rjmholtTylerLeonhardt
authored andcommitted
Add Start-EditorServices script from vscode-powershell repo (#639)
* Add Start-EditorServices script from vscode-powershell repo * Move Start-EditorServices to module and overwrite the old one * Add EnableConsoleRepl flag * Revert EnableConsoleRepl * read session data from file * add file length check as well * add CoreCLR System.Reflection * Fix issue with checking if session file available Change to check length of sessionPath file and only if file exists test passes. Update to use CodeBase which is the original location of the file under bin dir. Location is in the temp dir when run under xUnit.
1 parent 6f81539 commit 781f573

File tree

2 files changed

+238
-87
lines changed

2 files changed

+238
-87
lines changed

module/Start-EditorServices.ps1

+211-52
Original file line numberDiff line numberDiff line change
@@ -44,99 +44,213 @@ param(
4444
[ValidateNotNullOrEmpty()]
4545
$LogPath,
4646

47-
[ValidateSet("Normal", "Verbose", "Error","Diagnostic")]
47+
[ValidateSet("Diagnostic", "Normal", "Verbose", "Error", "Diagnostic")]
4848
$LogLevel,
4949

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+
5067
[switch]
5168
$WaitForDebugger,
5269

5370
[switch]
5471
$ConfirmInstall
5572
)
5673

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+
57136
# This variable will be assigned later to contain information about
58137
# what happened while attempting to launch the PowerShell Editor
59138
# Services host
60139
$resultDetails = $null;
61140

62141
function Test-ModuleAvailable($ModuleName, $ModuleVersion) {
142+
Log "Testing module availability $ModuleName $ModuleVersion"
143+
63144
$modules = Get-Module -ListAvailable $moduleName
64145
if ($modules -ne $null) {
65146
if ($ModuleVersion -ne $null) {
66147
foreach ($module in $modules) {
67148
if ($module.Version.Equals($moduleVersion)) {
149+
Log "$ModuleName $ModuleVersion found"
68150
return $true;
69151
}
70152
}
71153
}
72154
else {
155+
Log "$ModuleName $ModuleVersion found"
73156
return $true;
74157
}
75158
}
76159

160+
Log "$ModuleName $ModuleVersion NOT found"
77161
return $false;
78162
}
79163

80-
function Test-PortAvailability($PortNumber) {
81-
$portAvailable = $true;
164+
function Test-PortAvailability {
165+
param(
166+
[Parameter(Mandatory=$true)]
167+
[int]
168+
$PortNumber
169+
)
170+
171+
$portAvailable = $true
82172

83173
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();
174+
if ($isPS5orLater) {
175+
$ipAddresses = [System.Net.Dns]::GetHostEntryAsync("localhost").Result.AddressList
176+
}
177+
else {
178+
$ipAddresses = [System.Net.Dns]::GetHostEntry("localhost").AddressList
179+
}
88180

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+
}
89189
}
90190
catch [System.Net.Sockets.SocketException] {
191+
$portAvailable = $false
192+
91193
# 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;
194+
if ($_.Exception.SocketErrorCode -eq [System.Net.Sockets.SocketError]::AddressAlreadyInUse) {
195+
Log "Port $PortNumber is in use."
94196
}
95197
else {
96-
Write-Output ("Error code: " + $error[0].SocketErrorCode)
198+
Log "SocketException on port ${PortNumber}: $($_.Exception)"
97199
}
98200
}
99201

100-
return $portAvailable;
202+
$portAvailable
101203
}
102204

103-
$rand = [System.Random]::new()
104-
function Get-AvailablePort {
205+
$portsInUse = @{}
206+
$rand = New-Object System.Random
207+
function Get-AvailablePort() {
105208
$triesRemaining = 10;
106209

107210
while ($triesRemaining -gt 0) {
108-
$port = $rand.Next(10000, 30000)
109-
if ((Test-PortAvailability -PortAvailability $port) -eq $true) {
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"
110222
return $port
111223
}
112224

225+
Log "Port: $port is NOT available"
113226
$triesRemaining--;
114227
}
115228

229+
Log "Did not find any available ports!!"
116230
return $null
117231
}
118232

119-
# OUTPUT PROTOCOL
120-
# - "started 29981 39898" - Server(s) are started, language and debug server ports (respectively)
121-
# - "failed Error message describing the failure" - General failure while starting, show error message to user (?)
122-
# - "needs_install" - User should be prompted to install PowerShell Editor Services via the PowerShell Gallery
123-
124233
# Add BundledModulesPath to $env:PSModulePath
125234
if ($BundledModulesPath) {
126-
$env:PSMODULEPATH = $BundledModulesPath + [System.IO.Path]::PathSeparator + $env:PSMODULEPATH
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)
127238
}
128239

240+
LogSection "Check required modules available"
129241
# Check if PowerShellGet module is available
130242
if ((Test-ModuleAvailable "PowerShellGet") -eq $false) {
243+
Log "Failed to find PowerShellGet module"
131244
# TODO: WRITE ERROR
132245
}
133246

134247
# Check if the expected version of the PowerShell Editor Services
135248
# module is installed
136-
$parsedVersion = [System.Version]::new($EditorServicesVersion)
137-
if ((Test-ModuleAvailable "PowerShellEditorServices" -RequiredVersion $parsedVersion) -eq $false) {
138-
if ($ConfirmInstall) {
249+
$parsedVersion = New-Object System.Version @($EditorServicesVersion)
250+
if ((Test-ModuleAvailable "PowerShellEditorServices" $parsedVersion) -eq $false) {
251+
if ($ConfirmInstall -and $isPS5orLater) {
139252
# TODO: Check for error and return failure if necessary
253+
LogSection "Install PowerShellEditorServices"
140254
Install-Module "PowerShellEditorServices" -RequiredVersion $parsedVersion -Confirm
141255
}
142256
else {
@@ -146,49 +260,94 @@ if ((Test-ModuleAvailable "PowerShellEditorServices" -RequiredVersion $parsedVer
146260
}
147261
}
148262

149-
Import-Module PowerShellEditorServices -RequiredVersion $parsedVersion -ErrorAction Stop
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
150280

151-
# Locate available port numbers for services
152-
$languageServicePort = Get-AvailablePort
153-
$debugServicePort = Get-AvailablePort
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
154318

155-
$editorServicesHost =
156-
Start-EditorServicesHost `
157-
-HostName $HostName `
158-
-HostProfileId $HostProfileId `
159-
-HostVersion $HostVersion `
160-
-LogPath $LogPath `
161-
-LogLevel $LogLevel `
162-
-AdditionalModules @() `
163-
-LanguageServicePort $languageServicePort `
164-
-DebugServicePort $debugServicePort `
165-
-BundledModulesPath $BundledModulesPath `
166-
-WaitForDebugger:$WaitForDebugger.IsPresent
319+
Log "Wrote out session file"
320+
}
321+
catch [System.Exception] {
322+
$e = $_.Exception;
323+
$errorString = ""
167324

168-
# TODO: Verify that the service is started
325+
Log "ERRORS caught starting up EditorServicesHost"
169326

170-
$resultDetails = @{
171-
"status" = "started";
172-
"channel" = "tcp";
173-
"languageServicePort" = $languageServicePort;
174-
"debugServicePort" = $debugServicePort;
175-
};
327+
while ($e -ne $null) {
328+
$errorString = $errorString + ($e.Message + "`r`n" + $e.StackTrace + "`r`n")
329+
$e = $e.InnerException;
330+
Log $errorString
331+
}
176332

177-
# Notify the client that the services have started
178-
Write-Output (ConvertTo-Json -InputObject $resultDetails -Compress)
333+
ExitWithError ("An error occurred while starting PowerShell Editor Services:`r`n`r`n" + $errorString)
334+
}
179335

180336
try {
181337
# Wait for the host to complete execution before exiting
338+
LogSection "Waiting for EditorServicesHost to complete execution"
182339
$editorServicesHost.WaitForCompletion()
340+
Log "EditorServicesHost has completed execution"
183341
}
184342
catch [System.Exception] {
185-
$e = $_.Exception; #.InnerException;
343+
$e = $_.Exception;
186344
$errorString = ""
187345

346+
Log "ERRORS caught while waiting for EditorServicesHost to complete execution"
347+
188348
while ($e -ne $null) {
189349
$errorString = $errorString + ($e.Message + "`r`n" + $e.StackTrace + "`r`n")
190350
$e = $e.InnerException;
351+
Log $errorString
191352
}
192-
193-
Write-Error ("`r`nCaught error while waiting for EditorServicesHost to complete:`r`n" + $errorString)
194353
}

0 commit comments

Comments
 (0)