functions/Invoke-DBScriptRunner.ps1
function Invoke-DBScriptRunner { <# .SYNOPSIS Runs a query against one or more servers and databases. Captures the results and any messages. .DESCRIPTION Runs a query against one or more servers and databases. Captures the results and any messages. .PARAMETER Servers Collection of server / database names to run the query against. .PARAMETER Credentials Specifies credentials to connect to the database with. If not supplied then a trusted connection will be used. .PARAMETER Query The query to run against each server / database combo. .PARAMETER MaxThreads The max number of threads to run the query with. Defaults to 8. .PARAMETER CommandTimeout The command timeout for the query in seconds. .OUTPUTS ServerInstance - The ServerInstance passed in. Database - The Database passed in. Results - The results of they query if there are any as a [System.Data.DataTable]. Messages - the output of any PRINT statements used in the query. Success - True if the query succeeded, else false. Exception - A [System.Exception] if the query fails for any reason. .NOTES .EXAMPLE Runs the query against all of the server / databases specified. $servers = @() $servers += New-DBServer -ServerInstance "Server1" -Database "DbName1" $servers += New-DBServer -ServerInstance "Server1" -Database "DbName2" $servers += New-DBServer -ServerInstance "Server2" -Database "DbName1" $servers += New-DBServer -ServerInstance "Server2" -Database "DbName2" $query = " SET NOCOUNT ON PRINT CONCAT('HELLO WORLD: ', GETDATE()) PRINT CONCAT('FROM USER: ', ORIGINAL_LOGIN()) SELECT @@SERVERNAME AS [SERVERNAME], DB_NAME() AS [DB_NAME], @@VERSION AS [VERSION] " $results = Invoke-DBScriptRunner -Servers $servers -Query $query # the metadata return for each query invoked $results # output the total DataTable results of each query $results.Results | Format-Table .LINK https://github.com/tcartwright/tcdbtools .NOTES Author: Tim Cartwright #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateCount(1, 999)] [DBServer[]]$Servers, [pscredential]$Credentials, [string]$Query, [ValidateRange(1, 16)] [int]$MaxThreads = 8, [ValidateRange(1, 7200)] [int]$CommandTimeout = 30 ) begin { $ret = [System.Collections.ArrayList]::new() $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) # $RunspacePool.ThreadOptions = "ReuseThread" $RunspacePool.Open() $jobs = New-Object System.Collections.ArrayList $activity = "Running scripts" $counter = 1 [int]$total = $Servers.Count } process { try { foreach ($server in $Servers) { # $QueryScriptRunnerBlock.Invoke($server, $Credentials, $query, $CommandTimeout) $jobName = "Running query on: $($server.ServerInstance) - $($server.database)" Write-Verbose "JOB $jobName SQL: `r`n$sql`r`n`r`n" Write-Progress -Activity $activity ` -Status $jobName ` -PercentComplete (GetPercentComplete -counter $counter -total $total) $PowerShell = [powershell]::Create() $PowerShell.RunspacePool = $RunspacePool $PowerShell.AddScript("Import-Module SqlServer") | Out-Null $PowerShell.AddScript(".\functions\ado\New-DBSqlConnection.ps1") | Out-Null $PowerShell.AddScript(".\functions\ado\Invoke-DBDataTableQuery.ps1") | Out-Null $PowerShell.AddScript($ScriptRunnerBlock) | Out-Null $PowerShell.AddArgument($server.ServerInstance) | Out-Null $PowerShell.AddArgument($server.Database) | Out-Null $PowerShell.AddArgument($Credentials) | Out-Null $PowerShell.AddArgument($query) | Out-Null $PowerShell.AddArgument($CommandTimeout) | Out-Null $Handle = $PowerShell.BeginInvoke() $temp = "" | Select-Object Name, PowerShell, Handle $temp.Name = $jobName $temp.PowerShell = $PowerShell $temp.Handle = $Handle $jobs.Add($temp) | Out-Null $counter++ } Write-Progress -Activity $activity -Completed <# # START SCANNING THE JOBS WAITING FOR THEM TO FINISH #> $counter = 0 $activity = "Waiting for $($total) jobs to finish" while ($Jobs.Handle.IsCompleted -contains $false) { $counter = ($Jobs | Where-Object { $_.Handle.IsCompleted }).Count Write-Progress -Activity $activity ` -Status “Job(s) $counter of $total done” ` -PercentComplete (GetPercentComplete -counter $counter -total $total) Start-Sleep -Milliseconds 500 } Write-Progress -Activity $activity -Completed <# # NOW GATHER THE RESULTS OF THE JOBS #> foreach($job in $jobs) { $result = $job.Powershell.EndInvoke($job.Handle) $job.Powershell.Dispose() if ($result) { $ret.Add($result) | Out-Null } } return $ret | Sort-Object -property @{ Expression={$_.ServerInstance} }, @{ Expression={$_.Database} } } finally { if ($connection) { $connection.Dispose() } } } end { if ($RunspacePool) { $RunspacePool.Dispose() } } } class DBServer { [String]$ServerInstance [String]$Database = "master" DBServer ([String]$ServerInstance, [String]$Database) { $this.ServerInstance = $ServerInstance $this.Database = $Database } } function New-DBServer() { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Not needed')] param([String]$ServerInstance, [String]$Database = "master") return [DBServer]::new($ServerInstance, $Database) } |