1

I want to have a Powershell script in Windows 10, that will inspect if a TCP port is in use by some program/process, and if so, ask for elevated administrative privileges, and then kill that process.

After a ton of searching, I finally found a case simple enough to be used as a minimal example. First of all, install "Simple TCP/IP Services" from "Turn Windows Features on or off" (see also https://techgenix.com/windows-7-simple-tcpip-services-what-how/). It seems this service does not start immediately, so to start it, do from Administrator command prompt:

C:\>sc start simptcp

SERVICE_NAME: simptcp
        TYPE               : 20  WIN32_SHARE_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 22508
        FLAGS              :

With the "Simple TCP/IP Services", one can get a quote of the day via telnet localhost 17 - so, it listens on port 17.

Typically, this is what I would do, if I'd want to scan for port 17, and kill the corresponding process, in Administrator PowerShell; first, if the service is inactive, we get an error:

PS C:\WINDOWS\system32> $portProcessID = ( Get-NetTCPConnection -LocalPort 17 ).OwningProcess
Get-NetTCPConnection : No MSFT_NetTCPConnection objects found with property 'LocalPort' equal to '17'.  Verify the value of
the property and retry.
At line:1 char:20
+ $portProcessID = ( Get-NetTCPConnection -LocalPort 17 ).OwningProcess
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (17:UInt16) [Get-NetTCPConnection], CimJobException
    + FullyQualifiedErrorId : CmdletizationQuery_NotFound_LocalPort,Get-NetTCPConnection

... but if the service is started, the procedure would look like this:

PS C:\WINDOWS\system32> $portProcessID = ( Get-NetTCPConnection -LocalPort 17 ).OwningProcess
PS C:\WINDOWS\system32> $portProcessID
22508
22508
PS C:\WINDOWS\system32> # note, there are two process id above! can extract the first with $portProcessID[0]
PS C:\WINDOWS\system32> ( Get-WmiObject win32_process | Where-Object {$_.ProcessID -eq $portProcessID[0] } ).ProcessName
TCPSVCS.EXE
PS C:\WINDOWS\system32> Stop-Process -id $portProcessID[0]

Confirm
Are you sure you want to perform the Stop-Process operation on the following item: TCPSVCS(22508)?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y

To make sure it is shut down - again from Administrator Command Prompt:

C:\>sc queryex simptcp

SERVICE_NAME: simptcp
        TYPE               : 20  WIN32_SHARE_PROCESS
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 1067  (0x42b)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS              :

So far, so good - now I'd like to have this scripted ...

So, after some research I came up with the following Powershell script, check_and_free_port.ps1:

Add-Type -AssemblyName PresentationFramework

[int] $portNum = 65535

function Check-Port-Kill-Process {
  [bool] $portNumUsed = $true

  # cannot try/catch errors below;
  # as per https://stackoverflow.com/q/62346135, use -ErrorAction SilentlyContinue
  if ( Get-NetTCPConnection -LocalPort $portNum -ErrorAction SilentlyContinue ) {
    #"portNum $portNum in use"
    $portNumUsed = $true
    # note, we may end up with two process id above! can extract the first with $portProcessID[0]
    # (and that should work also even in the case when $portProcessID has a single entry)
    $portProcessID = ( Get-NetTCPConnection -LocalPort $portNum ).OwningProcess
  } else {
    $portNumUsed = $false
  }

  if ($portNumUsed) {
    $portProcessName = ( Get-WmiObject win32_process | Where-Object {$_.ProcessID -eq $portProcessID[0] } ).ProcessName
    "Port $portNum is in use by another process ($portProcessName, pid: $portProcessID)!"
  } else {
    "Port $portNum is unused"
  }

  if ($portNumUsed) {
    # ask for elevated administrative privilege, to be able to run `Stop-Process -id $portProcessID`
    $Msg = @"
NOTE: You will be asked next for administrative permission,
to kill the process ($portProcessName, pid: $portProcessID).
"@
    [System.Windows.MessageBox]::Show($Msg)

    # the below just shows powershell window and exits without waiting for the prompt, in spite of -Wait
    #Start-Process powershell -Wait -ArgumentList 'Stop-Process -id $portProcessID' -verb RunAs
    # https://stackoverflow.com/q/1741490/
    $proc = Start-Process powershell -ArgumentList 'Stop-Process -id $portProcessID[0]' -verb RunAs -PassThru
    $proc.WaitForExit()
  }
} # end function

$portNum = 17 # set actual port to test
Check-Port-Kill-Process

If the service is NOT started, and I run this script from a normal (non-elevated/non-Administrator) Command Prompt, I get this:

C:\>powershell .\check_and_free_port.ps1
Port 17 is unused

Great, that works as intended; however, let's start the service again (if you want to use the command line, you have to go back to Administrator Command Prompt, and then do sc start simptcp again) - and try the script again thereafter (in normal Command Prompt):

C:\>powershell .\check_and_free_port.ps1
Port 17 is in use by another process (TCPSVCS.EXE, pid: 25128 25128)!

... and I get a message box with:

---------------------------

---------------------------
NOTE: You will be asked next for administrative permission,
to kill the process (TCPSVCS.EXE, pid: 25128 25128).
---------------------------
OK   
---------------------------

... as intended; but then, I click OK here - and:

  • I get the "Do you want to allow this app to make changes to your device" privilege elevation prompt - great, exactly as intended
  • I click Yes here; thereafter:
  • I can see a PowerShell window get started for about a second, but then it disappears

... and since I did not get the "Are you sure you want to perform the Stop-Process..." prompt, and could not answer Y(es) to it, and the task has not been killed either.

So, basically, I've tried running Stop-Process -id $portProcessID in the elevated prompt above, but it did not work.

What changes need to be done to the script above, so that when the elevated Powershell process is started, it actually runs the Stop-Process -id $portProcessID command - and asks the resulting "Are you sure you want to perform the Stop-Process..." prompt, and waits for me to enter Y(es) there, before finally killing the required process (and then exiting)?

2
  • 1
    Run script As Admin by right click PS shortcut and select Run As Admin. Commented Sep 9, 2023 at 14:06
  • 1
    Thanks @jdweng but that is not the answer - as mentioned, I already do get the elevated permission prompt no problem, it's just that nothing executed in it. However, I think I found my errors, see my answer below. Commented Sep 9, 2023 at 14:14

2 Answers 2

2

To offer some improvements and general tips regarding your own solution:

Your code:

$portProcessIDsingle = $portProcessID[0]
$proc = Start-Process powershell -ArgumentList "-Command `"& {Stop-Process -id $portProcessIDsingle}`"" -verb RunAs -PassThru
$proc.WaitForExit()

can be simplified to:

Start-Process -Wait -Verb RunAs powershell -ArgumentList "Stop-Process -Id $($portProcessID[0])"
  • When using powershell.exe, the Windows PowerShell CLI, the -Command (-c) parameter is implied when you pass a command to execute.

    • However, with pwsh.exe, the PowerShell (Core) CLI, you must specify it, because -File is now the default.

    • For better or worse, the command can be passed either enclosed in (embedded) "..." as a whole, or as individual arguments that are joined to form the PowerShell code to execute after command-line parsing. While using overall "..." quoting is preferable when calling from shells (such as cmd.exe, to avoid potentially up-front interpretation of arguments, this isn't necessary in no-shell invocations such as with Start-Process.

  • Start-Process's -Wait switch is effective in combination with -Verb RunAs too.

  • There's no reason to use "& { ... }" in order to invoke code passed to PowerShell's CLI via the (possibly implied) -Command (-c) parameter - just use "..." directly. Older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected.

  • In order to embed an expression - such as indexed access to a variable's value ($portProcessID[0]) inside "...", an expandable (interpolating) string, you must enclose it in $(...), the subexpression operator.

    • For a comprehensive but concise summary of PowerShell's expandable strings (string-interpolation rules), see this answer.
Sign up to request clarification or add additional context in comments.

Comments

0

Got it: first problem was not having -Command in the argument list passed to PowerShell (see https://github.com/juanpablojofre/About/blob/master/v3/Topic/PowerShell.exe-Command-Line-Help.md)

Second problem became visible only when I added -NoExit to the argument list; then I could see that the newly started elevated PowerShell dumps this error:

Stop-Process : A positional parameter cannot be found that accepts argument '25128[0]'

... which means something got messed up with the quoting there.

After some tinkering, I finally arrived to this snippet, that works as intended:

  if ($portNumUsed) {
    # ask for elevated administrative privilege, to be able to run `Stop-Process -id $portProcessID`
    $Msg = @"
NOTE: You will be asked next for administrative permission,
to kill the process ($portProcessName, pid: $portProcessID).
"@
    [System.Windows.MessageBox]::Show($Msg)

    # the below just shows powershell window and exits without waiting for the prompt, in spite of -Wait
    #Start-Process powershell -Wait -ArgumentList 'Stop-Process -id $portProcessID' -verb RunAs
    # https://stackoverflow.com/q/1741490/
    # add -NoExit before -Command in -ArgumentList to debug; 
    #  might get something like below for -Command `"& {Stop-Process -id $portProcessID[0]}`":
    #  "Stop-Process : A positional parameter cannot be found that accepts argument '25128[0]'."
    # the below invocation seems to work fine:
    $portProcessIDsingle = $portProcessID[0]
    $proc = Start-Process powershell -ArgumentList "-Command `"& {Stop-Process -id $portProcessIDsingle}`"" -verb RunAs -PassThru
    $proc.WaitForExit()
  }

Note finally, that when the elevated Powershell terminal starts, it will not print the command itself, only it's output - and once you answer Y (or N), the elevated Powershell terminal will close itself (exactly as I wanted it to in this case).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.