Таким образом, у меня есть сценарий, который пройдет и проверит с помощью ping-запросов все серверы из списка, который хранится в SQL Server. Сценарий хорошо работает, но он делает все это последовательно (хромое).
Кто-то может выручить меня относительно того, как я изменил бы это для использования многопоточности вместо цикла foreach?
$Server = "ServerName"
$Database = "DatabaseName"
$con = "server=$Server;database=$Database;Integrated Security=sspi"
$cmd = "SELECT ServerName FROM dbo.vwServerListActive"
$da = new-object System.Data.SqlClient.SqlDataAdapter ($cmd, $con)
$dt = new-object System.Data.DataTable
$da.fill($dt) | out-null
foreach ($srv in $dt)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $ping.send($srv.ServerName)
$ServerName = $srv.ServerName
$ServerName
$Reply.status
if ($Reply.status –eq “Success”)
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'"
}
else
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'"
}
$Reply = ""
invoke-sqlcmd -serverinstance $Server -database $Database -query $sql
}
Если у вас есть PowerShell 2.0, вы можете использовать фоновые задания. Вам нужно разбить список серверов на "группы". Учитывая исходную таблицу с serverName и groupName:
CREATE TABLE [dbo].[vwServerListActive](
[serverName] [varchar](50) NULL,
[groupName] [char](1) NULL
)
Небольшая модификация вашего скрипта (сохраните как forum.ps1):
param($groupName)
$Server = "$env:computername\sql2k8"
$Database = "dbautility"
$con = "server=$Server;database=$Database;Integrated Security=sspi"
$cmd = "SELECT ServerName FROM dbo.vwServerListActive WHERE groupName ='$groupName'"
$da = new-object System.Data.SqlClient.SqlDataAdapter ($cmd, $con)
$dt = new-object System.Data.DataTable
$da.fill($dt) | out-null
foreach ($srv in $dt)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $ping.send($srv.ServerName)
new-object PSObject -Property @{ServerName=$($srv.ServerName); Reply=$($Reply.status)}
}
Затем вы можете вызвать скрипт для разных групп:
#groupName A
start-job -FilePath .\forum.ps1 -Name "Test" -ArgumentList "A"
#groupName B
start-job -FilePath .\forum.ps1 -Name "Test" -ArgumentList "B"
Get-Job -name "test" | wait-job | out-null
Get-Job -name "test" | receive-job
#get-job -name "test" |remove-job
Если вы используете PowerShell V1 или sqlps, вы можете использовать System.Diagnostics.ProcessStartInfo для запуска отдельных процессов powershell.exe и передать имя группы.
param($groupName)
$StartInfo = new-object System.Diagnostics.ProcessStartInfo
$StartInfo.FileName = "$pshome\powershell.exe"
$StartInfo.Arguments = " -NoProfile -Command C:\scripts\forum.ps1 $groupName"
$StartInfo.WorkingDirectory = "C:\scripts"
$StartInfo.LoadUserProfile = $true
$StartInfo.UseShellExecute = $true
[System.Diagnostics.Process]::Start($StartInfo) > $null
(Отредактировано в соответствии с предложением Чада Миллера + требование регулирования + исправление задания ожидания + исправление STA)
Support.ps1
powershell -File "Main.ps1" -Sta
Main.ps1
$Server = "ServerName"
$Database = "DatabaseName"
$con = "server=$Server;database=$Database;Integrated Security=sspi"
$cmd = "SELECT ServerName FROM dbo.vwServerListActive"
$da = New-Object System.Data.SqlClient.SqlDataAdapter -ArgumentList $cmd, $con
$dt = New-Object System.Data.DataTable
$da.Fill($dt) | Out-Null
$ThrottleLimit = 10
$activeJobs = New-Object 'System.Collections.Generic.List[Int32]'
$JobStateChanged = {
param (
[System.Object]$Sender,
[System.Management.Automation.JobStateEventArgs]$EventArgs
)
switch ($EventArgs.JobStateInfo.State)
{
Blocked { return }
Completed { $activeJobs.Remove($Sender.Id); break }
Failed { $activeJobs.Remove($Sender.Id); break }
NotStarted { return }
Running { return }
Stopped { $activeJobs.Remove($Sender.Id); break }
}
Unregister-Event -SourceIdentifier ("{0}.StateChanged" -f $Sender.Name)
}
foreach ($srv in $dt)
{
while ($true)
{
if ($activeJobs.Count -lt $ThrottleLimit)
{
$job = Start-Job -InitializationScript {
Add-PSSnapin -Name SqlServerCmdletSnapin100
} -ScriptBlock {
param (
[String]$Server,
[String]$Database,
[String]$ServerName
)
if (Test-Connection -ComputerName $ServerName -Quiet)
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'"
}
else
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'"
}
Invoke-SqlCmd -ServerInstance $Server -Database $Database -Query $sql
} -ArgumentList $Server, $Database, $srv.ServerName
$activeJobs.Add($job.Id)
Register-ObjectEvent -InputObject $job -EventName StateChanged -SourceIdentifier ("{0}.StateChanged" -f $job.Name) -Action $JobStateChanged
break
}
}
}
Get-Job | Where-Object { $_.State -eq "Running" } | Wait-Job
Get-Job | Remove-Job
Во-первых, я предлагаю создавать только один раз переменную $ ping вне «foreach ..».
Может быть, более простое решение ... теперь, когда вы используете SQL 2008, почему бы не использовать метод SMO enumAvailableSqlServers: "... SMOApplication] :: EnumAvailableSqlServers ($ false)". Это даст вам список всех доступных серверов в сети. Вот ссылка Microsoft MSDN, чтобы вы могли прочитать об этом:
http://msdn.microsoft.com/en-us/library/ms210350.aspx
so close.... вот что у меня есть
add-pssnapin SqlServerCmdletSnapin100
$Server = "ServerName"
$Database = "DatabaseName"
$con = "server=$Server;database=$Database;Integrated Security=sspi"
$cmd = "SELECT ServerName FROM dbo.vwServerListActive"
$da = New-Object System.Data.SqlClient.SqlDataAdapter -ArgumentList $cmd, $con
$dt = New-Object System.Data.DataTable
$da.Fill($dt) | Out-Null
foreach ($srv in $dt)
{
Start-Job -ScriptBlock {
param (
[String]$Server,
[String]$Database,
[String]$ServerName
)
if (Test-Connection -ComputerName $ServerName -quiet)
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'"
}
else
{
$sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'"
}
Invoke-SqlCmd -ServerInstance $Server -Database $Database -Query $sql
} -ArgumentList $Server, $Database, $srv.ServerName
}
и похоже, что он запускает несколько заданий... но моя таблица никогда не обновляется. Если я удалю "Start-Job" и список аргументов и использую $srv.ServerName, то он работает последовательно, как и раньше. Есть идеи? (Большое спасибо BTW за все ответы)
Вот скрипт от Jim Truher для фоновых заданий в PowerShell v1.0:
http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!130.entry
В PowerShell v2.0 фоновые задания встроены:
http://technet.microsoft.com/en-us/library/dd347692.aspx
-Oisin
Вот страница со сценарием, который может быть вам полезен. Я сам еще не использовал его, поэтому не могу комментировать это, кроме этого.
Powershell на самом деле не делает многопоточность вообще. Мне удалось применить ее, сымитировав ее с помощью скрипта fire-and-forget, запускаемого командой "start [powershell path] scriptname.ps1". Он запустит несколько страховок, но вы не сможете получить от них данные, не выполнив конечный запуск через базу данных или другой механизм передачи сообщений. Отследить, когда дочерние процессы завершаются, тоже непросто.
cmd /c "start /min /low C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command .\evtlogparse.ps1 "
В вашем случае, поскольку вы задаете строку SQL как часть цикла foreach, вы можете попытаться поместить код обновления БД во второй скрипт, который вы запускаете. Потенциально у вас будет много различных процессов, пытающихся обновить одну и ту же таблицу базы данных, так что вероятность возникновения проблем со временем довольно велика.
Кроме того, поскольку вы запускаете множество новых экземпляров powershell, вам потребуется много памяти, чтобы заставить это работать. Цикл foreach может быть просто быстрее, чем запуск кучи процессов.
Итак, это можно сделать, но это даже не похоже на красоту.