Latest Post

Tired of manually hunting down and executing multiple PowerShell scripts one by one? In this post, we’ll break down how to build a centralized, modern GUI dashboard—the Server Report Center—using pure PowerShell and WPF (Windows Presentation Foundation).

This guide serves as a quick revision of the core concepts, XAML integration, and PowerShell tricks used to bring this UI to life.

Here is the code.

Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase

# ==============================================================================
# CONFIGURATION: ADD, REMOVE, OR MODIFY YOUR BLOCKS HERE
# ==============================================================================
$ReportCards = @(
    @{
        Title       = "AWS Disk Info"
        Description = "Get disk usage and storage details for AWS EC2 instances."
        Script      = "AWSDiskInfo.ps1"
        Color       = "#F5A623" # Orange Accent
        BgColor     = "#FFF8F0"
    },
    @{
        Title       = "Azure Disk Info"
        Description = "Get disk usage and storage details for Azure VMs."
        Script      = "AzureDiskInfo.ps1"
        Color       = "#0078D4" # Blue Accent
        BgColor     = "#F0F7FF"
    },
    @{
        Title       = "MS SQL Disk Info"
        Description = "Get disk usage and storage details for SQL Server instances."
        Script      = "MSSQLDiskInfo.ps1"
        Color       = "#D9534F" # Red Accent
        BgColor     = "#FFF0F0"
    },
    @{
        Title       = "SQL Backup Info"
        Description = "Get backup details, status and history for SQL Server."
        Script      = "SQLBackupInfo.ps1"
        Color       = "#6F42C1" # Purple Accent
        BgColor     = "#F5F0FF"
    },
    @{
        Title       = "SQL Health Status"
        Description = "Get overall health status of SQL Server instances and databases."
        Script      = "SQLHealthStatus.ps1"
        Color       = "#28A745" # Green Accent
        BgColor     = "#F0FFF4"
    }
)

# Base Window XAML Template with Custom Frameless Title Bar
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Server Report Center" Height="550" Width="1100"
        WindowStartupLocation="CenterScreen"
        WindowStyle="None" AllowsTransparency="True"
        Background="Transparent" Foreground="#212529">

    <Window.Resources>
        <!-- Style for Window Control Buttons (Min/Max/Close) -->
        <Style x:Key="CaptionBtnStyle" TargetType="Button">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Width" Value="45"/>
            <Setter Property="Height" Value="40"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border x:Name="border" Background="{TemplateBinding Background}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="border" Property="Background" Value="#106EBE"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Specific Hover style for Close Button (Red) -->
        <Style x:Key="CloseBtnStyle" TargetType="Button" BasedOn="{StaticResource CaptionBtnStyle}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#E81123"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <!-- Outer Border with Background, Border, and CornerRadius for the whole window -->
    <Border Background="#F8F9FA" BorderBrush="#0078D4" BorderThickness="1" CornerRadius="12">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="60"/>   <!-- Header & Window Title Bar -->
                <RowDefinition Height="250"/>  <!-- Cards Area -->
                <RowDefinition Height="*"/>    <!-- Terminal / Status Log -->
            </Grid.RowDefinitions>

            <!-- Seamless Blue Top Header Bar (Clipped by parent Border's CornerRadius) -->
            <Border Grid.Row="0" Background="#0078D4" x:Name="TitleBar" CornerRadius="12,12,0,0">
                <Grid>
                    <!-- App Title Text (CENTERED) -->
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                        <TextBlock Text="Server Report Center" FontSize="20" FontWeight="Bold" Foreground="White"/>
                    </StackPanel>

                    <!-- Custom Window Control Buttons -->
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
                        <Button x:Name="BtnMinimize" Content="🗕" Style="{StaticResource CaptionBtnStyle}"/>
                        <Button x:Name="BtnMaximize" Content="🗖" Style="{StaticResource CaptionBtnStyle}"/>
                        <Button x:Name="BtnClose" Content="✕" Style="{StaticResource CloseBtnStyle}"/>
                    </StackPanel>
                </Grid>
            </Border>

            <!-- Dynamic Cards Grid Container -->
            <Grid x:Name="CardGrid" Grid.Row="1" Margin="15,15,15,5">
                <!-- Columns generated dynamically in PowerShell -->
            </Grid>

            <!-- Status & Console Log View -->
            <Border Grid.Row="2" Background="#1E1E1E" CornerRadius="6" Margin="20,5,20,15" Padding="10">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <TextBlock Text="Status Information" Foreground="#89B4FA" FontWeight="Bold" FontSize="12" Margin="0,0,0,5"/>
                    <TextBox x:Name="TxtLog" Grid.Row="1" Background="Transparent" Foreground="#DCDCDC" BorderThickness="0" 
                             FontFamily="Consolas" FontSize="12" IsReadOnly="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap"/>
                </Grid>
            </Border>
        </Grid>
    </Border>
</Window>
"@

# Read XML
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)

# Get Controls
$CardGrid    = $window.FindName("CardGrid")
$TxtLog      = $window.FindName("TxtLog")
$TitleBar    = $window.FindName("TitleBar")
$BtnMinimize = $window.FindName("BtnMinimize")
$BtnMaximize = $window.FindName("BtnMaximize")
$BtnClose    = $window.FindName("BtnClose")

# Enable Window Dragging on the Blue Header Bar
$TitleBar.Add_MouseLeftButtonDown({ $window.DragMove() })

# Handle Custom Window Buttons
$BtnMinimize.Add_Click({ $window.WindowState = [System.Windows.WindowState]::Minimized })
$BtnMaximize.Add_Click({
    if ($window.WindowState -eq [System.Windows.WindowState]::Maximized) {
        $window.WindowState = [System.Windows.WindowState]::Normal
    } else {
        $window.WindowState = [System.Windows.WindowState]::Maximized
    }
})
$BtnClose.Add_Click({ $window.Close() })

# Output Logger Helper
function Write-Log {
    param([string]$Message)
    $TimeStamp = Get-Date -Format "HH:mm:ss"
    $TxtLog.AppendText("[$TimeStamp] $Message`n")
    $TxtLog.ScrollToEnd()
    $window.Dispatcher.Invoke([Action]{}, [System.Windows.Threading.DispatcherPriority]::Background)
}

# Run Script Helper
function Execute-ReportScript {
    param([string]$ScriptName, [string]$ReportTitle)
    
    $ScriptPath = "C:\Test\PowerShellScripts\$ScriptName"
    Write-Log "Initiating: $ReportTitle..."

    if (Test-Path $ScriptPath) {
        try {
            $output = & $ScriptPath | Out-String
            if ($output) { Write-Log $output.Trim() }
            Write-Log "SUCCESS: $ReportTitle executed successfully."
        } catch {
            Write-Log "ERROR: Failed to run $ReportTitle.`n$_"
        }
    } else {
        Write-Log "WARNING: Could not locate file at $ScriptPath"
    }
}

# Dynamic Card Generation
for ($i = 0; $i -lt $ReportCards.Count; $i++) {
    $card = $ReportCards[$i]

    # Add Column Definition
    $colDef = New-Object System.Windows.Controls.ColumnDefinition
    $colDef.Width = New-Object System.Windows.GridLength(1, [System.Windows.GridUnitType]::Star)
    $CardGrid.ColumnDefinitions.Add($colDef)

    # Build Card XML Template Dynamically with Rounded Buttons
    [xml]$cardXaml = @"
    <Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            Background="$($card.BgColor)" BorderBrush="$($card.Color)" BorderThickness="1.5" CornerRadius="8" Margin="5">
        <Grid Margin="12">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="0" HorizontalAlignment="Center">
                <TextBlock Text="$($card.Title)" FontSize="14" FontWeight="Bold" Foreground="#212529" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
                <Rectangle Height="3" Width="30" Fill="$($card.Color)" Margin="0,5,0,0"/>
            </StackPanel>
            <TextBlock Grid.Row="1" Text="$($card.Description)" TextWrapping="Wrap" TextAlignment="Center" FontSize="11" Foreground="#6C757D" VerticalAlignment="Center"/>
            
            <Button Grid.Row="2" Content="► Run Report" Foreground="White" FontWeight="Bold" FontSize="13" Height="36" Cursor="Hand" BorderThickness="0">
                <Button.Template>
                    <ControlTemplate TargetType="Button">
                        <Border Background="$($card.Color)" CornerRadius="6">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </Grid>
    </Border>
"@

    $cardReader = (New-Object System.Xml.XmlNodeReader $cardXaml)
    $borderElement = [Windows.Markup.XamlReader]::Load($cardReader)

    [System.Windows.Controls.Grid]::SetColumn($borderElement, $i)

    $btn = $borderElement.Child.Children[2]
    $btn.Add_Click({
        Execute-ReportScript -ScriptName $card.Script -ReportTitle $card.Title
    }.GetNewClosure())

    $CardGrid.Children.Add($borderElement) | Out-Null
}

Write-Log "System initialized with $($ReportCards.Count) report modules."

# Render Window
$window.ShowDialog() | Out-Null

# Code completed

# Write above code in the txt file and save it as a ReportLauncher.ps1 and call the ReportLauncher.ps1 from the .bat file. 

Below is the code we can use it for the .bat file, once we click on the .bat file application get opend.

@echo off
powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Test\ReportLauncher.ps1"












 



Use the PowerShell code to get all the Windows servers' drive reports in one go.

<#
.SYNOPSIS
Collects drive space details from a list of Windows servers and emails an HTML report.

.NOTES
- Requires WinRM/CIM access to target servers (Enable-PSRemoting on targets, or WMI/DCOM open via firewall).
- Run under an account with admin rights on target servers, or supply -Credential.
- Works on Windows PowerShell 5.1 (built-in Send-MailMessage). For PS7, see notes at bottom.
#>

# ---- CONFIG ----

# V1 Enhancement 1:
# Read server names from an external text file instead of hard-coding them.
$serverListPath = "C:\Scripts\servers.txt"

$servers = Get-Content $serverListPath |
           Where-Object { $_.Trim() -ne "" }

$smtpServer    = "smtp.yourcompany.com"
$from          = "monitoring@yourcompany.com"
$to            = "you@yourcompany.com"
$subject       = "Windows Servers - Drive Space Report - $(Get-Date -Format 'yyyy-MM-dd')"
$freeThreshold = 20   # percent free below which status = Check Now


# ---- COLLECT DATA ----

$results = foreach ($server in $servers) {
    try {
        $disks = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $server -Filter "DriveType=3" -ErrorAction Stop

        foreach ($disk in $disks) {
            $sizeGB    = [math]::Round($disk.Size / 1GB, 2)
            $freeGB    = [math]::Round($disk.FreeSpace / 1GB, 2)
            $usedGB    = [math]::Round($sizeGB - $freeGB, 2)
            $pctFree   = if ($sizeGB -gt 0) { [math]::Round(($freeGB / $sizeGB) * 100, 1) } else { 0 }
            $status    = if ($pctFree -lt $freeThreshold) { "Check Now" } else { "Healthy" }

            [PSCustomObject]@{
                Server      = $server
                DeviceID    = $disk.DeviceID
                SizeGB      = $sizeGB
                UsedGB      = $usedGB
                FreeGB      = $freeGB
                PercentFree = $pctFree
                Status      = $status
            }
        }
    }
    catch {
        [PSCustomObject]@{
            Server      = $server
            DeviceID    = "-"
            SizeGB      = $null
            UsedGB      = $null
            FreeGB      = $null
            PercentFree = $null
            Status      = "Server not reachable"
        }
    }
}


# ---- BUILD HTML TABLE MANUALLY (for per-cell color coding) ----

$style = @"
<style>
table { border-collapse: collapse; font-family: Segoe UI, Arial, sans-serif; font-size: 13px; }
th, td { border: 1px solid #ccc; padding: 6px 12px; text-align: left; }
th { background: #2c3e50; color: white; }
tr:nth-child(even) { background: #f4f4f4; }
.healthy { color: green; font-weight: bold; }
.checknow { color: red; font-weight: bold; }
.unreachable { color: red; font-weight: bold; background: #ffe5e5; }
</style>
"@

$rows = foreach ($r in $results) {

    $statusClass = switch ($r.Status) {
        "Healthy"              { "healthy" }
        "Check Now"            { "checknow" }
        "Server not reachable" { "unreachable" }
        default                { "" }
    }

    "<tr>
        <td>$($r.Server)</td>
        <td>$($r.DeviceID)</td>
        <td>$($r.SizeGB)</td>
        <td>$($r.UsedGB)</td>
        <td>$($r.FreeGB)</td>
        <td>$($r.PercentFree)</td>
        <td class='$statusClass'>$($r.Status)</td>
    </tr>"
}


$html = @"
$style
<h2>Windows Server Drive Space Report - $(Get-Date -Format 'yyyy-MM-dd')</h2>

<table>
    <tr>
        <th>Server</th> 
        <th>DeviceID (Drive)</th> 
        <th>SizeGB</th> 
        <th>UsedGB</th> 
        <th>FreeGB</th> 
        <th>PercentFree</th> 
        <th>Status</th>
    </tr>
    $($rows -join "`n")
</table>

<br>
<p>
    Regards,<br>
    <strong>SQL DBA Team</strong><br>
    Database Administration
</p>
"@


# ---- SEND EMAIL ----

Send-MailMessage -From $from -To $to -Subject $subject -Body $html -BodyAsHtml -SmtpServer $smtpServer


<#
---- PS7 NOTE ----
Send-MailMessage is deprecated but still functional on Windows PowerShell 5.1 (default on Server OS).
On PowerShell 7+, replace the Send-MailMessage line with a System.Net.Mail.SmtpClient call, e.g.:

$msg = New-Object System.Net.Mail.MailMessage($from, $to, $subject, $html)
$msg.IsBodyHtml = $true
$smtp = New-Object System.Net.Mail.SmtpClient($smtpServer)
$smtp.Send($msg)

For all the disks" 
$disks = Get-CimInstance -ClassName Win32_LogicalDisk `
    -ComputerName $server `
    -Filter "DriveType=3" `
    -ErrorAction Stop
ONLY C: drive
$disks = Get-CimInstance -ClassName Win32_LogicalDisk `
    -ComputerName $server `
    -Filter "DeviceID='C:'" `
    -ErrorAction Stop
#>


 Latest updated one




Check this 


/* ============================================================================
   SQL SERVER HEALTH CHECK REPORT
   ----------------------------------------------------------------------------
   Single self-contained stored procedure that:
     1. Collects Server / Database / Backup / Disk health information
     2. Renders it into an HTML email body (pure HTML + inline CSS only,
        no external CDN / image / font links)
     3. Sends it via Database Mail (msdb.dbo.sp_send_dbmail)

   DEPLOY:
     - Create this in a utility/admin database on EVERY SQL Server instance
       (e.g. [DBA_Admin]) or in msdb if you prefer.
     - Requires Database Mail to be configured with a working profile.
     - Schedule via SQL Agent Job -> "EXEC dbo.usp_SQLServerHealthCheckReport"

   ASSUMPTIONS (documented so you can tune them to your environment):
     - Backup section excludes tempdb and system databases (master/model/msdb)
       to mirror typical operational reporting. Remove the filter below if you
       want system databases included in the backup checks too.
     - Log-backup checks only apply to databases NOT in SIMPLE recovery model.
     - A backup type that has NEVER been taken is treated as overdue
       (NULL last-backup date = CheckNow), per the requested rule set.
     - Disk stats are pulled via sys.dm_os_volume_stats (works for any drive
       hosting at least one SQL Server data/log file).
   ============================================================================ */

CREATE OR ALTER PROCEDURE dbo.usp_SQLServerHealthCheckReport
    @ProfileName      NVARCHAR(128) = N'DBMailProfile',   -- Database Mail profile name
    @Recipients       NVARCHAR(MAX) = N'dba-team@company.com',
    @Subject          NVARCHAR(255) = NULL,
    @FullBackupDaysThreshold   INT = 7,     -- Full backup age threshold (days)
    @DiffBackupHoursThreshold  INT = 24,    -- Diff backup age threshold (hours)
    @LogBackupMinutesThreshold INT = 60,    -- Log backup age threshold (minutes)
    @LowDiskCriticalPct        DECIMAL(5,2) = 15.00,  -- < this % -> Red
    @LowDiskWarningPct         DECIMAL(5,2) = 25.00   -- < this % -> Yellow
AS
BEGIN
    SET NOCOUNT ON;

    ----------------------------------------------------------------------
    -- 0. Common variables
    ----------------------------------------------------------------------
    DECLARE @GetDate        DATETIME = GETDATE();
    DECLARE @HostName       NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('MachineName'));
    DECLARE @ServerName     NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ServerName'));
    -- The following variables are no longer used in the Server Details table but kept for potential future use.
    DECLARE @Edition        NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('Edition'));
    DECLARE @ProductVersion NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ProductVersion'));
    DECLARE @IsClusteredInt INT           = CONVERT(INT, SERVERPROPERTY('IsClustered'));
    DECLARE @IsClustered    NVARCHAR(10)  = CASE WHEN @IsClusteredInt = 1 THEN N'Yes' ELSE N'No' END;
    DECLARE @CurrentNode    NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ComputerNamePhysicalNetBIOS'));
    DECLARE @SQLRestart     DATETIME;
    DECLARE @UptimeHrs      INT;
    DECLARE @SQLVersionFull NVARCHAR(400) = @@VERSION;

    SELECT @SQLRestart = sqlserver_start_time FROM sys.dm_os_sys_info;
    SET @UptimeHrs = DATEDIFF(HOUR, @SQLRestart, @GetDate);
    SET @SQLVersionFull = LTRIM(RTRIM(LEFT(@SQLVersionFull, CHARINDEX(CHAR(10), @SQLVersionFull + CHAR(10)) - 1)));

    IF @Subject IS NULL
        SET @Subject = N'SQL Server Health Check Report - ' + @ServerName + N' - ' + CONVERT(NVARCHAR(20), @GetDate, 120);

    -- Server status (uptime rule)
    DECLARE @ServerStatusText  NVARCHAR(20);
    DECLARE @ServerStatusColor NVARCHAR(20);
    DECLARE @ServerIsHealthy   BIT;

    IF @UptimeHrs < 24
    BEGIN
        SET @ServerStatusText  = N'CheckNow';
        SET @ServerStatusColor = N'#c0392b';
        SET @ServerIsHealthy   = 0;
    END
    ELSE
    BEGIN
        SET @ServerStatusText  = N'Relax';
        SET @ServerStatusColor = N'#1e7e34';
        SET @ServerIsHealthy   = 1;
    END

    ----------------------------------------------------------------------
    -- 1. DATABASE DETAILS
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DatabaseDetails') IS NOT NULL DROP TABLE #DatabaseDetails;

    SELECT
        d.name                  AS DatabaseName,
        d.recovery_model_desc   AS RecoveryModel,
        d.compatibility_level   AS CompatLevel,
        CAST(SUM(CASE WHEN mf.type = 0 THEN mf.size ELSE 0 END) * 8.0 / 1024 / 1024 AS DECIMAL(12,2)) AS SizeGB,
        d.state_desc            AS DbState,
        CASE WHEN d.state_desc = 'ONLINE' THEN N'Relax' ELSE N'CheckNow' END AS Status,
        CASE WHEN d.state_desc = 'ONLINE' THEN N'#1e7e34' ELSE N'#c0392b' END AS StatusColor
    INTO #DatabaseDetails
    FROM sys.databases d
    LEFT JOIN sys.master_files mf ON mf.database_id = d.database_id
    GROUP BY d.name, d.recovery_model_desc, d.compatibility_level, d.state_desc
    ORDER BY d.name;

    DECLARE @DbTotalCount INT, @DbOnlineCount INT;
    SELECT @DbTotalCount = COUNT(*), @DbOnlineCount = SUM(CASE WHEN DbState = 'ONLINE' THEN 1 ELSE 0 END)
    FROM #DatabaseDetails;

    ----------------------------------------------------------------------
    -- 2. BACKUP DETAILS  (user databases only - excludes tempdb / system DBs)
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#BackupDetails') IS NOT NULL DROP TABLE #BackupDetails;

    ;WITH LastFull AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'D' GROUP BY database_name
    ),
    LastDiff AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'I' GROUP BY database_name
    ),
    LastLog AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'L' GROUP BY database_name
    )
    SELECT
        d.name                AS DatabaseName,
        d.recovery_model_desc AS RecoveryModel,
        lf.LastBackupDate     AS LastFull,
        ld.LastBackupDate     AS LastDiff,
        ll.LastBackupDate     AS LastLog
    INTO #BackupDetails
    FROM sys.databases d
    LEFT JOIN LastFull lf ON lf.database_name = d.name
    LEFT JOIN LastDiff ld ON ld.database_name = d.name
    LEFT JOIN LastLog  ll ON ll.database_name = d.name
    WHERE d.database_id > 4          -- excludes master/model/msdb/tempdb
      AND d.name <> N'tempdb'
      AND d.state_desc = 'ONLINE';

    -- Add computed age / display / status columns
    ALTER TABLE #BackupDetails ADD
        FullBackupDisplay NVARCHAR(20),
        DiffBackupDisplay NVARCHAR(20),
        LogBackupDisplay  NVARCHAR(20),
        FullAgeDisplay  NVARCHAR(20),
        DiffAgeDisplay  NVARCHAR(20),
        LogAgeDisplay   NVARCHAR(20),
        Status          NVARCHAR(20),
        StatusColor     NVARCHAR(20);

    UPDATE b
    SET
        FullBackupDisplay = CASE WHEN LastFull IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastFull, 120) END,
        DiffBackupDisplay = CASE WHEN LastDiff IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastDiff, 120) END,
        LogBackupDisplay  = CASE WHEN RecoveryModel = 'SIMPLE' THEN N'N/A'
                               WHEN LastLog IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastLog, 120) END,
        FullAgeDisplay = CASE WHEN LastFull IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(DAY, LastFull, @GetDate)) + N' Days' END,
        DiffAgeDisplay = CASE WHEN LastDiff IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(HOUR, LastDiff, @GetDate)) + N' Hrs' END,
        LogAgeDisplay  = CASE WHEN RecoveryModel = 'SIMPLE' THEN N'N/A'
                               WHEN LastLog IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(MINUTE, LastLog, @GetDate)) + N' Min' END,
        Status = CASE WHEN
                        (LastFull IS NULL OR DATEDIFF(DAY, LastFull, @GetDate) > @FullBackupDaysThreshold)
                     OR (LastDiff IS NULL OR DATEDIFF(HOUR, LastDiff, @GetDate) > @DiffBackupHoursThreshold)
                     OR (RecoveryModel <> 'SIMPLE' AND (LastLog IS NULL OR DATEDIFF(MINUTE, LastLog, @GetDate) > @LogBackupMinutesThreshold))
                   THEN N'CheckNow' ELSE N'Relax' END,
        StatusColor = CASE WHEN
                        (LastFull IS NULL OR DATEDIFF(DAY, LastFull, @GetDate) > @FullBackupDaysThreshold)
                     OR (LastDiff IS NULL OR DATEDIFF(HOUR, LastDiff, @GetDate) > @DiffBackupHoursThreshold)
                     OR (RecoveryModel <> 'SIMPLE' AND (LastLog IS NULL OR DATEDIFF(MINUTE, LastLog, @GetDate) > @LogBackupMinutesThreshold))
                   THEN N'#c0392b' ELSE N'#1e7e34' END
    FROM #BackupDetails b;

    DECLARE @BackupTotalCount INT, @BackupOverdueCount INT;
    SELECT @BackupTotalCount = COUNT(*), @BackupOverdueCount = SUM(CASE WHEN Status = 'CheckNow' THEN 1 ELSE 0 END)
    FROM #BackupDetails;

    ----------------------------------------------------------------------
    -- 3. DISK DETAILS
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DiskDetails') IS NOT NULL DROP TABLE #DiskDetails;

    SELECT DISTINCT
        vs.volume_mount_point                                                   AS DriveName,
        ISNULL(vs.logical_volume_name, N'') AS VolumeLabel,
        CAST(vs.total_bytes     / 1073741824.0 AS DECIMAL(12,2))                AS TotalGB,
        CAST((vs.total_bytes - vs.available_bytes) / 1073741824.0 AS DECIMAL(12,2)) AS UsedGB,
        CAST(vs.available_bytes / 1073741824.0 AS DECIMAL(12,2))                AS FreeGB,
        CAST(vs.available_bytes * 100.0 / NULLIF(vs.total_bytes,0) AS DECIMAL(5,2)) AS FreePct
    INTO #DiskDetails
    FROM sys.master_files AS mf
    CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.file_id) AS vs;

    ALTER TABLE #DiskDetails ADD Status NVARCHAR(20), StatusColor NVARCHAR(20);

    UPDATE #DiskDetails
    SET Status = CASE
                    WHEN FreePct < @LowDiskCriticalPct THEN N'CheckNow'
                    WHEN FreePct < @LowDiskWarningPct  THEN N'CheckNow'
                    ELSE N'Relax'
                 END,
        StatusColor = CASE
                    WHEN FreePct < @LowDiskCriticalPct THEN N'#c0392b'   -- Red
                    WHEN FreePct < @LowDiskWarningPct  THEN N'#d68910'   -- Yellow/Amber
                    ELSE N'#1e7e34'                                     -- Green
                 END;

    DECLARE @DiskWorstPct DECIMAL(5,2), @DiskAnyWarning BIT = 0, @DiskAnyCritical BIT = 0;
    SELECT @DiskWorstPct = MIN(FreePct) FROM #DiskDetails;
    IF EXISTS (SELECT 1 FROM #DiskDetails WHERE FreePct < @LowDiskCriticalPct) SET @DiskAnyCritical = 1;
    ELSE IF EXISTS (SELECT 1 FROM #DiskDetails WHERE FreePct < @LowDiskWarningPct) SET @DiskAnyWarning = 1;

    ----------------------------------------------------------------------
    -- 4. Build HTML row fragments
    ----------------------------------------------------------------------
    DECLARE @ServerDetailsRows   NVARCHAR(MAX) = N'';
    DECLARE @DatabaseDetailsRows NVARCHAR(MAX) = N'';
    DECLARE @BackupDetailsRows   NVARCHAR(MAX) = N'';
    DECLARE @DiskDetailsRows     NVARCHAR(MAX) = N'';

    -- Server Details (single row) - modified: removed GetDate, SQL Instance, Edition, Build Number
    SET @ServerDetailsRows =
        N'<tr>' +
            N'<td>' + @HostName + N'</td>' +
            N'<td>' + @IsClustered + N'</td>' +
            N'<td>' + @CurrentNode + N'</td>' +
            N'<td>' + CONVERT(NVARCHAR(20), @SQLRestart, 120) + N'</td>' +
            N'<td>' + CONVERT(NVARCHAR(10), @UptimeHrs) + N'</td>' +
            N'<td><span class="badge" style="background-color:' + @ServerStatusColor + N';">' + @ServerStatusText + N'</span></td>' +
        N'</tr>';

    -- Database Details (cursor over #DatabaseDetails)
    DECLARE @DbName NVARCHAR(128), @DbRecovery NVARCHAR(60), @DbState NVARCHAR(60), @DbSize DECIMAL(12,2), @DbStatus NVARCHAR(20), @DbColor NVARCHAR(20);
    DECLARE db_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel, DbState, SizeGB, Status, StatusColor FROM #DatabaseDetails ORDER BY DatabaseName;
    OPEN db_cur;
    FETCH NEXT FROM db_cur INTO @DbName, @DbRecovery, @DbState, @DbSize, @DbStatus, @DbColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @DatabaseDetailsRows = @DatabaseDetailsRows +
            N'<tr>' +
                N'<td>' + @DbName + N'</td>' +
                N'<td>' + @DbRecovery + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DbSize) + N'</td>' +
                N'<td>' + @DbState + N'</td>' +
                N'<td><span class="badge" style="background-color:' + @DbColor + N';">' + @DbStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM db_cur INTO @DbName, @DbRecovery, @DbState, @DbSize, @DbStatus, @DbColor;
    END
    CLOSE db_cur; DEALLOCATE db_cur;

    -- Backup Details
    DECLARE @BkName NVARCHAR(128), @BkRecovery NVARCHAR(60),
            @BkFullDt NVARCHAR(20), @BkFullAge NVARCHAR(20),
            @BkDiffDt NVARCHAR(20), @BkDiffAge NVARCHAR(20),
            @BkLogDt  NVARCHAR(20), @BkLogAge  NVARCHAR(20),
            @BkStatus NVARCHAR(20), @BkColor NVARCHAR(20);
    DECLARE bk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel,
               FullBackupDisplay, FullAgeDisplay,
               DiffBackupDisplay, DiffAgeDisplay,
               LogBackupDisplay,  LogAgeDisplay,
               Status, StatusColor
        FROM #BackupDetails ORDER BY DatabaseName;
    OPEN bk_cur;
    FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFullDt, @BkFullAge, @BkDiffDt, @BkDiffAge, @BkLogDt, @BkLogAge, @BkStatus, @BkColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @BackupDetailsRows = @BackupDetailsRows +
            N'<tr>' +
                N'<td>' + @BkName + N'</td>' +
                N'<td>' + @BkRecovery + N'</td>' +
                N'<td>' + @BkFullDt + N'</td>' +
                N'<td>' + @BkFullAge + N'</td>' +
                N'<td>' + @BkDiffDt + N'</td>' +
                N'<td>' + @BkDiffAge + N'</td>' +
                N'<td>' + @BkLogDt + N'</td>' +
                N'<td>' + @BkLogAge + N'</td>' +
                N'<td><span class="badge" style="background-color:' + @BkColor + N';">' + @BkStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFullDt, @BkFullAge, @BkDiffDt, @BkDiffAge, @BkLogDt, @BkLogAge, @BkStatus, @BkColor;
    END
    CLOSE bk_cur; DEALLOCATE bk_cur;

    -- Disk Details
    DECLARE @DkDrive NVARCHAR(10), @DkLabel NVARCHAR(128), @DkTotal DECIMAL(12,2), @DkUsed DECIMAL(12,2), @DkFree DECIMAL(12,2), @DkPct DECIMAL(5,2), @DkStatus NVARCHAR(20), @DkColor NVARCHAR(20);
    DECLARE dk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DriveName, VolumeLabel, TotalGB, UsedGB, FreeGB, FreePct, Status, StatusColor FROM #DiskDetails ORDER BY DriveName;
    OPEN dk_cur;
    FETCH NEXT FROM dk_cur INTO @DkDrive, @DkLabel, @DkTotal, @DkUsed, @DkFree, @DkPct, @DkStatus, @DkColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @DiskDetailsRows = @DiskDetailsRows +
            N'<tr>' +
                N'<td>' + @DkDrive + N'</td>' +
                N'<td>' + @DkLabel + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkTotal) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkUsed) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkFree) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkPct) + N'%</td>' +
                N'<td><span class="badge" style="background-color:' + @DkColor + N';">' + @DkStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM dk_cur INTO @DkDrive, @DkLabel, @DkTotal, @DkUsed, @DkFree, @DkPct, @DkStatus, @DkColor;
    END
    CLOSE dk_cur; DEALLOCATE dk_cur;

    ----------------------------------------------------------------------
    -- 5. Dashboard card values
    ----------------------------------------------------------------------
    DECLARE @CardServerStatus NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardServerColor  NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardServerBg     NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'#eafaf1' ELSE N'#fdecea' END;

    DECLARE @CardDbStatus NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardDbColor  NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardDbBg     NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'#eaf3fb' ELSE N'#fdecea' END;

    DECLARE @CardBackupStatus NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardBackupColor  NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardBackupBg     NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'#fdf6e3' ELSE N'#fdecea' END;

    DECLARE @CardDiskStatus NVARCHAR(20) = CASE WHEN @DiskAnyCritical = 1 OR @DiskAnyWarning = 1 THEN N'WARNING' ELSE N'HEALTHY' END;
    DECLARE @CardDiskColor  NVARCHAR(20) = CASE WHEN @DiskAnyCritical = 1 OR @DiskAnyWarning = 1 THEN N'#c0392b' ELSE N'#1e7e34' END;
    DECLARE @CardDiskBg     NVARCHAR(20) = N'#fdecea';

    ----------------------------------------------------------------------
    -- 6. Assemble final HTML
    ----------------------------------------------------------------------
    DECLARE @HTML NVARCHAR(MAX);

    SET @HTML = N'
<html>
<head>
<meta charset="UTF-8">
<style>
  body { font-family: Segoe UI, Arial, sans-serif; background-color:#ffffff; margin:0; padding:0; color:#2c3e50; }
  .container { max-width:1200px; margin:0 auto; padding:20px; background-color:#ffffff; }
  .header-box { background-color:#0b3d91; color:#ffffff; text-align:center; border-radius:10px; padding:20px 15px; margin-bottom:10px; }
  .header-box h1 { color:#ffffff; font-size:24px; margin:0 0 12px 0; }
  .header-box .meta { color:#ffffff; font-size:13px; }
  .header-box .meta span { margin:0 18px; }
  h2.section-title { color:#0b3d91; font-size:16px; border-bottom:2px solid #0b3d91; padding-bottom:5px; margin-top:30px; }
  table {
    border-collapse: separate;
    border-spacing: 0;
    width:100%;
    margin-top:10px;
    font-size:13px;
    border-radius:8px;
    overflow:hidden;
    border:1px solid #d0d7de;
  }
  th {
    background-color:#0b3d91;
    color:#ffffff;
    text-align:left;
    padding:8px 10px;
    border: none;
  }
  td {
    padding:8px 10px;
    border-bottom:1px solid #e0e0e0;
    border: none;
  }
  tr:nth-child(even) { background-color:#f9fafc; }
  .badge { color:#ffffff; padding:4px 10px; border-radius:12px; font-size:12px; font-weight:bold; display:inline-block; }
  .dashboard { width:100%; margin-top:10px; }
  .card { border-radius:8px; padding:15px; width:23%; display:inline-block; vertical-align:top; margin-right:1%; box-sizing:border-box; }
  .card-title { font-size:13px; font-weight:bold; color:#555; letter-spacing:0.5px; }
  .card-value { font-size:20px; font-weight:bold; margin:8px 0 4px 0; }
  .card-sub { font-size:12px; color:#777; }
  .disclaimer-box { margin-top:30px; padding:12px 15px; background-color:#fdf6e3; border-radius:8px; font-size:12px; color:#7a5c00; text-align:center; font-style:italic; }
  .signature-box { margin-top:15px; padding:15px; background-color:#eaf3fb; border-radius:8px; font-size:13px; color:#2c3e50; }
  .signature-box .sig-name { font-weight:bold; color:#0b3d91; }
</style>
</head>
<body>
<div class="container">
  <div class="header-box">
    <h1>SQL Server Health Check Report</h1>
    <div class="meta">
      <span><b>Server:</b> ' + @ServerName + N'</span>
      <span><b>Generated:</b> ' + CONVERT(NVARCHAR(20), @GetDate, 120) + N'</span>
      <span><b>SQL Version:</b> ' + @SQLVersionFull + N'</span>
    </div>
  </div>

  <h2 class="section-title">HEALTH SUMMARY DASHBOARD</h2>
  <div class="dashboard">
    <div class="card" style="background-color:' + @CardServerBg + N';">
      <div class="card-title">SERVER STATUS</div>
      <div class="card-value" style="color:' + @CardServerColor + N';">' + CASE WHEN @ServerIsHealthy = 1 THEN N'Running' ELSE N'Recently Restarted' END + N'</div>
      <div class="card-sub">Uptime: ' + CONVERT(NVARCHAR(10), @UptimeHrs) + N' Hrs</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardServerColor + N';">' + @CardServerStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardDbBg + N';">
      <div class="card-title">DATABASES</div>
      <div class="card-value" style="color:' + @CardDbColor + N';">' + CONVERT(NVARCHAR(10), @DbOnlineCount) + N' / ' + CONVERT(NVARCHAR(10), @DbTotalCount) + N' Online</div>
      <div class="card-sub">' + CONVERT(NVARCHAR(10), @DbTotalCount - @DbOnlineCount) + N' Offline / Suspect</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardDbColor + N';">' + @CardDbStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardBackupBg + N';">
      <div class="card-title">BACKUPS</div>
      <div class="card-value" style="color:' + @CardBackupColor + N';">' + CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'All Successful' ELSE N'Attention Needed' END + N'</div>
      <div class="card-sub">' + CONVERT(NVARCHAR(10), ISNULL(@BackupOverdueCount,0)) + N' Overdue</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardBackupColor + N';">' + @CardBackupStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardDiskBg + N';">
      <div class="card-title">DISK SPACE</div>
      <div class="card-value" style="color:' + @CardDiskColor + N';">' + ISNULL(CONVERT(NVARCHAR(10), @DiskWorstPct), N'N/A') + N'% Free</div>
      <div class="card-sub">Lowest Free Space</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardDiskColor + N';">' + @CardDiskStatus + N'</span></div>
    </div>
  </div>

  <h2 class="section-title">1. SERVER DETAILS</h2>
  <table>
    <tr>
      <th>Hostname</th><th>Is Cluster</th><th>Current Node</th><th>SQL Restart</th><th>Uptime (Hrs)</th><th>Status</th>
    </tr>
    ' + @ServerDetailsRows + N'
  </table>

  <h2 class="section-title">2. DATABASE DETAILS</h2>
  <table>
    <tr>
      <th>Database Name</th><th>Recovery Model</th><th>Database Size (GB)</th><th>State</th><th>Status</th>
    </tr>
    ' + @DatabaseDetailsRows + N'
  </table>

  <h2 class="section-title">3. DATABASE BACKUP DETAILS</h2>
  <table>
    <tr>
      <th>Database Name</th><th>Recovery Model</th><th>Full Backup</th><th>Full Backup Age</th><th>Diff Backup</th><th>Diff Backup Age</th><th>Log Backup</th><th>Log Backup Age</th><th>Status</th>
    </tr>
    ' + @BackupDetailsRows + N'
  </table>

  <h2 class="section-title">4. DISK DETAILS</h2>
  <table>
    <tr>
      <th>Drive</th><th>Volume Label</th><th>Total (GB)</th><th>Used (GB)</th><th>Free (GB)</th><th>Free %</th><th>Status</th>
    </tr>
    ' + @DiskDetailsRows + N'
  </table>

  <h2 class="section-title">5. DISCLAIMER</h2>
  <div class="disclaimer-box">This is an automated mail please do not respond to this mail</div>

  <h2 class="section-title">6. SIGNATURE</h2>
  <div class="signature-box">
    <div>Regards,</div>
    <div class="sig-name">Database Administration Team</div>
    <div>Automated SQL Server Health Monitoring</div>
  </div>
</div>
</body>
</html>';

    ----------------------------------------------------------------------
    -- 7. Send mail
    ----------------------------------------------------------------------
    EXEC msdb.dbo.sp_send_dbmail
        @profile_name = @ProfileName,
        @recipients   = @Recipients,
        @subject      = @Subject,
        @body         = @HTML,
        @body_format  = 'HTML';

    ----------------------------------------------------------------------
    -- 8. Cleanup
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DatabaseDetails') IS NOT NULL DROP TABLE #DatabaseDetails;
    IF OBJECT_ID('tempdb..#BackupDetails')   IS NOT NULL DROP TABLE #BackupDetails;
    IF OBJECT_ID('tempdb..#DiskDetails')     IS NOT NULL DROP TABLE #DiskDetails;

END
GO

/* ============================================================================
   EXAMPLE EXECUTION
   ============================================================================
   EXEC dbo.usp_SQLServerHealthCheckReport
        @ProfileName = N'DBMailProfile',
        @Recipients  = N'dba-team@company.com';

   -- Optional: schedule this as a daily/hourly SQL Agent Job on each server,
   -- adjusting thresholds via the parameters if a given server needs
   -- different SLAs (e.g. tighter log backup windows on OLTP boxes).
   ============================================================================ */


DeepSeek

/* ============================================================================
   SQL SERVER HEALTH CHECK REPORT
   ----------------------------------------------------------------------------
   Single self-contained stored procedure that:
     1. Collects Server / Database / Backup / Disk health information
     2. Renders it into an HTML email body (pure HTML + inline CSS only,
        no external CDN / image / font links)
     3. Sends it via Database Mail (msdb.dbo.sp_send_dbmail)

   DEPLOY:
     - Create this in a utility/admin database on EVERY SQL Server instance
       (e.g. [DBA_Admin]) or in msdb if you prefer.
     - Requires Database Mail to be configured with a working profile.
     - Schedule via SQL Agent Job -> "EXEC dbo.usp_SQLServerHealthCheckReport"

   ASSUMPTIONS (documented so you can tune them to your environment):
     - Backup section excludes tempdb and system databases (master/model/msdb)
       to mirror typical operational reporting. Remove the filter below if you
       want system databases included in the backup checks too.
     - Log-backup checks only apply to databases NOT in SIMPLE recovery model.
     - A backup type that has NEVER been taken is treated as overdue
       (NULL last-backup date = CheckNow), per the requested rule set.
     - Disk stats are pulled via sys.dm_os_volume_stats (works for any drive
       hosting at least one SQL Server data/log file).
   ============================================================================ */

CREATE OR ALTER PROCEDURE dbo.usp_SQLServerHealthCheckReport
    @ProfileName      NVARCHAR(128) = N'DBMailProfile',   -- Database Mail profile name
    @Recipients       NVARCHAR(MAX) = N'dba-team@company.com',
    @Subject          NVARCHAR(255) = NULL,
    @FullBackupDaysThreshold   INT = 7,     -- Full backup age threshold (days)
    @DiffBackupHoursThreshold  INT = 24,    -- Diff backup age threshold (hours)
    @LogBackupMinutesThreshold INT = 60,    -- Log backup age threshold (minutes)
    @LowDiskCriticalPct        DECIMAL(5,2) = 15.00,  -- < this % -> Red
    @LowDiskWarningPct         DECIMAL(5,2) = 25.00   -- < this % -> Yellow
AS
BEGIN
    SET NOCOUNT ON;

    ----------------------------------------------------------------------
    -- 0. Common variables
    ----------------------------------------------------------------------
    DECLARE @GetDate        DATETIME = GETDATE();
    DECLARE @HostName       NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('MachineName'));
    DECLARE @ServerName     NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ServerName'));
    -- The following variables are no longer used in the Server Details table but kept for potential future use.
    DECLARE @Edition        NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('Edition'));
    DECLARE @ProductVersion NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ProductVersion'));
    DECLARE @IsClusteredInt INT           = CONVERT(INT, SERVERPROPERTY('IsClustered'));
    DECLARE @IsClustered    NVARCHAR(10)  = CASE WHEN @IsClusteredInt = 1 THEN N'Yes' ELSE N'No' END;
    DECLARE @CurrentNode    NVARCHAR(128) = CONVERT(NVARCHAR(128), SERVERPROPERTY('ComputerNamePhysicalNetBIOS'));
    DECLARE @SQLRestart     DATETIME;
    DECLARE @UptimeHrs      INT;
    DECLARE @SQLVersionFull NVARCHAR(400) = @@VERSION;

    SELECT @SQLRestart = sqlserver_start_time FROM sys.dm_os_sys_info;
    SET @UptimeHrs = DATEDIFF(HOUR, @SQLRestart, @GetDate);
    SET @SQLVersionFull = LTRIM(RTRIM(LEFT(@SQLVersionFull, CHARINDEX(CHAR(10), @SQLVersionFull + CHAR(10)) - 1)));

    IF @Subject IS NULL
        SET @Subject = N'SQL Server Health Check Report - ' + @ServerName + N' - ' + CONVERT(NVARCHAR(20), @GetDate, 120);

    -- Server status (uptime rule)
    DECLARE @ServerStatusText  NVARCHAR(20);
    DECLARE @ServerStatusColor NVARCHAR(20);
    DECLARE @ServerIsHealthy   BIT;

    IF @UptimeHrs < 24
    BEGIN
        SET @ServerStatusText  = N'CheckNow';
        SET @ServerStatusColor = N'#c0392b';
        SET @ServerIsHealthy   = 0;
    END
    ELSE
    BEGIN
        SET @ServerStatusText  = N'Relax';
        SET @ServerStatusColor = N'#1e7e34';
        SET @ServerIsHealthy   = 1;
    END

    ----------------------------------------------------------------------
    -- 1. DATABASE DETAILS
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DatabaseDetails') IS NOT NULL DROP TABLE #DatabaseDetails;

    SELECT
        d.name                  AS DatabaseName,
        d.recovery_model_desc   AS RecoveryModel,
        d.compatibility_level   AS CompatLevel,
        CAST(SUM(CASE WHEN mf.type = 0 THEN mf.size ELSE 0 END) * 8.0 / 1024 / 1024 AS DECIMAL(12,2)) AS SizeGB,
        d.state_desc            AS DbState,
        CASE WHEN d.state_desc = 'ONLINE' THEN N'Relax' ELSE N'CheckNow' END AS Status,
        CASE WHEN d.state_desc = 'ONLINE' THEN N'#1e7e34' ELSE N'#c0392b' END AS StatusColor
    INTO #DatabaseDetails
    FROM sys.databases d
    LEFT JOIN sys.master_files mf ON mf.database_id = d.database_id
    GROUP BY d.name, d.recovery_model_desc, d.compatibility_level, d.state_desc
    ORDER BY d.name;

    DECLARE @DbTotalCount INT, @DbOnlineCount INT;
    SELECT @DbTotalCount = COUNT(*), @DbOnlineCount = SUM(CASE WHEN DbState = 'ONLINE' THEN 1 ELSE 0 END)
    FROM #DatabaseDetails;

    ----------------------------------------------------------------------
    -- 2. BACKUP DETAILS  (user databases only - excludes tempdb / system DBs)
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#BackupDetails') IS NOT NULL DROP TABLE #BackupDetails;

    ;WITH LastFull AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'D' GROUP BY database_name
    ),
    LastDiff AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'I' GROUP BY database_name
    ),
    LastLog AS (
        SELECT database_name, MAX(backup_finish_date) AS LastBackupDate
        FROM msdb.dbo.backupset WHERE type = 'L' GROUP BY database_name
    )
    SELECT
        d.name                AS DatabaseName,
        d.recovery_model_desc AS RecoveryModel,
        lf.LastBackupDate     AS LastFull,
        ld.LastBackupDate     AS LastDiff,
        ll.LastBackupDate     AS LastLog
    INTO #BackupDetails
    FROM sys.databases d
    LEFT JOIN LastFull lf ON lf.database_name = d.name
    LEFT JOIN LastDiff ld ON ld.database_name = d.name
    LEFT JOIN LastLog  ll ON ll.database_name = d.name
    WHERE d.database_id > 4          -- excludes master/model/msdb/tempdb
      AND d.name <> N'tempdb'
      AND d.state_desc = 'ONLINE';

    -- Add computed age / display / status columns
    ALTER TABLE #BackupDetails ADD
        FullBackupDisplay NVARCHAR(20),
        DiffBackupDisplay NVARCHAR(20),
        LogBackupDisplay  NVARCHAR(20),
        FullAgeDisplay  NVARCHAR(20),
        DiffAgeDisplay  NVARCHAR(20),
        LogAgeDisplay   NVARCHAR(20),
        Status          NVARCHAR(20),
        StatusColor     NVARCHAR(20);

    UPDATE b
    SET
        FullBackupDisplay = CASE WHEN LastFull IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastFull, 120) END,
        DiffBackupDisplay = CASE WHEN LastDiff IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastDiff, 120) END,
        LogBackupDisplay  = CASE WHEN RecoveryModel = 'SIMPLE' THEN N'N/A'
                               WHEN LastLog IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(20), LastLog, 120) END,
        FullAgeDisplay = CASE WHEN LastFull IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(DAY, LastFull, @GetDate)) + N' Days' END,
        DiffAgeDisplay = CASE WHEN LastDiff IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(HOUR, LastDiff, @GetDate)) + N' Hrs' END,
        LogAgeDisplay  = CASE WHEN RecoveryModel = 'SIMPLE' THEN N'N/A'
                               WHEN LastLog IS NULL THEN N'Never'
                               ELSE CONVERT(NVARCHAR(10), DATEDIFF(MINUTE, LastLog, @GetDate)) + N' Min' END,
        Status = CASE WHEN
                        (LastFull IS NULL OR DATEDIFF(DAY, LastFull, @GetDate) > @FullBackupDaysThreshold)
                     OR (LastDiff IS NULL OR DATEDIFF(HOUR, LastDiff, @GetDate) > @DiffBackupHoursThreshold)
                     OR (RecoveryModel <> 'SIMPLE' AND (LastLog IS NULL OR DATEDIFF(MINUTE, LastLog, @GetDate) > @LogBackupMinutesThreshold))
                   THEN N'CheckNow' ELSE N'Relax' END,
        StatusColor = CASE WHEN
                        (LastFull IS NULL OR DATEDIFF(DAY, LastFull, @GetDate) > @FullBackupDaysThreshold)
                     OR (LastDiff IS NULL OR DATEDIFF(HOUR, LastDiff, @GetDate) > @DiffBackupHoursThreshold)
                     OR (RecoveryModel <> 'SIMPLE' AND (LastLog IS NULL OR DATEDIFF(MINUTE, LastLog, @GetDate) > @LogBackupMinutesThreshold))
                   THEN N'#c0392b' ELSE N'#1e7e34' END
    FROM #BackupDetails b;

    DECLARE @BackupTotalCount INT, @BackupOverdueCount INT;
    SELECT @BackupTotalCount = COUNT(*), @BackupOverdueCount = SUM(CASE WHEN Status = 'CheckNow' THEN 1 ELSE 0 END)
    FROM #BackupDetails;

    ----------------------------------------------------------------------
    -- 3. DISK DETAILS
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DiskDetails') IS NOT NULL DROP TABLE #DiskDetails;

    SELECT DISTINCT
        vs.volume_mount_point                                                   AS DriveName,
        ISNULL(vs.logical_volume_name, N'') AS VolumeLabel,
        CAST(vs.total_bytes     / 1073741824.0 AS DECIMAL(12,2))                AS TotalGB,
        CAST((vs.total_bytes - vs.available_bytes) / 1073741824.0 AS DECIMAL(12,2)) AS UsedGB,
        CAST(vs.available_bytes / 1073741824.0 AS DECIMAL(12,2))                AS FreeGB,
        CAST(vs.available_bytes * 100.0 / NULLIF(vs.total_bytes,0) AS DECIMAL(5,2)) AS FreePct
    INTO #DiskDetails
    FROM sys.master_files AS mf
    CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.file_id) AS vs;

    ALTER TABLE #DiskDetails ADD Status NVARCHAR(20), StatusColor NVARCHAR(20);

    UPDATE #DiskDetails
    SET Status = CASE
                    WHEN FreePct < @LowDiskCriticalPct THEN N'CheckNow'
                    WHEN FreePct < @LowDiskWarningPct  THEN N'CheckNow'
                    ELSE N'Relax'
                 END,
        StatusColor = CASE
                    WHEN FreePct < @LowDiskCriticalPct THEN N'#c0392b'   -- Red
                    WHEN FreePct < @LowDiskWarningPct  THEN N'#d68910'   -- Yellow/Amber
                    ELSE N'#1e7e34'                                     -- Green
                 END;

    DECLARE @DiskWorstPct DECIMAL(5,2), @DiskAnyWarning BIT = 0, @DiskAnyCritical BIT = 0;
    SELECT @DiskWorstPct = MIN(FreePct) FROM #DiskDetails;
    IF EXISTS (SELECT 1 FROM #DiskDetails WHERE FreePct < @LowDiskCriticalPct) SET @DiskAnyCritical = 1;
    ELSE IF EXISTS (SELECT 1 FROM #DiskDetails WHERE FreePct < @LowDiskWarningPct) SET @DiskAnyWarning = 1;

    ----------------------------------------------------------------------
    -- 4. Build HTML row fragments
    ----------------------------------------------------------------------
    DECLARE @ServerDetailsRows   NVARCHAR(MAX) = N'';
    DECLARE @DatabaseDetailsRows NVARCHAR(MAX) = N'';
    DECLARE @BackupDetailsRows   NVARCHAR(MAX) = N'';
    DECLARE @DiskDetailsRows     NVARCHAR(MAX) = N'';

    -- Server Details (single row) - modified: removed GetDate, SQL Instance, Edition, Build Number
    SET @ServerDetailsRows =
        N'<tr>' +
            N'<td>' + @HostName + N'</td>' +
            N'<td>' + @IsClustered + N'</td>' +
            N'<td>' + @CurrentNode + N'</td>' +
            N'<td>' + CONVERT(NVARCHAR(20), @SQLRestart, 120) + N'</td>' +
            N'<td>' + CONVERT(NVARCHAR(10), @UptimeHrs) + N'</td>' +
            N'<td><span class="badge" style="background-color:' + @ServerStatusColor + N';">' + @ServerStatusText + N'</span></td>' +
        N'</tr>';

    -- Database Details (cursor over #DatabaseDetails)
    DECLARE @DbName NVARCHAR(128), @DbRecovery NVARCHAR(60), @DbState NVARCHAR(60), @DbSize DECIMAL(12,2), @DbStatus NVARCHAR(20), @DbColor NVARCHAR(20);
    DECLARE db_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel, DbState, SizeGB, Status, StatusColor FROM #DatabaseDetails ORDER BY DatabaseName;
    OPEN db_cur;
    FETCH NEXT FROM db_cur INTO @DbName, @DbRecovery, @DbState, @DbSize, @DbStatus, @DbColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @DatabaseDetailsRows = @DatabaseDetailsRows +
            N'<tr>' +
                N'<td>' + @DbName + N'</td>' +
                N'<td>' + @DbRecovery + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DbSize) + N'</td>' +
                N'<td>' + @DbState + N'</td>' +
                N'<td><span class="badge" style="background-color:' + @DbColor + N';">' + @DbStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM db_cur INTO @DbName, @DbRecovery, @DbState, @DbSize, @DbStatus, @DbColor;
    END
    CLOSE db_cur; DEALLOCATE db_cur;

    -- Backup Details
    DECLARE @BkName NVARCHAR(128), @BkRecovery NVARCHAR(60),
            @BkFullDt NVARCHAR(20), @BkFullAge NVARCHAR(20),
            @BkDiffDt NVARCHAR(20), @BkDiffAge NVARCHAR(20),
            @BkLogDt  NVARCHAR(20), @BkLogAge  NVARCHAR(20),
            @BkStatus NVARCHAR(20), @BkColor NVARCHAR(20);
    DECLARE bk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel,
               FullBackupDisplay, FullAgeDisplay,
               DiffBackupDisplay, DiffAgeDisplay,
               LogBackupDisplay,  LogAgeDisplay,
               Status, StatusColor
        FROM #BackupDetails ORDER BY DatabaseName;
    OPEN bk_cur;
    FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFullDt, @BkFullAge, @BkDiffDt, @BkDiffAge, @BkLogDt, @BkLogAge, @BkStatus, @BkColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @BackupDetailsRows = @BackupDetailsRows +
            N'<tr>' +
                N'<td>' + @BkName + N'</td>' +
                N'<td>' + @BkRecovery + N'</td>' +
                N'<td>' + @BkFullDt + N'</td>' +
                N'<td>' + @BkFullAge + N'</td>' +
                N'<td>' + @BkDiffDt + N'</td>' +
                N'<td>' + @BkDiffAge + N'</td>' +
                N'<td>' + @BkLogDt + N'</td>' +
                N'<td>' + @BkLogAge + N'</td>' +
                N'<td><span class="badge" style="background-color:' + @BkColor + N';">' + @BkStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFullDt, @BkFullAge, @BkDiffDt, @BkDiffAge, @BkLogDt, @BkLogAge, @BkStatus, @BkColor;
    END
    CLOSE bk_cur; DEALLOCATE bk_cur;

    -- Disk Details
    DECLARE @DkDrive NVARCHAR(10), @DkLabel NVARCHAR(128), @DkTotal DECIMAL(12,2), @DkUsed DECIMAL(12,2), @DkFree DECIMAL(12,2), @DkPct DECIMAL(5,2), @DkStatus NVARCHAR(20), @DkColor NVARCHAR(20);
    DECLARE dk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DriveName, VolumeLabel, TotalGB, UsedGB, FreeGB, FreePct, Status, StatusColor FROM #DiskDetails ORDER BY DriveName;
    OPEN dk_cur;
    FETCH NEXT FROM dk_cur INTO @DkDrive, @DkLabel, @DkTotal, @DkUsed, @DkFree, @DkPct, @DkStatus, @DkColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @DiskDetailsRows = @DiskDetailsRows +
            N'<tr>' +
                N'<td>' + @DkDrive + N'</td>' +
                N'<td>' + @DkLabel + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkTotal) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkUsed) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkFree) + N'</td>' +
                N'<td>' + CONVERT(NVARCHAR(20), @DkPct) + N'%</td>' +
                N'<td><span class="badge" style="background-color:' + @DkColor + N';">' + @DkStatus + N'</span></td>' +
            N'</tr>';
        FETCH NEXT FROM dk_cur INTO @DkDrive, @DkLabel, @DkTotal, @DkUsed, @DkFree, @DkPct, @DkStatus, @DkColor;
    END
    CLOSE dk_cur; DEALLOCATE dk_cur;

    ----------------------------------------------------------------------
    -- 5. Dashboard card values
    ----------------------------------------------------------------------
    DECLARE @CardServerStatus NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardServerColor  NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardServerBg     NVARCHAR(20)  = CASE WHEN @ServerIsHealthy = 1 THEN N'#eafaf1' ELSE N'#fdecea' END;

    DECLARE @CardDbStatus NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardDbColor  NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardDbBg     NVARCHAR(20) = CASE WHEN @DbOnlineCount = @DbTotalCount THEN N'#eaf3fb' ELSE N'#fdecea' END;

    DECLARE @CardBackupStatus NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'HEALTHY' ELSE N'WARNING' END;
    DECLARE @CardBackupColor  NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'#1e7e34' ELSE N'#c0392b' END;
    DECLARE @CardBackupBg     NVARCHAR(20) = CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'#fdf6e3' ELSE N'#fdecea' END;

    DECLARE @CardDiskStatus NVARCHAR(20) = CASE WHEN @DiskAnyCritical = 1 OR @DiskAnyWarning = 1 THEN N'WARNING' ELSE N'HEALTHY' END;
    DECLARE @CardDiskColor  NVARCHAR(20) = CASE WHEN @DiskAnyCritical = 1 OR @DiskAnyWarning = 1 THEN N'#c0392b' ELSE N'#1e7e34' END;
    DECLARE @CardDiskBg     NVARCHAR(20) = N'#fdecea';

    ----------------------------------------------------------------------
    -- 6. Assemble final HTML
    ----------------------------------------------------------------------
    DECLARE @HTML NVARCHAR(MAX);

    SET @HTML = N'
<html>
<head>
<meta charset="UTF-8">
<style>
  body { font-family: Segoe UI, Arial, sans-serif; background-color:#ffffff; margin:0; padding:0; color:#2c3e50; }
  .container { max-width:1200px; margin:0 auto; padding:20px; background-color:#ffffff; }
  .header-box { background-color:#0b3d91; color:#ffffff; text-align:center; border-radius:10px; padding:20px 15px; margin-bottom:10px; }
  .header-box h1 { color:#ffffff; font-size:24px; margin:0 0 12px 0; }
  .header-box .meta { color:#ffffff; font-size:13px; }
  .header-box .meta span { margin:0 18px; }
  h2.section-title { color:#0b3d91; font-size:16px; border-bottom:2px solid #0b3d91; padding-bottom:5px; margin-top:30px; }
  table { border-collapse: collapse; width:100%; margin-top:10px; font-size:13px; }
  th { background-color:#0b3d91; color:#ffffff; text-align:left; padding:8px 10px; }
  td { padding:8px 10px; border-bottom:1px solid #e0e0e0; }
  tr:nth-child(even) { background-color:#f9fafc; }
  .badge { color:#ffffff; padding:4px 10px; border-radius:12px; font-size:12px; font-weight:bold; display:inline-block; }
  .dashboard { width:100%; margin-top:10px; }
  .card { border-radius:8px; padding:15px; width:23%; display:inline-block; vertical-align:top; margin-right:1%; box-sizing:border-box; }
  .card-title { font-size:13px; font-weight:bold; color:#555; letter-spacing:0.5px; }
  .card-value { font-size:20px; font-weight:bold; margin:8px 0 4px 0; }
  .card-sub { font-size:12px; color:#777; }
  .disclaimer-box { margin-top:30px; padding:12px 15px; background-color:#fdf6e3; border-radius:8px; font-size:12px; color:#7a5c00; text-align:center; font-style:italic; }
  .signature-box { margin-top:15px; padding:15px; background-color:#eaf3fb; border-radius:8px; font-size:13px; color:#2c3e50; }
  .signature-box .sig-name { font-weight:bold; color:#0b3d91; }
</style>
</head>
<body>
<div class="container">
  <div class="header-box">
    <h1>SQL Server Health Check Report</h1>
    <div class="meta">
      <span><b>Server:</b> ' + @ServerName + N'</span>
      <span><b>Generated:</b> ' + CONVERT(NVARCHAR(20), @GetDate, 120) + N'</span>
      <span><b>SQL Version:</b> ' + @SQLVersionFull + N'</span>
    </div>
  </div>

  <h2 class="section-title">HEALTH SUMMARY DASHBOARD</h2>
  <div class="dashboard">
    <div class="card" style="background-color:' + @CardServerBg + N';">
      <div class="card-title">SERVER STATUS</div>
      <div class="card-value" style="color:' + @CardServerColor + N';">' + CASE WHEN @ServerIsHealthy = 1 THEN N'Running' ELSE N'Recently Restarted' END + N'</div>
      <div class="card-sub">Uptime: ' + CONVERT(NVARCHAR(10), @UptimeHrs) + N' Hrs</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardServerColor + N';">' + @CardServerStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardDbBg + N';">
      <div class="card-title">DATABASES</div>
      <div class="card-value" style="color:' + @CardDbColor + N';">' + CONVERT(NVARCHAR(10), @DbOnlineCount) + N' / ' + CONVERT(NVARCHAR(10), @DbTotalCount) + N' Online</div>
      <div class="card-sub">' + CONVERT(NVARCHAR(10), @DbTotalCount - @DbOnlineCount) + N' Offline / Suspect</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardDbColor + N';">' + @CardDbStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardBackupBg + N';">
      <div class="card-title">BACKUPS</div>
      <div class="card-value" style="color:' + @CardBackupColor + N';">' + CASE WHEN ISNULL(@BackupOverdueCount,0) = 0 THEN N'All Successful' ELSE N'Attention Needed' END + N'</div>
      <div class="card-sub">' + CONVERT(NVARCHAR(10), ISNULL(@BackupOverdueCount,0)) + N' Overdue</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardBackupColor + N';">' + @CardBackupStatus + N'</span></div>
    </div>
    <div class="card" style="background-color:' + @CardDiskBg + N';">
      <div class="card-title">DISK SPACE</div>
      <div class="card-value" style="color:' + @CardDiskColor + N';">' + ISNULL(CONVERT(NVARCHAR(10), @DiskWorstPct), N'N/A') + N'% Free</div>
      <div class="card-sub">Lowest Free Space</div>
      <div style="margin-top:8px;"><span class="badge" style="background-color:' + @CardDiskColor + N';">' + @CardDiskStatus + N'</span></div>
    </div>
  </div>

  <h2 class="section-title">1. SERVER DETAILS</h2>
  <table>
    <tr>
      <th>Hostname</th><th>Is Cluster</th><th>Current Node</th><th>SQL Restart</th><th>Uptime (Hrs)</th><th>Status</th>
    </tr>
    ' + @ServerDetailsRows + N'
  </table>

  <h2 class="section-title">2. DATABASE DETAILS</h2>
  <table>
    <tr>
      <th>Database Name</th><th>Recovery Model</th><th>Database Size (GB)</th><th>State</th><th>Status</th>
    </tr>
    ' + @DatabaseDetailsRows + N'
  </table>

  <h2 class="section-title">3. DATABASE BACKUP DETAILS</h2>
  <table>
    <tr>
      <th>Database Name</th><th>Recovery Model</th><th>Full Backup</th><th>Full Backup Age</th><th>Diff Backup</th><th>Diff Backup Age</th><th>Log Backup</th><th>Log Backup Age</th><th>Status</th>
    </tr>
    ' + @BackupDetailsRows + N'
  </table>

  <h2 class="section-title">4. DISK DETAILS</h2>
  <table>
    <tr>
      <th>Drive</th><th>Volume Label</th><th>Total (GB)</th><th>Used (GB)</th><th>Free (GB)</th><th>Free %</th><th>Status</th>
    </tr>
    ' + @DiskDetailsRows + N'
  </table>

  <h2 class="section-title">5. DISCLAIMER</h2>
  <div class="disclaimer-box">This is an automated mail please do not respond to this mail</div>

  <h2 class="section-title">6. SIGNATURE</h2>
  <div class="signature-box">
    <div>Regards,</div>
    <div class="sig-name">Database Administration Team</div>
    <div>Automated SQL Server Health Monitoring</div>
  </div>
</div>
</body>
</html>';

    ----------------------------------------------------------------------
    -- 7. Send mail
    ----------------------------------------------------------------------
    EXEC msdb.dbo.sp_send_dbmail
        @profile_name = @ProfileName,
        @recipients   = @Recipients,
        @subject      = @Subject,
        @body         = @HTML,
        @body_format  = 'HTML';

    ----------------------------------------------------------------------
    -- 8. Cleanup
    ----------------------------------------------------------------------
    IF OBJECT_ID('tempdb..#DatabaseDetails') IS NOT NULL DROP TABLE #DatabaseDetails;
    IF OBJECT_ID('tempdb..#BackupDetails')   IS NOT NULL DROP TABLE #BackupDetails;
    IF OBJECT_ID('tempdb..#DiskDetails')     IS NOT NULL DROP TABLE #DiskDetails;

END
GO

/* ============================================================================
   EXAMPLE EXECUTION
   ============================================================================
   EXEC dbo.usp_SQLServerHealthCheckReport
        @ProfileName = N'DBMailProfile',
        @Recipients  = N'dba-team@company.com';

   -- Optional: schedule this as a daily/hourly SQL Agent Job on each server,
   -- adjusting thresholds via the parameters if a given server needs
   -- different SLAs (e.g. tighter log backup windows on OLTP boxes).
   ============================================================================ */

Author Name

Contact Form

Name

Email *

Message *

Powered by Blogger.