强制杀死PowerShell时如何避免内存泄漏?

问题描述 投票:0回答:2

我正在制作一个使用 PowerShell 创建快捷方式的 Python 脚本。我利用了这样一个事实:如果网络路径不可用,则创建快捷方式将需要很长时间。如果进程花费太长时间,我会强制终止该进程,这会导致一堆可用计算机的快捷方式。这是循环内的内容

temp = "powershell; $WshShell = New-Object -ComObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut(\"\"\"%cd%\\" + list[i][:list[i].find("\\")] + ".lnk\"\"\"); $Shortcut.TargetPath = \"\"\"\\" + list[i][list[i].find("\\"):-1] + "\"\"\"; $Shortcut.Save()"
subprocess.Popen(temp, shell=True)

这会贯穿一个类似于“COMPUTER1/在此处插入 ip ”的列表

然后我有另一个脚本,它使用任务列表来查找 PowerShell 进程并在大约 2 秒后杀死它们。这可行,但会导致内存泄漏。我认为这是杀死 PowerShell 的结果,并且资源没有从 RAM 中释放。

有没有办法释放内存或完全避免泄漏?

python powershell memory-leaks kill-process
2个回答
0
投票

如果您在循环中运行它,那么是的..您一定会遇到内存问题,因为您不断创建 COM 对象,但从未从内存中释放它们。

Shortcut.Save()
之后,您需要确保 Windows 垃圾收集器将使用以下方法从内存中删除它们:

$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($shortcut)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WshShell)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

在网络路径不可用时强制终止脚本也是一个坏主意。 为什么不在尝试创建快捷方式之前测试网络路径? 这就是 Powershell cmdlet Test-NetConnectionTest-Path 的用途。

顺便说一句将所有代码写在一长行中不仅丑陋,而且也无助于发现您可能在其中放入的任何错误。最好在多行中编写代码并进行适当的缩进。


0
投票
使用

tasklist

 查找 
powershell
 进程并在大约 2 秒后杀死它们

如果您的意思是

taskkill.exe

:默认情况下(除非通过了
/F
),它会尝试“合作地”终止进程,即它给目标进程一个机会在终止之前进行清理,甚至允许它“拒绝” 
终止。 但是,这种协作机制仅适用于 GUI 子系统应用程序,而 PowerShell 是一个 console
子系统应用程序。 因此,PowerShell 没有有机会清理
,虽然看起来其内存的托管部分(由.NET运行时管理的部分)似乎确实得到了清理,但任何非托管资源,包括 COM 对象,不会释放 请注意,PowerShell 的 Stop-Process

cmdlet always

强制终止进程,因此本身就存在内存泄漏的风险 - 请参阅 GitHub 问题 #13664 进行讨论。 因此你的方法无法避免内存泄漏


因此,您需要一种处理超时

进程内

的方法,您可以确保正确的清理,这也相当于一个更有效的解决方案,因为您可以使用单个进程: 以下示例代码说明了一种可行的方法。 注:

它依赖于

    PowerShell (Core) 7
  • -only -Parallel 参数 ForEach-Object

    并行线程
    中运行操作。

    Windows PowerShell
  • (旧版、随 Windows 提供、仅限 Windows 的 PowerShell 版本,其最新且
  • 最后一个

    版本为 5.1),您可以: 安装

      Start-ThreadJob
    • 自带的模块,并用它来启动各个线程作业;例如与 Install-Module -Scope CurrentUser ThreadJob

      
      
      
      在紧要关头,回退到使用 - 基于子进程,速度较慢且资源更加密集 - 常规背景 

      jobs
    • ,使用
    • Start-Job

      
      
      

    • # Create sample input in the form of 'Computer1', 'Computer2', ... $computersToTest = 1..5 | ForEach-Object { 'Computer' + $_ } # Launch a parallel thread for each computer. $jb = $computersToTest | ForEach-Object -AsJob -ThrottleLimit $computersToTest.Count -Parallel { # Simulate a long-running network operation here; e.g.: # Test-NetConnection $_ -Port 445 -InformationLevel Quiet Start-Sleep (Get-Random 6) $_ } # Check for computers that have responded within a specific timeout period, # and act on only them. $sw = [System.Diagnostics.Stopwatch]::StartNew() while ($sw.Elapsed.TotalSeconds -le 2) { # Check for any output from the job's child jobs and act on it. $jb | Receive-Job | ForEach-Object { "Taking action for $_..." } # Sleep a little before checking for job output again. Start-Sleep -Milliseconds 200 } # Clean up. This will stop any child jobs that haven't finished yet. $jb | Remove-Job -Force
  • 注:

上面

使用了轮询循环 - 即使它们可能已经提前到达 - 您可以将
Wait-Job -Timeout

Receive-Job 结合使用,如下所示: # ... job-creation code omitted; copy from above. # Wait for the job(s) to complete, with a timeout. $timedOut = $null -eq ($jb | Wait-Job -Timeout 3) # Stop any child jobs that haven't completed yet. $jb | Stop-Job # See how many have completed. $numCompleted = @($jb.ChildJobs.State -eq 'Completed').Count Write-Verbose -Verbose "$numCompleted of $($jb.ChildJobs.Count) thread(s) completed within the timeout period." # Process whatever results are available. $jb | Receive-Job | ForEach-Object { "Taking action for $_..." } # Clean up. $jb | Remove-Job

    

© www.soinside.com 2019 - 2024. All rights reserved.