Skip to content

Commit 8e04e18

Browse files
committed
Move Start-EditorServices to module and overwrite the old one
1 parent f767813 commit 8e04e18

File tree

2 files changed

+211
-405
lines changed

2 files changed

+211
-405
lines changed

module/Start-EditorServices.ps1

Lines changed: 211 additions & 52 deletions
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)