Latest Post

 Test this one
/*

/* ============================================================================
   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
    @SignatureName             NVARCHAR(200) = N'Database Administration Team',
    @SignatureTitle            NVARCHAR(200) = N'SQL Server Monitoring & Support',
    @SignatureContact          NVARCHAR(200) = N'dba-team@company.com'
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'));
    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 date/age / display / status columns
    ALTER TABLE #BackupDetails ADD
        FullBackupDT    NVARCHAR(20),
        DiffBackupDT    NVARCHAR(20),
        LogBackupDT     NVARCHAR(20),
        FullAgeDisplay  NVARCHAR(20),
        DiffAgeDisplay  NVARCHAR(20),
        LogAgeDisplay   NVARCHAR(20),
        Status          NVARCHAR(20),
        StatusColor     NVARCHAR(20);

    UPDATE b
    SET
        FullBackupDT   = CASE WHEN LastFull IS NULL THEN N'Never' ELSE CONVERT(NVARCHAR(20), LastFull, 120) END,
        DiffBackupDT   = CASE WHEN LastDiff IS NULL THEN N'Never' ELSE CONVERT(NVARCHAR(20), LastDiff, 120) END,
        LogBackupDT    = 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)
    SET @ServerDetailsRows =
        N'<tr>' +
            N'<td>' + CONVERT(NVARCHAR(20), @GetDate, 120) + N'</td>' +
            N'<td>' + @HostName + N'</td>' +
            N'<td>' + @ServerName + N'</td>' +
            N'<td>' + @Edition + N'</td>' +
            N'<td>' + @ProductVersion + 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), @BkFull NVARCHAR(20),
            @BkDiffDT NVARCHAR(20), @BkDiff NVARCHAR(20),
            @BkLogDT  NVARCHAR(20), @BkLog NVARCHAR(20),
            @BkStatus NVARCHAR(20), @BkColor NVARCHAR(20);
    DECLARE bk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel, FullBackupDT, FullAgeDisplay, DiffBackupDT, DiffAgeDisplay, LogBackupDT, LogAgeDisplay, Status, StatusColor
        FROM #BackupDetails ORDER BY DatabaseName;
    OPEN bk_cur;
    FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFullDT, @BkFull, @BkDiffDT, @BkDiff, @BkLogDT, @BkLog, @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>' + @BkFull + N'</td>' +
                N'<td>' + @BkDiffDT + N'</td>' +
                N'<td>' + @BkDiff + N'</td>' +
                N'<td>' + @BkLogDT + N'</td>' +
                N'<td>' + @BkLog + 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, @BkFull, @BkDiffDT, @BkDiff, @BkLogDT, @BkLog, @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:#f4f6f8; margin:0; padding:0; color:#2c3e50; }
  .container { max-width:1200px; margin:0 auto; padding:20px; }
  .report-header { background-color:#0b3d91; color:#ffffff; border-radius:10px; padding:22px 25px; margin-bottom:10px; }
  .report-header h1 { color:#ffffff; font-size:26px; margin:0 0 10px 0; }
  .meta { color:#eaf1fb; font-size:13px; }
  .meta span { margin-right:25px; }
  h2.section-title { color:#0b3d91; font-size:16px; border-bottom:2px solid #0b3d91; padding-bottom:5px; margin-top:30px; }
  .table-wrap { border-radius:10px; overflow:hidden; border:1px solid #e0e0e0; margin-top:10px; }
  table { border-collapse: collapse; width:100%; 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; }
  tr:last-child td { border-bottom:none; }
  .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:14px; 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; }
  .footer { margin-top:30px; padding:12px; background-color:#eaf3fb; border-radius:10px; font-size:12px; color:#555; }
  .signature { margin-top:25px; padding:15px 20px; background-color:#ffffff; border:1px solid #e0e0e0; border-radius:10px; font-size:13px; color:#2c3e50; }
  .signature .sig-name { font-weight:bold; color:#0b3d91; }
</style>
</head>
<body>
<div class="container">
  <div class="report-header">
    <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>
  <div class="table-wrap">
  <table>
    <tr>
      <th>GetDate</th><th>Hostname</th><th>SQL Instance</th><th>Edition</th><th>Build Number</th>
      <th>Is Cluster</th><th>Current Node</th><th>SQL Restart</th><th>Uptime (Hrs)</th><th>Status</th>
    </tr>
    ' + @ServerDetailsRows + N'
  </table>
  </div>

  <h2 class="section-title">2. DATABASE DETAILS</h2>
  <div class="table-wrap">
  <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>
  </div>

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

  <h2 class="section-title">4. DISK DETAILS</h2>
  <div class="table-wrap">
  <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>
  </div>

  <div class="signature">
    Regards,<br>
    <span class="sig-name">' + @SignatureName + N'</span><br>
    ' + @SignatureTitle + N'<br>
    ' + @SignatureContact + N'
  </div>

  <div class="footer">This is an automated email generated by usp_SQLServerHealthCheckReport. Please do not reply.</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).
   ============================================================================ */


*/

 This is the latest normal one

/*

/* ============================================================================
   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'));
    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
        FullAgeDisplay  NVARCHAR(20),
        DiffAgeDisplay  NVARCHAR(20),
        LogAgeDisplay   NVARCHAR(20),
        Status          NVARCHAR(20),
        StatusColor     NVARCHAR(20);

    UPDATE b
    SET
        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)
    SET @ServerDetailsRows =
        N'<tr>' +
            N'<td>' + CONVERT(NVARCHAR(20), @GetDate, 120) + N'</td>' +
            N'<td>' + @HostName + N'</td>' +
            N'<td>' + @ServerName + N'</td>' +
            N'<td>' + @Edition + N'</td>' +
            N'<td>' + @ProductVersion + 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), @BkFull NVARCHAR(20), @BkDiff NVARCHAR(20), @BkLog NVARCHAR(20), @BkStatus NVARCHAR(20), @BkColor NVARCHAR(20);
    DECLARE bk_cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT DatabaseName, RecoveryModel, FullAgeDisplay, DiffAgeDisplay, LogAgeDisplay, Status, StatusColor FROM #BackupDetails ORDER BY DatabaseName;
    OPEN bk_cur;
    FETCH NEXT FROM bk_cur INTO @BkName, @BkRecovery, @BkFull, @BkDiff, @BkLog, @BkStatus, @BkColor;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @BackupDetailsRows = @BackupDetailsRows +
            N'<tr>' +
                N'<td>' + @BkName + N'</td>' +
                N'<td>' + @BkRecovery + N'</td>' +
                N'<td>' + @BkFull + N'</td>' +
                N'<td>' + @BkDiff + N'</td>' +
                N'<td>' + @BkLog + 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, @BkFull, @BkDiff, @BkLog, @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:#f4f6f8; margin:0; padding:0; color:#2c3e50; }
  .container { max-width:1200px; margin:0 auto; padding:20px; }
  .header-bar { background-color:#0b1f3a; height:8px; width:100%; }
  h1 { color:#0b3d91; font-size:26px; margin:20px 0 5px 0; }
  .meta { color:#555; font-size:13px; margin-bottom:20px; }
  .meta span { margin-right:25px; }
  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; }
  .footer { margin-top:30px; padding:12px; background-color:#eaf3fb; border-radius:6px; font-size:12px; color:#555; }
</style>
</head>
<body>
<div class="header-bar"></div>
<div class="container">
  <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>

  <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>GetDate</th><th>Hostname</th><th>SQL Instance</th><th>Edition</th><th>Build Number</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 Age</th><th>Diff Backup Age</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>

  <div class="footer">This is an automated email generated by usp_SQLServerHealthCheckReport. Please do not reply.</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).
   ============================================================================ */

*/

Here it is 

/*

USE master;
GO
IF OBJECT_ID('dbo.sp_Send_SQLHealthCheck_Report', 'P') IS NOT NULL
DROP PROCEDURE dbo.sp_Send_SQLHealthCheck_Report;
GO
CREATE PROCEDURE dbo.sp_Send_SQLHealthCheck_Report
@MailProfile NVARCHAR(128) = 'your_mail_profile', -- Replace with your DB Mail Profile
@Recipients  NVARCHAR(MAX) = 'dba-team@company.com' -- Replace with target email address
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

--------------------------------------------------------------------------------
-- 1. SERVER & DATABASE METADATA
--------------------------------------------------------------------------------
DECLARE @GetDate DATETIME = GETDATE();
DECLARE @Subject NVARCHAR(256) = 'SQL Server Health Check Report - ' + @@SERVERNAME;
DECLARE @Hostname NVARCHAR(128) = CAST(SERVERPROPERTY('MachineName') AS NVARCHAR(128));
DECLARE @InstanceName NVARCHAR(128) = @@SERVERNAME;
DECLARE @Edition NVARCHAR(128) = CAST(SERVERPROPERTY('Edition') AS NVARCHAR(128));
DECLARE @BuildNumber NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
DECLARE @IsCluster INT = CAST(SERVERPROPERTY('IsClustered') AS INT);
DECLARE @CurrentNode NVARCHAR(128) = ISNULL(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS NVARCHAR(128)), 'N/A');
DECLARE @SQLRestart DATETIME;
DECLARE @UptimeHours INT;

SELECT @SQLRestart = sqlserver_start_time FROM sys.dm_os_sys_info;
SET @UptimeHours = DATEDIFF(HOUR, @SQLRestart, @GetDate);

--------------------------------------------------------------------------------
-- 2. TEMP TABLES FOR DATA COLLECTION
--------------------------------------------------------------------------------
IF OBJECT_ID('tempdb..#DatabaseInfo') IS NOT NULL DROP TABLE #DatabaseInfo;
IF OBJECT_ID('tempdb..#BackupInfo') IS NOT NULL DROP TABLE #BackupInfo;
IF OBJECT_ID('tempdb..#DiskInfo') IS NOT NULL DROP TABLE #DiskInfo;

-- Database State & Size
SELECT 
    d.name,
    d.recovery_model_desc,
    d.compatibility_level,
    CAST(SUM(mf.size * 8.0 / 1024 / 1024) AS DECIMAL(10,2)) AS SizeGB,
    d.state_desc,
    d.database_id
INTO #DatabaseInfo
FROM sys.databases d
LEFT JOIN sys.master_files mf ON d.database_id = mf.database_id
WHERE d.name NOT IN ('tempdb')
GROUP BY d.name, d.recovery_model_desc, d.compatibility_level, d.state_desc, d.database_id;

-- Backup Information
SELECT 
    d.name AS DBName,
    d.recovery_model_desc,
    MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS LastFull,
    DATEDIFF(DAY, MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END), GETDATE()) AS FullAgeDays,
    MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS LastDiff,
    DATEDIFF(HOUR, MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END), GETDATE()) AS DiffAgeHours,
    MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS LastLog,
    DATEDIFF(MINUTE, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END), GETDATE()) AS LogAgeMinutes
INTO #BackupInfo
FROM #DatabaseInfo d
LEFT JOIN msdb.dbo.backupset b ON d.name = b.database_name
GROUP BY d.name, d.recovery_model_desc;

-- Disk Space
SELECT DISTINCT
    volume_mount_point AS VolumeName,
    CAST(total_bytes / 1073741824.0 AS DECIMAL(10,2)) AS TotalGB,
    CAST((total_bytes - available_bytes) / 1073741824.0 AS DECIMAL(10,2)) AS UsedGB,
    CAST(available_bytes / 1073741824.0 AS DECIMAL(10,2)) AS FreeGB,
    CAST((available_bytes * 100.0 / total_bytes) AS DECIMAL(5,2)) AS FreePct
INTO #DiskInfo
FROM sys.master_files AS f
CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id);

--------------------------------------------------------------------------------
-- 3. EXECUTIVE SUMMARY KPIs
--------------------------------------------------------------------------------
DECLARE @TotalDBs INT = (SELECT COUNT(*) FROM #DatabaseInfo);
DECLARE @OnlineDBs INT = (SELECT COUNT(*) FROM #DatabaseInfo WHERE state_desc = 'ONLINE');
DECLARE @OfflineCount INT = @TotalDBs - @OnlineDBs;
DECLARE @BackupAlertCount INT = 0;

SELECT @BackupAlertCount = COUNT(*) 
FROM #BackupInfo 
WHERE FullAgeDays > 7 OR FullAgeDays IS NULL 
   OR DiffAgeHours > 24 
   OR (recovery_model_desc <> 'SIMPLE' AND (LogAgeMinutes > 60 OR LogAgeMinutes IS NULL));

DECLARE @LowestDiskDrive NVARCHAR(128) = '';
DECLARE @LowestDiskPct DECIMAL(5,2) = 100.0;

SELECT TOP 1 @LowestDiskDrive = VolumeName, @LowestDiskPct = FreePct 
FROM #DiskInfo ORDER BY FreePct ASC;

--------------------------------------------------------------------------------
-- 4. HTML CSS STYLES & BUILD STRINGS
--------------------------------------------------------------------------------
-- Note: Removed Flexbox and external Font Awesome link for Outlook compatibility
DECLARE @CSS NVARCHAR(MAX) = N'
<style>
    body { font-family: "Segoe UI", Arial, sans-serif; background-color: #f8fafc; margin: 0; padding: 20px; }
    .container { max-width: 1150px; margin: auto; background-color: #ffffff; border-radius: 12px; border: 1px solid #e2e8f0; padding: 25px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); }
    .header { background-color: #1e3a8a; border-radius: 12px 12px 0 0; padding: 20px; margin: -25px -25px 20px -25px; text-align: center; }
    .header h1 { font-size: 26px; color: #ffffff; font-weight: 800; margin: 0; }
    .header .sub { font-size: 13px; color: #bfdbfe; margin-top: 6px; }
    .header .sub b { color: #ffffff; }
    
    /* Card Styles */
    .card { border-radius: 12px; padding: 18px; border: 1px solid; background-color: #ffffff; width: 100%; box-sizing: border-box; }
    .card-green { background-color: #f0fdf4; border-color: #bbf7d0; }
    .card-blue { background-color: #eff6ff; border-color: #bfdbfe; }
    .card-orange { background-color: #fffbeb; border-color: #fde68a; }
    .card-red { background-color: #fef2f2; border-color: #fecaca; }
    
    .card-icon-circle { width: 50px; height: 50px; border-radius: 50%; text-align: center; line-height: 50px; font-size: 24px; color: white; display: inline-block; vertical-align: middle; margin-right: 10px; }
    .icon-green { background-color: #22c55e; }
    .icon-blue { background-color: #3b82f6; }
    .icon-orange { background-color: #f59e0b; }
    .icon-red { background-color: #ef4444; }
    
    .card-header-text { display: inline-block; vertical-align: middle; }
    .card-header-title { font-size: 12px; text-transform: uppercase; font-weight: 800; color: #1e293b; letter-spacing: 0.5px; margin: 0 0 2px 0; }
    .card-header-value { font-size: 22px; font-weight: 700; margin: 0; line-height: 1.2; }
    
    .card-footer-row { border-top: 1px solid rgba(0,0,0,0.08); padding-top: 12px; text-align: center; margin-top: 15px; }
    .card-sub { font-size: 12px; color: #64748b; margin-bottom: 8px; }
    
    .card-badge { display: inline-block; padding: 4px 16px; border-radius: 20px; font-weight: 700; font-size: 12px; }
    .badge-green { background-color: #dcfce7; color: #166534; }
    .badge-red { background-color: #fee2e2; color: #991b1b; }
    .badge-orange { background-color: #fef3c7; color: #92400e; }
    
    .section-title { font-size: 15px; font-weight: 700; color: #0f172a; border-left: 4px solid #3b82f6; padding-left: 10px; margin-bottom: 12px; margin-top: 25px; }
    table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; margin-bottom: 15px; border: 1px solid #1e3a8a; border-radius: 8px; overflow: hidden; }
    th { background-color: #1e3a8a; color: #ffffff; padding: 10px 12px; border: none; font-weight: 600; text-align: left; }
    td { padding: 9px 12px; border-bottom: 1px solid #e2e8f0; color: #334155; }
    tr:last-child td { border-bottom: none; }
    .text-center { text-align: center; }
    .text-right { text-align: right; }
    .status-badge { display: inline-block; padding: 3px 14px; border-radius: 20px; font-weight: 600; font-size: 11px; }
    .status-ok { background-color: #dcfce7; color: #166534; }
    .status-warn { background-color: #fef3c7; color: #92400e; }
    .status-err { background-color: #fee2e2; color: #991b1b; }
    .footer { margin-top: 20px; padding: 12px 15px; background-color: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; color: #1e3a8a; font-size: 12px; }
    .footer .signature { margin-top: 10px; font-weight: 600; color: #0f172a; }
</style>';

-- Build Dashboard Cards using TABLE layout for Outlook compatibility
-- Using Unicode Emojis instead of Font Awesome
DECLARE @DashboardHTML NVARCHAR(MAX) = N'
<table width="100%" cellpadding="10" cellspacing="0" border="0">
<tr>
    <td width="25%" valign="top">
        <div class="card card-green">
            <div>
                <div class="card-icon-circle icon-green">&#128187;</div>
                <div class="card-header-text">
                    <div class="card-header-title">SQL Service</div>
                    <div class="card-header-value" style="color:#15803d;">Running</div>
                </div>
            </div>
            <div class="card-footer-row">
                <div class="card-badge badge-green">&#10004; HEALTHY</div>
            </div>
        </div>
    </td>
    <td width="25%" valign="top">
        <div class="card card-blue">
            <div>
                <div class="card-icon-circle icon-blue">&#128450;</div>
                <div class="card-header-text">
                    <div class="card-header-title">Databases</div>
                    <div class="card-header-value" style="color:#1d4ed8;">' + CAST(@OnlineDBs AS VARCHAR(10)) + ' / ' + CAST(@TotalDBs AS VARCHAR(10)) + ' Online</div>
                </div>
            </div>
            <div class="card-footer-row">
                <div class="card-sub">' + CAST(@OfflineCount AS VARCHAR(10)) + ' Offline / Suspect</div>
                <div class="card-badge badge-green">&#10004; HEALTHY</div>
            </div>
        </div>
    </td>
    <td width="25%" valign="top">
        <div class="card card-orange">
            <div>
                <div class="card-icon-circle icon-orange">&#128230;</div>
                <div class="card-header-text">
                    <div class="card-header-title">Backups</div>
                    <div class="card-header-value" style="color:#b45309;">' + CASE WHEN @BackupAlertCount = 0 THEN 'All Successful' ELSE CAST(@BackupAlertCount AS VARCHAR(10)) + ' Overdue' END + '</div>
                </div>
            </div>
            <div class="card-footer-row">
                <div class="card-sub">' + CAST(@BackupAlertCount AS VARCHAR(10)) + ' Overdue</div>
                <div class="card-badge ' + CASE WHEN @BackupAlertCount = 0 THEN 'badge-green' ELSE 'badge-orange' END + '">' + CASE WHEN @BackupAlertCount = 0 THEN '&#10004; HEALTHY' ELSE '&#9888; WARNING' END + '</div>
            </div>
        </div>
    </td>
    <td width="25%" valign="top">
        <div class="card card-red">
            <div>
                <div class="card-icon-circle icon-red">&#128190;</div>
                <div class="card-header-text">
                    <div class="card-header-title">Disk Space</div>
                    <div class="card-header-value" style="color:#dc2626;">' + @LowestDiskDrive + ' ' + CAST(@LowestDiskPct AS VARCHAR(10)) + '% Free</div>
                </div>
            </div>
            <div class="card-footer-row">
                <div class="card-sub">Lowest Free Space</div>
                <div class="card-badge badge-orange">&#9888; WARNING</div>
            </div>
        </div>
    </td>
</tr>
</table>';

-- 1. Server Details Table
DECLARE @ServerHTML NVARCHAR(MAX) = N'
<div class="section-title">1. SERVER DETAILS</div>
<table>
    <thead><tr>
        <th>GetDate</th><th>Hostname</th><th>SQL Instance</th><th>Edition</th><th>Build Number</th>
        <th>Is Cluster</th><th>Current Node</th><th>SQL Restart</th><th class="text-right">Uptime (Hrs)</th><th class="text-center">Status</th>
    </tr></thead>
    <tbody><tr>
        <td>' + CONVERT(VARCHAR(19), @GetDate, 120) + '</td>
        <td>' + @Hostname + '</td>
        <td style="font-weight:600;">' + @InstanceName + '</td>
        <td>' + @Edition + '</td>
        <td>' + @BuildNumber + '</td>
        <td>' + CASE WHEN @IsCluster = 1 THEN 'Yes' ELSE 'No' END + '</td>
        <td>' + @CurrentNode + '</td>
        <td>' + CONVERT(VARCHAR(16), @SQLRestart, 120) + '</td>
        <td class="text-right">' + CAST(@UptimeHours AS VARCHAR(10)) + '</td>
        <td class="text-center"><span class="status-badge status-ok">Relax</span></td>
    </tr></tbody>
</table>';

-- 2. Database Details Table
DECLARE @DbHTML NVARCHAR(MAX) = N'
<div class="section-title">2. DATABASE DETAILS</div>
<table>
    <thead><tr>
        <th>Database Name</th><th>Recovery Model</th><th>Compatibility Level</th><th class="text-right">Database Size (GB)</th><th>State</th><th class="text-center">Status</th>
    </tr></thead>
    <tbody>';

SELECT @DbHTML = @DbHTML + 
    '<tr>' +
    '<td style="font-weight:600;">' + name + '</td>' +
    '<td>' + recovery_model_desc + '</td>' +
    '<td>' + CAST(compatibility_level AS VARCHAR) + '</td>' +
    '<td class="text-right">' + CAST(SizeGB AS VARCHAR) + '</td>' +
    '<td>' + CASE WHEN state_desc = 'ONLINE' THEN state_desc ELSE '<span style="color:#dc2626;font-weight:700;">' + state_desc + '</span>' END + '</td>' +
    '<td class="text-center"><span class="status-badge ' + CASE WHEN state_desc = 'ONLINE' THEN 'status-ok' ELSE 'status-err' END + '">' + CASE WHEN state_desc = 'ONLINE' THEN 'Relax' ELSE 'CheckNow' END + '</span></td>' +
    '</tr>'
FROM #DatabaseInfo
ORDER BY name;

SET @DbHTML = @DbHTML + '</tbody></table>';

-- 3. Database Backup Details
DECLARE @BackupHTML NVARCHAR(MAX) = N'
<div class="section-title">3. DATABASE BACKUP DETAILS</div>
<table>
    <thead><tr>
        <th>Database Name</th><th>Recovery Model</th><th>Last Full Backup</th><th class="text-right">Full Age</th>
        <th>Last Diff Backup</th><th class="text-right">Diff Age</th>
        <th>Last Log Backup</th><th class="text-right">Log Age</th><th class="text-center">Status</th>
    </tr></thead>
    <tbody>';

SELECT @BackupHTML = @BackupHTML + 
    '<tr>' +
    '<td style="font-weight:600;">' + DBName + '</td>' +
    '<td>' + recovery_model_desc + '</td>' +
    '<td>' + ISNULL(CONVERT(VARCHAR(16), LastFull, 120), '<span style="color:#94a3b8;">Never</span>') + '</td>' +
    '<td class="text-right">' + ISNULL(CAST(FullAgeDays AS VARCHAR(10)) + ' Days', 'N/A') + '</td>' +
    '<td>' + ISNULL(CONVERT(VARCHAR(16), LastDiff, 120), '<span style="color:#94a3b8;">Never</span>') + '</td>' +
    '<td class="text-right">' + ISNULL(CAST(DiffAgeHours AS VARCHAR(10)) + ' Hrs', 'N/A') + '</td>' +
    '<td>' + ISNULL(CONVERT(VARCHAR(16), LastLog, 120), '<span style="color:#94a3b8;">Never</span>') + '</td>' +
    '<td class="text-right">' + 
        CASE WHEN recovery_model_desc = 'SIMPLE' THEN 'N/A'
             WHEN LogAgeMinutes IS NULL THEN 'N/A'
             WHEN LogAgeMinutes < 60 THEN CAST(LogAgeMinutes AS VARCHAR(10)) + ' Min'
             ELSE CAST(LogAgeMinutes / 60 AS VARCHAR(10)) + ' Hrs'
        END + '</td>' +
    '<td class="text-center"><span class="status-badge ' + 
        CASE WHEN FullAgeDays > 7 OR FullAgeDays IS NULL OR DiffAgeHours > 24 OR (recovery_model_desc <> 'SIMPLE' AND (LogAgeMinutes > 60 OR LogAgeMinutes IS NULL)) THEN 'status-err' ELSE 'status-ok' END + '">' +
        CASE WHEN FullAgeDays > 7 OR FullAgeDays IS NULL OR DiffAgeHours > 24 OR (recovery_model_desc <> 'SIMPLE' AND (LogAgeMinutes > 60 OR LogAgeMinutes IS NULL)) THEN 'CheckNow' ELSE 'Relax' END + '</span></td>' +
    '</tr>'
FROM #BackupInfo
ORDER BY DBName;

SET @BackupHTML = @BackupHTML + '</tbody></table>';

-- 4. Disk Details Table
DECLARE @DiskHTML NVARCHAR(MAX) = N'
<div class="section-title">4. DISK DETAILS</div>
<table>
    <thead><tr>
        <th>Drive</th><th class="text-right">Total (GB)</th><th class="text-right">Used (GB)</th><th class="text-right">Free (GB)</th>
        <th class="text-center">Free %</th><th class="text-center">Status</th>
    </tr></thead>
    <tbody>';

SELECT @DiskHTML = @DiskHTML + 
    '<tr>' +
    '<td style="font-weight:600;">' + VolumeName + '</td>' +
    '<td class="text-right">' + CAST(TotalGB AS VARCHAR(20)) + '</td>' +
    '<td class="text-right">' + CAST(UsedGB AS VARCHAR(20)) + '</td>' +
    '<td class="text-right" style="font-weight:' + CASE WHEN FreePct < 25 THEN '700' ELSE '400' END + ';">' + CAST(FreeGB AS VARCHAR(20)) + '</td>' +
    '<td class="text-center" style="font-weight:700;color:' + CASE WHEN FreePct < 15 THEN '#dc2626' WHEN FreePct < 25 THEN '#b45309' ELSE '#1e293b' END + ';">' + CAST(FreePct AS VARCHAR(10)) + '%</td>' +
    '<td class="text-center"><span class="status-badge ' + 
        CASE WHEN FreePct < 15 THEN 'status-err' WHEN FreePct < 25 THEN 'status-warn' ELSE 'status-ok' END + '">' +
        CASE WHEN FreePct < 25 THEN 'CheckNow' ELSE 'Relax' END + '</span></td>' +
    '</tr>'
FROM #DiskInfo
ORDER BY VolumeName;

SET @DiskHTML = @DiskHTML + '</tbody></table>';

-- Footer with Signature
DECLARE @FooterHTML NVARCHAR(MAX) = N'
<div class="footer">
    <div style="font-size: 12px; color: #1e3a8a;">
        <i>&#9432;</i> This is an automated email. Please do not reply.
    </div>
    <div class="signature">
        Thanks &amp; regards,<br>
        Your Name
    </div>
</div>';

--------------------------------------------------------------------------------
-- 5. ASSEMBLE COMPLETE HTML DOCUMENT
--------------------------------------------------------------------------------
DECLARE @FullHTMLBody NVARCHAR(MAX) = N'
<!DOCTYPE html>
<html>
<head>' + @CSS + '</head>
<body>
    <div class="container">
        <div class="header">
            <h1>SQL Server Health Check Report</h1>
            <div class="sub">
                Server: <b>' + @InstanceName + '</b> &nbsp;|&nbsp; 
                Generated: ' + CONVERT(VARCHAR(19), @GetDate, 120) + ' &nbsp;|&nbsp; 
                SQL Version: ' + @Edition + ' (' + @BuildNumber + ')
            </div>
        </div>
        ' + @DashboardHTML + '
        ' + @ServerHTML + '
        ' + @DbHTML + '
        ' + @BackupHTML + '
        ' + @DiskHTML + '
        ' + @FooterHTML + '
    </div>
</body>
</html>';

--------------------------------------------------------------------------------
-- 6. SEND EMAIL
--------------------------------------------------------------------------------
EXEC msdb.dbo.sp_send_dbmail
    @profile_name = @MailProfile,
    @recipients   = @Recipients,
    @subject      = @Subject,
    @body         = @FullHTMLBody,
    @body_format  = 'HTML';

-- Cleanup
IF OBJECT_ID('tempdb..#DatabaseInfo') IS NOT NULL DROP TABLE #DatabaseInfo;
IF OBJECT_ID('tempdb..#BackupInfo') IS NOT NULL DROP TABLE #BackupInfo;
IF OBJECT_ID('tempdb..#DiskInfo') IS NOT NULL DROP TABLE #DiskInfo;

END;
GO


*/

Author Name

Contact Form

Name

Email *

Message *

Powered by Blogger.