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.
我想在Windows 10中有一个PowerShell脚本,它将检查某个程序/进程是否正在使用一个TCP端口,如果是,则请求提升管理权限,然后终止该进程。
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:
经过大量的搜索,我终于找到了一个足够简单的案例,可以作为一个最小的例子。首先,安装“打开或关闭Windows功能”中的“简单的tcp/IP服务”(另见https://techgenix.com/windows-7-simple-tcpip-services-what-how/).此服务似乎不会立即启动,因此要启动它,请从管理员命令提示符执行以下操作:
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.
使用“简单的TCP/IP服务”,用户可以通过telnet本地主机17获取当天的报价-因此,它在端口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:
通常,如果我想在管理员PowerShell中扫描端口17并终止相应的进程,我会这样做;首先,如果服务处于非活动状态,我们会收到一个错误:
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
:
因此,在进行了一些研究之后,我想出了以下PowerShell脚本: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):
很好,它按预期工作;但是,让我们再次启动该服务(如果要使用命令行,您必须返回到管理员命令提示符,然后再次执行sc start simptcp)-然后在之后再次尝试该脚本(在正常的命令提示符中):
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.
因此,基本上,我尝试在上面提升的提示符中运行Stop-Process-id$portProcessID,但不起作用。
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)?
需要对上面的脚本进行哪些更改,以便在启动提升的PowerShell进程时,它实际上运行Stop-Process-id$portProcessID命令-并询问结果“是否确实要执行Stop-Process...”提示,并等待我在那里输入Y(ES),最后终止所需的进程(然后退出)?
更多回答
Run script As Admin by right click PS shortcut and select Run As Admin.
以管理员身份运行脚本,方法是右键单击PS快捷方式,然后选择以管理员身份运行。
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.
谢谢@jdweng,但这不是答案-正如前面提到的,我已经得到了提升的权限提示没有问题,只是里面没有执行任何东西。但是,我想我找到了我的错误,请看下面我的回答。
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.
使用Powershell.exe(Windows PowerShell CLI)时,当您传递要执行的命令时,会隐含-Command(-c)参数。
However, with pwsh.exe
, the PowerShell (Core) CLI, you must specify it, because -File
is now the default.
但是,对于PowerShell(核心)CLI pwsh.exe,您必须指定它,因为-文件现在是缺省设置。
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
.
不管是好是坏,该命令可以用(嵌入的)“...”括起来传递作为一个整体,或作为连接成PowerShell代码的单个参数在命令行分析后执行。同时使用整体的“...”当从外壳(如cmd.exe)调用时,最好使用引号,以避免可能预先解释参数,这在无外壳调用(如使用Start-Process)中不是必需的。
Start-Process
's -Wait
switch is effective in combination with -Verb RunAs
too.
Start-Process的-Wait开关与-Verb RunAs结合使用也很有效。
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.
没有理由使用“&{...}”来调用通过(可能隐含的)-Command(-c)参数传递到PowerShell的CLI的代码-只需使用“...”直接去吧。较早版本的CLI文档错误地建议&{...}是必需的,但后来更正了这一点。
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.
为了嵌入一个表达式--比如对变量值($portProcessID[0])的索引访问,“.“,一个可扩展(插值)字符串,必须将其括在$(...)中,子表达式操作符。
- For a comprehensive but concise summary of PowerShell's expandable strings (string-interpolation rules), see this answer.
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)
明白了:第一个问题是没有将参数列表中的-Command传递给PowerShell(请参阅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:
第二个问题只有在我将-noexit添加到参数列表中时才可见;然后我可以看到新启动的提升PowerShell转储此错误:
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).
最后,请注意,当提升的PowerShell终端启动时,它不会打印命令本身,而只是输出-一旦您回答Y(或N),提升的PowerShell终端将自动关闭(就像我在本例中希望它关闭的那样)。
更多回答
我是一名优秀的程序员,十分优秀!