66# - Verifying the existence of dependencies like PowerShellGet
77# - Verifying that the expected version of the PowerShellEditorServices module is installed
88# - Installing the PowerShellEditorServices module if confirmed by the user
9- # - Finding unused TCP port numbers for the language and debug services to use
9+ # - Creating named pipes for the language and debug services to use (if using named pipes)
1010# - Starting the language and debug services from the PowerShellEditorServices module
1111#
1212# NOTE: If editor integration authors make modifications to this
3939 [ValidateNotNullOrEmpty ()]
4040 $LogPath ,
4141
42- [ValidateSet (" Diagnostic" , " Normal" , " Verbose" , " Error" , " Diagnostic " )]
42+ [ValidateSet (" Diagnostic" , " Normal" , " Verbose" , " Error" )]
4343 $LogLevel ,
4444
4545 [Parameter (Mandatory = $true )]
7575 $DebugServicePipeName = $null
7676)
7777
78- $minPortNumber = 10000
79- $maxPortNumber = 30000
78+ $DEFAULT_USER_MODE = " 600"
8079
8180if ($LogLevel -eq " Diagnostic" ) {
8281 $VerbosePreference = ' Continue'
@@ -168,67 +167,71 @@ function Test-ModuleAvailable($ModuleName, $ModuleVersion) {
168167 return $false ;
169168}
170169
171- function Test-PortAvailability {
170+ function New-NamedPipeName {
171+
172+ # We try 10 times to find a valid pipe name
173+ for ($i = 0 ; $i -lt 10 ; $i ++ ) {
174+ # add a guid to make the pipe unique
175+ $PipeName = " PSES_$ ( (New-Guid ).Guid) "
176+
177+ if ((Test-NamedPipeName - PipeName $PipeName )) {
178+ return $PipeName
179+ }
180+ }
181+ ExitWithError " Could not find valid a pipe name."
182+ }
183+
184+ function Get-NamedPipePath {
172185 param (
173186 [Parameter (Mandatory = $true )]
174- [int ]
175- $PortNumber
187+ [ValidateNotNullOrEmpty ()]
188+ [string ]
189+ $PipeName
176190 )
177191
178- $portAvailable = $true
179-
180- try {
181- # After some research, I don't believe we should run into problems using an IPv4 port
182- # that happens to be in use via an IPv6 address. That is based on this info:
183- # https://www.ibm.com/support/knowledgecenter/ssw_i5_54/rzai2/rzai2compipv4ipv6.htm#rzai2compipv4ipv6__compports
184- $ipAddress = [System.Net.IPAddress ]::Loopback
185- Log " Testing availability of port ${PortNumber} at address ${ipAddress} / $ ( $ipAddress.AddressFamily ) "
186-
187- $tcpListener = Microsoft.PowerShell.Utility\New-Object System.Net.Sockets.TcpListener @ ($ipAddress , $PortNumber )
188- $tcpListener.Start ()
189- $tcpListener.Stop ()
192+ if (-not $IsLinux -and -not $IsMacOS ) {
193+ return " \\.\pipe\$PipeName " ;
190194 }
191- catch [System.Net.Sockets.SocketException ] {
192- $portAvailable = $false
193-
194- # Check the SocketErrorCode to see if it's the expected exception
195- if ($_.Exception.SocketErrorCode -eq [System.Net.Sockets.SocketError ]::AddressAlreadyInUse) {
196- Log " Port $PortNumber is in use."
197- }
198- else {
199- Log " SocketException on port ${PortNumber} : $ ( $_.Exception ) "
200- }
195+ else {
196+ # Windows uses NamedPipes where non-Windows platforms use Unix Domain Sockets.
197+ # the Unix Domain Sockets live in the tmp directory and are prefixed with "CoreFxPipe_"
198+ return (Join-Path - Path ([System.IO.Path ]::GetTempPath()) - ChildPath " CoreFxPipe_$PipeName " )
201199 }
202200
203- $portAvailable
204201}
205202
206- $portsInUse = @ {}
207- $rand = Microsoft.PowerShell.Utility\New-Object System.Random
208- function Get-AvailablePort () {
209- $triesRemaining = 10 ;
210-
211- while ($triesRemaining -gt 0 ) {
212- do {
213- $port = $rand.Next ($minPortNumber , $maxPortNumber )
214- }
215- while ($portsInUse.ContainsKey ($port ))
216-
217- # Whether we succeed or fail, don't try this port again
218- $portsInUse [$port ] = 1
203+ # Returns True if it's a valid pipe name
204+ # A valid pipe name is a file that does not exist either
205+ # in the temp directory (macOS & Linux) or in the pipe directory (Windows)
206+ function Test-NamedPipeName {
207+ param (
208+ [Parameter (Mandatory = $true )]
209+ [ValidateNotNullOrEmpty ()]
210+ [string ]
211+ $PipeName
212+ )
219213
220- Log " Checking port: $port , attempts remaining $triesRemaining --------------------"
221- if ((Test-PortAvailability - PortNumber $port ) -eq $true ) {
222- Log " Port: $port is available"
223- return $port
224- }
214+ $path = Get-NamedPipePath - PipeName $PipeName
215+ return -not (Test-Path $path )
216+ }
225217
226- Log " Port: $port is NOT available"
227- $triesRemaining -- ;
218+ function Set-NamedPipeMode {
219+ param (
220+ [Parameter (Mandatory = $true )]
221+ [ValidateNotNullOrEmpty ()]
222+ [string ]
223+ $PipeFile
224+ )
225+ chmod $DEFAULT_USER_MODE $PipeFile
226+ if ($IsLinux ) {
227+ $mode = stat - c " %A" $PipeFile
228+ }
229+ else {
230+ $mode = stat -f " %A" $PipeFile
231+ }
232+ if ($mode -ne $DEFAULT_USER_MODE ) {
233+ ExitWithError " Permissions to the pipe file were not set properly. Expected: $DEFAULT_USER_MODE Actual: $mode for file: $PipeFile "
228234 }
229-
230- Log " Did not find any available ports!!"
231- return $null
232235}
233236
234237# Add BundledModulesPath to $env:PSModulePath
@@ -251,21 +254,36 @@ try {
251254
252255 Microsoft.PowerShell.Core\Import-Module PowerShellEditorServices - ErrorAction Stop
253256
254- # Locate available port numbers for services
255- # There could be only one service on Stdio channel
256-
257- $languageServiceTransport = $null
258- $debugServiceTransport = $null
257+ # Locate available port numbers for services
258+ # There could be only one service on Stdio channel
259259
260- if ($Stdio.IsPresent -and -not $DebugServiceOnly.IsPresent ) { $languageServiceTransport = " Stdio" }
261- elseif ($LanguageServicePipeName ) { $languageServiceTransport = " NamedPipe" ; $languageServicePipeName = " $LanguageServicePipeName " }
262- elseif ($languageServicePort = Get-AvailablePort ) { $languageServiceTransport = " Tcp" }
263- else { ExitWithError " Failed to find an open socket port for language service." }
260+ $languageServiceTransport = $null
261+ $debugServiceTransport = $null
264262
265- if ($Stdio.IsPresent -and $DebugServiceOnly.IsPresent ) { $debugServiceTransport = " Stdio" }
266- elseif ($DebugServicePipeName ) { $debugServiceTransport = " NamedPipe" ; $debugServicePipeName = " $DebugServicePipeName " }
267- elseif ($debugServicePort = Get-AvailablePort ) { $debugServiceTransport = " Tcp" }
268- else { ExitWithError " Failed to find an open socket port for debug service." }
263+ if ($Stdio.IsPresent ) {
264+ $languageServiceTransport = " Stdio"
265+ $debugServiceTransport = " Stdio"
266+ }
267+ else {
268+ $languageServiceTransport = " NamedPipe"
269+ $debugServiceTransport = " NamedPipe"
270+ if (-not $LanguageServicePipeName ) {
271+ $LanguageServicePipeName = New-NamedPipeName
272+ }
273+ else {
274+ if (-not (Test-NamedPipeName - PipeName $LanguageServicePipeName )) {
275+ ExitWithError " Pipe name supplied is already taken: $LanguageServicePipeName "
276+ }
277+ }
278+ if (-not $DebugServicePipeName ) {
279+ $DebugServicePipeName = New-NamedPipeName
280+ }
281+ else {
282+ if (-not (Test-NamedPipeName - PipeName $DebugServicePipeName )) {
283+ ExitWithError " Pipe name supplied is already taken: $DebugServicePipeName "
284+ }
285+ }
286+ }
269287
270288 if ($EnableConsoleRepl ) {
271289 Write-Host " PowerShell Integrated Console`n "
@@ -281,11 +299,9 @@ try {
281299 - LogPath $LogPath `
282300 - LogLevel $LogLevel `
283301 - AdditionalModules $AdditionalModules `
284- - LanguageServicePort $languageServicePort `
285- - DebugServicePort $debugServicePort `
286302 - LanguageServiceNamedPipe $LanguageServicePipeName `
287303 - DebugServiceNamedPipe $DebugServicePipeName `
288- - Stdio:$ Stdio.IsPresent `
304+ - Stdio:( $TransportType -eq " Stdio" ) `
289305 - BundledModulesPath $BundledModulesPath `
290306 - EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
291307 - DebugServiceOnly:$DebugServiceOnly.IsPresent `
@@ -300,11 +316,18 @@ try {
300316 " debugServiceTransport" = $debugServiceTransport ;
301317 };
302318
303- if ($languageServicePipeName ) { $resultDetails [" languageServicePipeName" ] = " $languageServicePipeName " }
304- if ($debugServicePipeName ) { $resultDetails [" debugServicePipeName" ] = " $debugServicePipeName " }
305-
306- if ($languageServicePort ) { $resultDetails [" languageServicePort" ] = $languageServicePort }
307- if ($debugServicePort ) { $resultDetails [" debugServicePort" ] = $debugServicePort }
319+ if ($LanguageServicePipeName ) {
320+ $resultDetails [" languageServicePipeName" ] = Get-NamedPipePath - PipeName $LanguageServicePipeName
321+ if ($IsLinux -or $IsMacOS ) {
322+ Set-NamedPipeMode - PipeFile $resultDetails [" languageServicePipeName" ]
323+ }
324+ }
325+ if ($DebugServicePipeName ) {
326+ $resultDetails [" debugServicePipeName" ] = Get-NamedPipePath - PipeName $DebugServicePipeName
327+ if ($IsLinux -or $IsMacOS ) {
328+ Set-NamedPipeMode - PipeFile $resultDetails [" debugServicePipeName" ]
329+ }
330+ }
308331
309332 # Notify the client that the services have started
310333 WriteSessionFile $resultDetails
0 commit comments