PowerShell & Net.WebClient。需要澄清工作行为

PowerShell & Net.WebClient. Need clarification on jobs behaviour

假设我正在使用 Net.WebClient 的 DownloadFile 方法下载大文件:

$uri1 = "blabla.com/distro/blabla_2gb.exe"
$localfile1 = "$Env:userprofile\Downloads\blabla_2gb.exe"

$wbcl = New-Object System.Net.WebClient
$wbcl.DownloadFile($uri1, $localfile1)
$wbcl.Dispose()

在这种情况下,我可以随时使用 Alt + F4 之类的命令终止我的脚本。下载过程将停止,$wbcl 将自动销毁。

但是如果我在工作中做同样的事情:

Start-Job -ScriptBlock `
{
  #SAME CODE AS ABOVE
} | Out-Null

#SOME PARALLEL ACTIVITY

Wait-Job -ID 1 | Out-Null

即使父脚本已关闭,下载仍在继续。根据文档,父脚本的终止将导致所有相应作业的停止。那为什么一直在下载呢?

P.S。我知道我可以通过使用 DownloadFileAsync 避免在这里开始工作,但我真的很想了解这种机制:)

我认为这是因为执行已流入 PowerShell 不再控制它的 .NET 方法。

例如,如果我 运行...

Start-Job -ScriptBlock { Start-Sleep -Seconds 30 }

...或...

Start-Job -ScriptBlock { while ($true) { } }

...我可以在任务管理器中看到有两个 PowerShell 进程。如果我然后单击 PowerShell window 的关闭按钮(Alt + F4 对我不起作用)两个进程立即消失.

如果我运行...

Start-Job -ScriptBlock { [System.Threading.Thread]::Sleep([TimeSpan]::FromSeconds(30)) }

...然后我在任务管理器中也看到了两个 PowerShell 进程。但是,关闭PowerShell window 后,只有一个PowerShell 进程立即消失;另一个在 30 秒的剩余时间后消失。有趣的是,如果我 运行 exit 而不是关闭 PowerShell window,window 将保持打开状态并带有闪烁的光标,直到作业完成。

观察这一点的另一种方法是 Stop-Job。在这个脚本中...

$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 30 }
Start-Sleep -Seconds 1 # Give the job time to transition to the Running state
$job | Stop-Job

...Stop-Job returns 立即,而在此脚本中...

$job = Start-Job -ScriptBlock { [System.Threading.Thread]::Sleep([TimeSpan]::FromSeconds(30)) }
Start-Sleep -Seconds 1 # Give the job time to transition to the Running state
$job | Stop-Job

...需要 30 秒。

我不太熟悉 PowerShell 执行的低级工作原理,但在父进程关闭时的前两个片段中,作业进程是 运行ning PowerShell 代码,因此它将是能够在任意点中断并响应父进程的终止信号。在第三个片段中,作业进程是 运行ning .NET 代码,等待方法 return。我不能说 运行 调用 .NET 代码的线程是与父进程通信的同一个线程,还是不同的线程,而 PowerShell 只是尊重 the dangers of aborting another thread(PowerShell当 运行 在工作之外时中断 DownloadFile() 退出没有问题建议前者),但结果是相同的:作业进程不会终止,因为它“卡在”.NET 代码中直到完成。

这也可能与为什么 Ctrl + C 在执行 .NET 方法时不能(立即)工作有关。参见

还有一点:确保在 finally 块中调用 Dispose() 以确保它确实被调用,即使 DownloadFile() 抛出异常...

$wbcl = New-Object System.Net.WebClient
try
{
    $wbcl.DownloadFile($uri1, $localfile1)
}
finally
{
    $wbcl.Dispose()
}

简单地说,这...

$uri1       = "blabla.com/distro/blabla_2gb.exe"
$localfile1 = "$Env:userprofile\Downloads\blabla_2gb.exe"

$wbcl = New-Object System.Net.WebClient
$wbcl.DownloadFile($uri1, $localfile1)
$wbcl.Dispose()

.. 不是工作。这是一个交互式会话。 Close/exit 会话,您已终止 activity。

这个...

Start-Job -ScriptBlock `
{
  #SAME CODE AS ABOVE
} | Out-Null

#SOME PARALLEL ACTIVITY

Wait-Job -ID 1 | Out-Null

... 是真正的后台作业,从交互式 Powershell 会话开始。

如果您想查看您的代码正在调用什么,请利用...

Trace-Command

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/trace-command?view=powershell-7.1

Trace-Command -Name metadata,parameterbinding,cmdlet -Expression {
    $uri1       = "blabla.com/distro/blabla_2gb.exe"
    $localfile1 = "$Env:userprofile\Downloads\blabla_2gb.exe"

    $wbcl = New-Object System.Net.WebClient
    $wbcl.DownloadFile($uri1, $localfile1)
    $wbcl.Dispose()
} -PSHost

你会注意到,你从上面得到了大量的交互数据。

# Results
<#
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [New-Object]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [New-Object]
DEBUG: ParameterBinding Information: 0 :     BIND arg [System.Net.WebClient] to parameter [TypeName]
DEBUG: ParameterBinding Information: 0 :         Executing VALIDATION metadata: [System.Management.Automation.ValidateTrustedDataAttribute]
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.Net.WebClient] to param [TypeName] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [New-Object]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Module]
DEBUG: ParameterBinding Information: 0 :     BIND arg [True] to parameter [ListAvailable]
DEBUG: ParameterBinding Information: 0 :         COERCE arg to [System.Management.Automation.SwitchParameter]
DEBUG: ParameterBinding Information: 0 :             Trying to convert argument value from System.Boolean to System.Management.Automation.SwitchParamet
er...

#>

使用 Job 执行上述操作,不会 return 交互内容。你要具体问一下job/details.

的状态
Get-Job
# Results
<#
Id Name PSJobTypeName State     HasMoreData Location  Command 
-- ---- ------------- -----     ----------- --------  ------- 
1  Job1 BackgroundJob Completed True        localhost ... 
#>

Get-Job -Name 'Job1' | 
Select-Object -Property '*' | 
Format-List -Force
# Results
<#
State         : Completed
HasMoreData   : True
StatusMessage : 
Location      : localhost
Command       : 
                    $uri1       = 'http://mirror.internode.on.net/pub/test/10meg.test'
                    $localfile1 = "$Env:userprofile\Downloadsmeg.test"
                
                    $wbcl = New-Object System.Net.WebClient
                    $wbcl.DownloadFile($uri1, $localfile1)
                    $wbcl.Dispose()
            
                    #SOME PARALLEL ACTIVITY
                    Wait-Job -ID 1 | 
                    Out-Null
            
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : 1af73ea0-c0bf-4cc1-b637-71b0e48862bc
Id            : 1
Name          : Job1
ChildJobs     : {Job2}
PSBeginTime   : 18-Apr-21 21:29:44
PSEndTime     : 18-Apr-21 21:29:46
PSJobTypeName : BackgroundJob
Output        : {}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}
Information   : {}
#>

根据我对下载方式的评论:

https://blog.jourdant.me/post/3-ways-to-download-files-with-powershell

  1. Invoke-WebRequest

Cons

Speed. This cmdlet is slow. From what I have observed, the HTTP response stream is buffered into memory. Once the file has been fully loaded, it is flushed to disk. This adds a huge performance hit and potential memory issues for large files. If anyone knows specifics on how this cmdlet operates, let me know!.

Another potentially serious con for this method is the reliance on Internet Explorer. For example, this cmdlet cannot be used on Windows Server Core edition servers as the Internet Explorer binaries are not included by default. In some cases, you can use the -UseBasicParsing parameter, but it does not work in all cases.

  1. System.Net.WebClient

A common .NET class used for downloading files is the System.Net.WebClient class.

Cons

There is no visible progress indicator (or any way to query the progress mid-transfer). It essentially blocks the thread until the download completes or fails. This isn't a major con, however, sometimes it is handy to know how far through the transfer you are.

  1. Start-BitsTransfer

If you haven't heard of BITS before, check this out. BITS is primarily designed for asynchronous file downloads, but works perfectly fine synchronously too (assuming you have BITS enabled).

Cons

While BITS is enabled by default on many machines, you can't guarantee it is enabled on all (unless you are actively managing this). Also with the way BITS is designed, if other BITS jobs are running in the background, your job could be queued or run at a later time hindering the execution of your script.