我有一个PowerShell脚本,它使用异步计时器事件(后台进程)来测量某个条件发生了多长时间,然后再采取适当的操作。 当我在PowerGUI中运行脚本时,这非常正常,但是当我使用dot-sourcing运行脚本或通过批处理文件运行脚本时,Timer事件操作不会触发。
以下是代码段。
$timer = New-Object System.Timers.Timer
$timer.Interval = 10000
$timer.AutoReset = $true
$timeout = 0
$action = {
"timeout: $timeout" | Add-Content $loglocation
<more stuff here>
$timer.stop()
}
$start = Register-ObjectEvent -InputObject $timer -SourceIdentifier TimerElapsed -EventName Elapsed -Action $action
$timer.start()
while(1)
{
<do some testing here>
}
所以当它工作时,我会看到每10秒写入日志的“timeout:XX”输出。但这只发生在编辑器中运行时。当我通过批处理文件运行时没有任何反应(虽然我可以确认while循环处理正常)。
所以我的问题是,当我在PowerGUI内部运行脚本而不是命令行时,为什么我的体验会有所不同?我的想法是范围或并行线程可能存在问题,但我不确定问题是什么。此外,我没有在任何函数或循环中运行这些事件。
答案 0 :(得分:6)
调用脚本文件时,$ action脚本块使用调用者的范围(父范围)执行,而不是脚本文件的范围(子范围)。因此,脚本文件中定义的变量在$ action脚本块中不可用,除非它们被定义为使用全局范围或dot-sourced(这将使它们在全局范围内可用)。有关详细信息,请参阅this wonderful article。
假设以下代码包含在名为test.ps1的文件中。
$timer = New-Object System.Timers.Timer
$timer.Interval = 10000
$timer.AutoReset = $false
$timeout = 100
$location = 'SomeLocation'
$sourceIdentifier = 'SomeIdentifier'
$action = {
Write-Host "Timer Event Elapsed. Timeout: $timeout, Location: $location, SourceIdentifier: $sourceIdentifier"
$timer.stop()
Unregister-Event $sourceIdentifier
}
$start = Register-ObjectEvent -InputObject $timer -SourceIdentifier $sourceIdentifier -EventName Elapsed -Action $action
$timer.start()
while(1)
{
Write-Host "Looping..."
Start-Sleep -s 5
}
从powershell控制台调用时,执行$ action脚本块时,它使用的变量将没有值。
./test.ps1
Timer Event Elapsed. Timeout: , Location: , SourceIdentifier:
如果在调用脚本之前定义$ action脚本块中使用的变量,则在执行操作时值将可用:
$timeout = 5; $location = "SomeLocation"; $sourceIdentifier = "SomeSI"
./test.ps1
Timer Event Elapsed. Timeout: 5, Location: SomeLocation, SourceIdentifier: SomeSI
如果您点源脚本,脚本中定义的变量将在当前范围内可用,因此当操作执行时,值将可用:
. ./test.ps1
Timer Event Elapsed. Timeout: 100, Location: SomeLocation, SourceIdentifier: SomeIdentifier
如果变量已在脚本文件的全局范围内声明:
$global:timeout = 100
$global:location = 'SomeLocation'
$global:sourceIdentifier = 'SomeIdentifier'
然后,当$ action脚本块在父作用域中执行时,这些值将可用:
./test.ps1
Timer Event Elapsed. Timeout: 100, Location: SomeLocation, SourceIdentifier: SomeIdentifier
答案 1 :(得分:0)
就像dugas的答案一样,但是如果您不想使用额外的变量来使您的powershell实例混乱或进行任何点源处理,则可以将其放入函数中。这还具有让您使用命名参数的好处。如果您将来想重用它,也可以使其更具模块化。
function Start-Timer
{
param($timeout = 5, $location = "SomeLocation", $sourceIdentifier = "SomeSI")
$timer = [System.Timers.Timer]::new()
$timer.Interval = $timeout
$timer.AutoReset = $False
$action =
{
$myArgs = $event.MessageData
$timeout = $myArgs.timeout
$location = $myArgs.location
$sourceIdentifier = $myArgs.sourceIdentifier
$timer = $myArgs.timer
Write-Host "Timer Event Elapsed. Timeout: $timeout, Location: $location, SourceIdentifier: $sourceIdentifier"
$timer.Stop()
Unregister-Event $sourceIdentifier
}
# You have to pass the data this way
$passThru =
@{
timeout = $timeout;
location = $location;
sourceIdentifier = $sourceIdentifier;
timer = $timer;
}
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Tick -Action $action -MessageData $passThru | Out-Null
$timer.Start()
}
然后您可以使用命名参数进行调用:
Start-Timer -location "NewLocation"
仅使用此方法的缺点是,如果处理程序使用包含范围中的大量变量,则代码将变得混乱。