Sql-server – Running sp_AskBrent with @ExpertMode = 1 in PowerShell

powershellsp-askbrentsql serversqlcmd

I am trying to run sp_AskBrent from PowerShell using Invoke-SQLCmd and capture its output in a variable:

$query = "EXEC SAIDBA.monitoring.sp_AskBrent @Seconds=10;"
$check = Invoke-Sqlcmd -ServerInstance $ServerInstance -Query $query 
             -ErrorAction Stop -ConnectionTimeout 3

When running with ExpertMode = 0, no problem.

But when running with ExpertMode = 1, we notice three things:

  1. It outputs every data to the shell
  2. $check is null
  3. It ends with the following error:

    Invoke-Sqlcmd: Duplicate column names are not permitted in SQL PowerShell. To repeat a column, use a column alias for the duplicate column in the format Column_Name AS New_Name.
    At line:1 char:18

    • $check = Invoke-Sqlcmd -ServerInstance $ServerInstance -Query $query -Er …
    • CategoryInfo: SyntaxError: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
    • FullyQualifiedErrorId : DuplicateColumnNameErrorMessage, Microsoft.SqlServer.Management.PowerShell.GetScriptCommand

I think there is a workaround of providing parameters for sp_AskBrent to store the ExpertMode data into tables and select from those tables afterwards, but I want to make sure there is no way to get everything back at once in PowerShell.

Best Answer

Not to discount Brent's answer (although he does give some good ones on his training at times)...

In PowerShell you can execute queries that return multiple datasets, you just cannot do this using Invoke-Sqlcmd as it is not built for it currently.

You would have two options of doing this with either .NET native code (e.g. System.Data.SqlClient) or using the trusty SMO. I tend to chose SMO simply in the event I want to include server properties or something else that SMO has access to.

In the context of sp_AskBrent I went ahead and spent a few minutes just building the output into an HTML report. The main points of interest in this script is that to handle multiple datasets you will execute your query with ExecuteWithResults method which is available under the following namespaces:

  • Microsoft.SqlServer.Management.Smo.Server
  • Microsoft.SqlServer.Management.Smo.Database

You can see an example of using the database namespace on MSDN here. In my script I am using the Server namespace. It will work either way but if you review the MSDN article the ExecuteWithResults will return a DataSet object and that object will contain a DataTable. The number of DataTables is based on the number of datasets returned by your code. In the case of Brent's procedure you will get back 5 DataTables. You could verify this in the code below by adding in $results.Count, just before the foreach loop.

Now one other note in this script is I chose to output to HTML. You can chose another format if desired but outputting everything to the console is just not readable. I also will note I added help information so you can use Get-Help against the script if you need to see details on parameters or reminder yourself how to call it.

I used a little snippet of code from Pinal Dave to just generate some CPU usage on my local instance for this example. (Otherwise the procedure did not return much information).

enter image description here

enter image description here

Script

<#
    .SYNOPSIS
        Executes sp_AskBrent and outputs to HTML
    .DESCRIPTION
        Execute sp_AskBrent in normal or expert mode and outputs to an HTML.
    .PARAMETER server
        String. [REQUIRED] The SQL Server instance you wish to get results on.
    .PARAMETER timelimit
        Integer. Time used for @seconds parameter of sp_AskBrent
    .PARAMETER expertMode
        Switch. Just opts to have @ExpertMode = 1 for the query
    .PARAMETER sqlversion
        Integer. If on machine with multiple SQL Server tool versions installed, specify version.
    .PARAMETER outfile
        String. Set output file to generate HTML
    .EXAMPLE
    Run command in normal mode, which returns one dataset
    .\Script.ps1 -server MANATARMS\SQL12 -timelimit 5 -sqlversion 11 -outfile 'C:\temp\MyServer.html'
    .EXAMPLE
    Run command in expert mode, which returns multiple datasets
    .\Script.ps1 -server MANATARMS\SQL12 -timelimit 5 -expertMode -sqlversion 11 -outfile 'C:\temp\MyServer.html'
    .NOTES
    Does not check if sp_AskBrent is on the server before executing the query.
#>
[cmdletbinding()]
param(
    [Parameter(Mandatory=$true,Position=0)]
    [Alias("instance")]
    [string]
    $server,

    [Parameter(Mandatory=$false,Position=1)]
    [int]
    $timelimit = 10,

    [Parameter(Mandatory=$false,Position=2)]
    [switch]
    $expertMode,

    [Parameter(Mandatory=$false,Position=3)]
    [int]
    $sqlversion = 11,

    [Parameter(Mandatory=$false,Position=4)]
    [string]
    $outfile= 'C:\temp\Testing.html'
)

$HtmlTop = @"
<!DOCTYPE html>
<html>
<head>
<style>
    body { 
        background-color:white;
        font-family:Tahoma,Arial;
        font-size:10pt;
    }
    table {
        border-collapse: collapse;
    }
    th { 
        border:2px solid black;
        color:white;
        background-color: #0066FF;
        padding: 4px;
    }
        th.subhead {
            color:black;
            background-color: #8dbafc
        }
    td {
        border: 1px solid black;
        padding: 2px;
    }

</style>
</head>
<body>
<h2>Server Name: $($server)</h2>
<h3>Run Date: $(Get-Date)</h3>
"@

$HtmlTop | Out-File -FilePath $outfile -Force

if ($expertMode) {
    $askBrent = "EXEC sp_AskBrent @Seconds=$($timelimit), @ExpertMode=1"
}
else {
    $askBrent = "EXEC sp_AskBrent @Seconds=$($timelimit), @ExpertMode=0"
}

try { 
    Add-Type -AssemblyName "Microsoft.SqlServer.Smo,Version=$($sqlversion).0.0.0,Culture=neutral,PublicKeyToken=89845dcd8080cc91"
}
catch {
    Add-Type -AssemblyName "Microsoft.SqlServer.Smo"
}

$srv = New-Object 'Microsoft.SqlServer.Management.Smo.Server' $server

$results = $srv.ConnectionContext.ExecuteWithResults($askBrent);

foreach ($t in $results.Tables) {
    $t | Select-Object * -ExcludeProperty RowError, RowState, HasErrors, Table, ItemArray | ConvertTo-Html -As Table -Fragment | Out-String | Out-File -FilePath $outfile -Append
    ## This is just to get a break between tables ##
    "<hr>" | Out-File -FilePath $outfile -Append
}