PowerShell脚本更新SQL表异常:仅重复更新单个任务5次
问题根源分析
- 任务ID复用问题:原脚本用
ExecuteScalar()获取tID,该方法仅返回查询结果的第一行第一列,导致所有任务都复用Tasks表中第一个启用任务的ID,后续TaskLogs的tID全为同一个值,更新时关联错误。 - 事件作用域陷阱:
Register-ObjectEvent的-Action块在独立作用域执行,直接引用外部循环的$rid会因延迟绑定,最终所有事件触发时都使用循环最后一次的$rid值,导致重复更新同一条记录。 - 变量拼写错误:Action块中的
$eventsubsciber拼写错误(应为$eventSubscriber),导致无法注销事件订阅,可能引发重复触发。 - 函数变量作用域错误:
JobCompleted函数调用时传递了两个参数,但函数定义仅接受一个;且函数内部引用的$job、$rid等变量不在函数作用域内,无法正确获取对应任务信息。 - 数据库连接有效性问题:长期运行的脚本中,事件触发时初始创建的数据库连接可能已断开,导致更新操作失败。
修复后的脚本
$connectionstring = "Server=localhost;Database=testDB;Trusted_Connection=True;" $jobs=@() $testOutput="C:\Users\tarik.gacanovic\Desktop\outfile.txt" $maxConcurrentJobs=5 # 一次性查询所有启用任务的完整信息(含ID、执行路径、参数) $tasksQuery = @" SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ID, ExecPath, Param1, Param2, Param3 FROM Tasks WHERE Enabled=1 "@ $tasks = Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'testDB' -Query $tasksQuery function JobCompleted($jobId, $taskId) { # 事件中单独创建数据库连接,避免连接失效 $connection = New-Object System.Data.SqlClient.SqlConnection($connectionstring) try { $connection.Open() $job = Get-Job -Id $jobId if (-not $job) { return } # 根据任务状态设置对应参数 switch ($job.State) { 'Completed' { $status = 2 $note = "任务执行成功" } 'Failed' { $status = 3 $note = "任务执行失败" } default { $status = 9 $note = "状态未定义:$($job.State)" } } # 参数化更新语句,避免SQL注入并确保关联正确 $updateQuery = "UPDATE TaskLogs SET Status=@Status, Finished=GETDATE(), Note=@Note WHERE ID=@JobId AND tID=@TaskId" $cmd = New-Object System.Data.SqlClient.SqlCommand($updateQuery, $connection) $cmd.Parameters.AddWithValue("@Status", $status) $cmd.Parameters.AddWithValue("@Note", $note) $cmd.Parameters.AddWithValue("@JobId", $jobId) $cmd.Parameters.AddWithValue("@TaskId", $taskId) $rowsAffected = $cmd.ExecuteNonQuery() "$(Get-Date): 更新TaskLogs,受影响行数:$rowsAffected" | Out-File -FilePath $testOutput -Append } catch { "$(Get-Date): 更新失败 - $_" | Out-File -FilePath $testOutput -Append } finally { $connection.Close() } } foreach($task in $tasks){ # 控制并发任务数 while(($jobs | Where-Object{$_.State -eq 'Running'}).Count -ge $maxConcurrentJobs){ Start-Sleep -Milliseconds 500 } # 通过参数传递执行路径和参数,避免依赖环境变量 $job = Start-Job -ScriptBlock { param($execPath, $param1, $param2, $param3) & $execPath $param1 $param2 $param3 } -ArgumentList $task.ExecPath, $task.Param1, $task.Param2, $task.Param3 $jobs += $job # 插入TaskLogs初始记录,使用参数化查询 $connection = New-Object System.Data.SqlClient.SqlConnection($connectionstring) try { $connection.Open() $status = 1 $note = "任务启动中 - $($job.Name)" $insertQuery = "INSERT INTO TaskLogs(ID, tID, Status, Created, Finished, Note) VALUES(@JobId, @TaskId, @Status, GETDATE(), NULL, @Note)" $cmd = New-Object System.Data.SqlClient.SqlCommand($insertQuery, $connection) $cmd.Parameters.AddWithValue("@JobId", $job.Id) $cmd.Parameters.AddWithValue("@TaskId", $task.ID) $cmd.Parameters.AddWithValue("@Status", $status) $cmd.Parameters.AddWithValue("@Note", $note) $cmd.ExecuteNonQuery() "$(Get-Date): 插入TaskLogs记录" | Out-File -FilePath $testOutput -Append } catch { "$(Get-Date): 插入记录失败 - $_" | Out-File -FilePath $testOutput -Append Stop-Job -Job $job Remove-Job -Job $job continue } finally { $connection.Close() } # 注册事件,通过-MessageData传递任务ID,避免作用域陷阱 Register-ObjectEvent -InputObject $job -EventName StateChanged -Action { $jobId = $EventArgs.JobStateInfo.Job.Id $taskId = $Event.MessageData JobCompleted -jobId $jobId -taskId $taskId Unregister-Event -SourceIdentifier $EventSubscriber.SourceIdentifier Remove-Job -Id $jobId } -MessageData $task.ID }
关键修复点说明
- 任务ID正确关联:一次性查询所有启用任务的ID,确保每个任务对应唯一的
tID。 - 解决作用域陷阱:通过
-MessageData将任务ID传递给事件Action,从事件参数中直接获取JobID,避免引用外部循环变量。 - 参数化SQL查询:所有数据库操作使用参数化查询,防止SQL注入并确保变量传递准确。
- 独立数据库连接:在事件和插入操作中单独创建连接,避免长期持有连接导致的失效问题。
- 完善错误处理:增加异常捕获,记录错误日志并清理失败任务,提升脚本稳定性。
内容的提问来源于stack exchange,提问作者Tigi Casper
相关产品推荐
相关产品推荐

