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)?