Last year I wrote an article that explained how to use the SQLSERVER PSDrive to create an HTML report highlighting some server and database information. If you want a refresher you can find that article here. In short, you can install the SQL Server PowerShell module on your client desktop and use it to manage remote servers.
During a PowerShell class I was teaching this week, I included some content on the SQL cmdlets and had an opportunity to revisit the original script. Even though the original version was intended to work with remote servers, I had a few bugs that prevented that from happening. I also decided to include some additional information as well as incorporate more conditional formatting. For example, depending on how much free space is left, the report might format the value in yellow, to indicate a warning or in red to indicate something more critical.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
In the SQLSERVER PSdrive, the Databases directory doesn't show any system databasese by default unless you use -Force.
So I updated the script to allow you to include those databases if you want.
Here is the revised script:
#requires -version 4.0 #requires -module SQLPS <# Create a SQL Server database report This script includes a graphic file which by default should be in the same directory as this script. Otherwise, edit the $ImageFile variable. It is recommended that you import the SQLPS module first and then run this script. Usage: c:\scripts\SQLServerReport -computername CHI-SQL01 -includesystem -path c:\work\CHI-SQL01-DB.htm #> [cmdletbinding()] Param( [Parameter(Position=0,HelpMessage="Enter the name of a SQL server")] [ValidateNotNullorEmpty()] [Alias("CN")] [string]$computername=$env:computername, [Parameter(Position=1,HelpMessage="Enter the named instance such as Default")] [ValidateNotNullorEmpty()] [string]$Instance = "Default", [switch]$IncludeSystem, [ValidateNotNullorEmpty()] [string]$Path="$env:temp\sqlrpt.htm", [switch]$ShowReport ) $scriptversion = "2.0" Write-Verbose "Starting $($MyInvocation.Mycommand)" #define the path to the graphic $graphic = "db.png" #the default location is the same directory as this script $imagefile = Join-path -path (split-path $MyInvocation.InvocationName) -ChildPath $graphic #define an empty array to hold all of the HTML fragments $fragments=@("<br><br><br>") #get uptime Write-Verbose "Getting SQL Server uptime" Try { #try to connect to the SQL server $starttime = Invoke-Sqlcmd -Query 'SELECT sqlserver_start_time AS StartTime FROM sys.dm_os_sys_info' -ServerInstance $computername -database master -ErrorAction Stop } Catch { Write-warning "Can't connect to $computername. $($_.exception.message)" #bail out Return } Write-Verbose "Getting SQL Version" $version = Invoke-Sqlcmd "Select @@version AS Version,@@ServerName AS Name" -ServerInstance $computername #create an object $uptime = New-Object -TypeName PSObject -Property @{ StartTime = $starttime.Item(0) Uptime = (Get-Date)-$starttime.Item(0) Version = $version.Item(0).replace("`n","|") } $tmp = $uptime | ConvertTo-HTML -fragment -AS List #replace "|" place holder with <br>" $fragments += $tmp.replace("|","<br>") #get services Write-Verbose "Querying SQL services" $services = Get-Service -DisplayName *SQL* -ComputerName $computername | Select Name,Displayname,Status #add conditional formatting to display stopped services in yellow [xml]$html = $services | ConvertTo-Html -fragment #check each row, skipping the TH header row for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the last column and assign a class to the row if ($html.table.tr[$i].td[-1] -ne 'Running') { $html.table.tr[$i].lastChild.setAttribute("class","warn") | Out-Null } } #for $fragments += "<h3>SQL Services</h3>" $fragments += $html.InnerXML #get database information #path to databases $dbpath = "SQLServer:\SQL\$computername\$instance\databases" Write-Verbose "Querying database information from $dbpath" if ($IncludeSystem) { Write-Verbose "Including system databases" $dbs = Get-ChildItem -path $dbpath -Force } else { $dbs = Get-ChildItem -path $dbpath } [xml]$html = $dbs | Select Name, @{Name="SizeMB";Expression={$_.size}}, @{Name="DataSpaceMB";Expression={$_.DataSpaceUsage}}, @{Name="AvailableMB";Expression={$_.SpaceAvailable}}, @{Name="PercentFree";Expression={ [math]::Round((($_.SpaceAvailable/1kb)/$_.size)*100,2) }} | Sort PercentFree | ConvertTo-HTML -fragment for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the last column and assign a class to the row if (($html.table.tr[$i].td[-1] -as [double]) -le 15) { $html.table.tr[$i].lastChild.SetAttribute("class","danger") | Out-Null } elseif (($html.table.tr[$i].td[-1] -as [double]) -le 25) { $class.value = "warn" $html.table.tr[$i].lastChild.SetAttribute("class","warn") | Out-Null } } $fragments += "<h3>Database Utilization</h3>" $fragments += $html.InnerXml $fragments += "<h3>Database Backup</h3>" $fragments += $dbs | Select Name,Owner,CreateDate,Last*,RecoveryModel | ConvertTo-Html -Fragment #volume usage Write-Verbose "Querying system volumes" $data = Get-CimInstance win32_volume -filter "drivetype=3" -ComputerName $computername $drives = foreach ($item in $data) { $prophash = [ordered]@{ Drive = $item.DriveLetter Volume = $item.DeviceID Compressed = $item.Compressed SizeGB = $item.capacity/1GB -as [int] FreeGB = "{0:N4}" -f ($item.Freespace/1GB ) PercentFree = [math]::Round((($item.Freespace/$item.capacity) * 100),2) } #create a new object from the property hash New-Object PSObject -Property $prophash } [xml]$html = $drives | ConvertTo-Html -fragment #check each row, skipping the TH header row for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the last column and assign a class to the row if (($html.table.tr[$i].td[-1] -as [int]) -le 25) { $html.table.tr[$i].lastChild.SetAttribute("class","danger") | Out-Null } elseif (($html.table.tr[$i].td[-1] -as [int]) -le 35) { $class.value = "warn" $html.table.tr[$i].lastChild.SetAttribute("class","warn") | Out-Null } } $fragments += "<h3>Volume Utilization</h3>" $fragments += $html.innerxml #define the HTML style Write-Verbose "preparing report" #encode the graphic file to embed into the HTML $ImageBits = [Convert]::ToBase64String((Get-Content $imagefile -Encoding Byte)) $ImageHTML = "<img src=data:image/png;base64,$($ImageBits) alt='db utilization'/>" #define a here string for the html header $head = @" <style> body { background-color:#FAFAFA; font-family:Arial; font-size:12pt; } td, th { border:1px solid black; border-collapse:collapse; } th { color:white; background-color:black; } table, tr, td, th { padding: 2px; margin: 0px } tr:nth-child(odd) {background-color: lightgray} table { margin-left:50px; } img { float:left; margin: 0px 25px; } .danger {background-color: red} .warn {background-color: yellow} </style> $imagehtml <br><br><br> <H2>SQL Server Report: $($version.name)</H2> <br> "@ #HTML to display at the end of the report $footer = @" <br> <i> Date : $(Get-Date)<br> Author : $env:USERDOMAIN\$env:username<br> Version: $scriptVersion<br> </i> "@ #create the HTML document ConvertTo-HTML -Head $head -Body $fragments -PostContent $footer | Out-File -FilePath $path -Encoding ascii if ($ShowReport) { #open the finished report Write-Verbose "Opening report $path" Invoke-Item $path } Write-Verbose "Ending $($MyInvocation.Mycommand)"
My script includes a graphic file to make it pretty. You can download my graphic file db.png. Put the graphic in the same directory as the script. Or modify the script to use your own graphics. You can then run a command like this to generate the HTML report.
c:\scripts\sqlserverreport.ps1 -computername chi-sql01 -IncludeSystem -path s:\chi-sql01.htm -verbose
This script lacks a provision for alternate credentials and assumes the account you are running under has the necessary SQL permissions. When finished you can get a report like this.
I hope you'll let me know what you think and if you find this useful.
Very nice script.
Thanks for posting the revised version!
Lovely
however invoke-sqlcmd is not the best way to connect to the database – I love the HTML and this is brilliant
What I may do is change this so as not to use the invoke-sqlcmd but the ado .net connection string as invoke-sqlcmd needs additional functionality.
Thanks though this is a great example of how to use ps for wrapping tsql and sql server functionality. I come from the other side as a sql server expert.
You may very well be correct about Invoke-SQLCmd. IT Pros with more database experience and wisdom may certainly choose another option but for those of us with simple needs and more limited knowledge, Invoke-SQLCmd meets the need. Although now I’m curious about where Invoke-SQLCmd is lacking for you in this context.