Многопоточность Помощь с Powershell - PullRequest
8 голосов
/ 13 июля 2010

Итак, у меня есть скрипт, который будет проходить и проверять все серверы из списка, который хранится в 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


    }

Ответы [ 7 ]

4 голосов
/ 14 июля 2010

(отредактировано согласно предложению Чада Миллера + требование регулирования + исправление ожидания задания + исправление 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
2 голосов
/ 14 июля 2010

Если у вас есть 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
0 голосов
/ 14 июля 2010

Вот сценарий от Джима Трухера для фоновых заданий в 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

0 голосов
/ 14 июля 2010

так близко .... вот что у меня есть

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 за все ответы)

0 голосов
/ 13 июля 2010

Во-первых, я предлагаю создать только один раз переменную $ ping вне 'foreach ..'.
Возможно, более простое решение ... теперь, когда вы используете SQL 2008, почему бы не использовать метод SMO 'enumAvailableSqlServers: "... SMOApplication] :: EnumAvailableSqlServers ($ false)".Это даст вам список всех доступных серверов в сети.Вот ссылка Microsoft MSDN, чтобы вы могли прочитать об этом: http://msdn.microsoft.com/en-us/library/ms210350.aspx

0 голосов
/ 13 июля 2010

Powershell вообще не поддерживает многопоточность.Мне удалось разобрать его на месте, подделав его сценарием «забей и забудь», запущенным с помощью «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 может быть просто быстрее, чем запускать кучу процессов.

Итак, это можно сделать, но это совсем не похоже на красивое.

0 голосов
/ 13 июля 2010

Вот страница со сценарием, который может быть полезен для вас.Я еще не использовал это сам, поэтому я не могу комментировать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...