From 770ed4c01efdffc47dbab285bb1991fdbebc854d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Doma=C5=84ski?= Date: Fri, 12 Jun 2026 06:29:37 +0000 Subject: [PATCH] Jun 12, 2026, 8:29 AM --- toolbox/DBAToolBox.ps1 | 53 ++++++++++++++++++++++++++- toolbox/Modules/SQLManager.psm1 | 65 ++++++++++++++++++++++++++++++--- toolbox/UI/MainWindow.xaml | 3 ++ 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/toolbox/DBAToolBox.ps1 b/toolbox/DBAToolBox.ps1 index cdb3d77..370bff7 100644 --- a/toolbox/DBAToolBox.ps1 +++ b/toolbox/DBAToolBox.ps1 @@ -44,6 +44,7 @@ $btnConnect = $Window.FindName("btnConnect") $txtServerName = $Window.FindName("txtServerName") $txtLogs = $Window.FindName("txtLogs") $dgServerInfo = $Window.FindName("dgServerInfo") +$dgDatabases = $Window.FindName("dgDatabases") $dgServerErrors = $Window.FindName("dgServerErrors") $tvServers = $Window.FindName("tvServers") $btnAddServer = $Window.FindName("btnAddServer") @@ -107,6 +108,21 @@ function Refresh-ServerTree { try { $dbs = Get-SQLDatabases -ServerInstance $srvName if ($dbs -and $dbs.Count -gt 0) { + # Cache the loaded databases + if ($null -eq $script:ServerConnectionStates[$srvName]) { + $script:ServerConnectionStates[$srvName] = @{ + Success = $true + Instance = $srvName + Version = "Unknown (Expanded)" + Edition = "Unknown" + Status = "Online" + Databases = $dbs + Errors = $null + } + } else { + $script:ServerConnectionStates[$srvName].Databases = $dbs + } + foreach ($db in $dbs) { $dbNode = New-Object System.Windows.Controls.TreeViewItem $dbNode.Header = "$($db.Name) ($($db.Status))" @@ -156,6 +172,13 @@ $tvServers.Add_SelectedItemChanged({ # Server is connected: show its version/details in the DataGrid $dgServerInfo.ItemsSource = @([PSCustomObject]$connState | Select-Object Instance, Version, Edition, Status) + # Show cached databases if available + if ($null -ne $connState.Databases) { + $dgDatabases.ItemsSource = @($connState.Databases) + } else { + $dgDatabases.ItemsSource = $null + } + # Show cached error logs if available if ($null -ne $connState.Errors) { $dgServerErrors.ItemsSource = @($connState.Errors) @@ -165,6 +188,7 @@ $tvServers.Add_SelectedItemChanged({ } else { # Server is not connected: clear the DataGrids so no old server details/errors are visible $dgServerInfo.ItemsSource = $null + $dgDatabases.ItemsSource = $null $dgServerErrors.ItemsSource = $null } } elseif ($selectedItem.Tag -is [System.Management.Automation.PSCustomObject]) { @@ -173,7 +197,8 @@ $tvServers.Add_SelectedItemChanged({ Write-Log "Selected database: $($db.Name) (Size: $($db.Size), Status: $($db.Status))" # Show database details in the main information grid - $dgServerInfo.ItemsSource = @($db | Select-Object Name, Status, Size, RecoveryModel) + $dgServerInfo.ItemsSource = @($db | Select-Object Name, Status, Size, RecoveryModel, LastFullBackup, LastLogBackup) + $dgDatabases.ItemsSource = $null $dgServerErrors.ItemsSource = $null # Also, update txtServerName with the parent server's tag so they see which server it belongs to @@ -186,6 +211,7 @@ $tvServers.Add_SelectedItemChanged({ # Hide detail panel if selection is null or ROOT $gridDetails.Visibility = "Collapsed" $dgServerInfo.ItemsSource = $null + $dgDatabases.ItemsSource = $null $dgServerErrors.ItemsSource = $null } }) @@ -267,6 +293,25 @@ $btnConnect.Add_Click({ Write-Log "Connected successfully to $($result.Instance)!" $dgServerInfo.ItemsSource = @([PSCustomObject]$result | Select-Object Instance, Version, Edition, Status) + # Fetch databases list using Get-SQLDatabases + Write-Log "Fetching databases list for $server..." + try { + $dbs = Get-SQLDatabases -ServerInstance $server + if ($dbs -and $dbs.Count -gt 0) { + $dgDatabases.ItemsSource = @($dbs) + $result.Databases = $dbs + Write-Log "Loaded $($dbs.Count) databases." + } else { + $dgDatabases.ItemsSource = $null + $result.Databases = $null + Write-Log "No databases found on $server." + } + } catch { + Write-Log "Warning: Could not load databases: $($_.Exception.Message)" + $dgDatabases.ItemsSource = $null + $result.Databases = $null + } + # Fetch errors from the last hour using Get-SQLErrors Write-Log "Fetching error logs (last hour) for $server..." try { @@ -277,25 +322,29 @@ $btnConnect.Add_Click({ Write-Log "Loaded $($errors.Count) error log entries." } else { $dgServerErrors.ItemsSource = $null + $result.Errors = $null Write-Log "No errors found in the last hour." } } catch { Write-Log "Warning: Could not load error log: $($_.Exception.Message)" $dgServerErrors.ItemsSource = $null + $result.Errors = $null } - # Cache successful connection state with its fetched errors + # Cache successful connection state with its fetched errors and databases $script:ServerConnectionStates[$server] = $result } else { Write-Log "Connection failed: $($result.Error)" $script:ServerConnectionStates[$server] = $null $dgServerInfo.ItemsSource = $null + $dgDatabases.ItemsSource = $null $dgServerErrors.ItemsSource = $null } } catch { Write-Log "Critical Error: $($_.Exception.Message)" $script:ServerConnectionStates[$server] = $null $dgServerInfo.ItemsSource = $null + $dgDatabases.ItemsSource = $null $dgServerErrors.ItemsSource = $null } finally { $btnConnect.IsEnabled = $true diff --git a/toolbox/Modules/SQLManager.psm1 b/toolbox/Modules/SQLManager.psm1 index 48dd231..98ab1d7 100644 --- a/toolbox/Modules/SQLManager.psm1 +++ b/toolbox/Modules/SQLManager.psm1 @@ -86,10 +86,37 @@ function Get-SQLDatabases { try { $connection.Open() - # Query databases from sys.databases joined with sys.master_files to correctly sum file sizes. - # sys.databases does not have a "size" column. We must group and sum size from sys.master_files. $cmd = $connection.CreateCommand() + # Primary query including backups $cmd.CommandText = @" +SELECT + d.name, + d.state_desc, + COALESCE(SUM(CAST(f.size AS FLOAT) * 8.0 / 1024.0), 0.0) as size_mb, + d.recovery_model_desc, + ( + SELECT MAX(b.backup_finish_date) + FROM msdb.dbo.backupset b + WHERE b.database_name = d.name AND b.type = 'D' + ) AS last_full_backup, + ( + SELECT MAX(b.backup_finish_date) + FROM msdb.dbo.backupset b + WHERE b.database_name = d.name AND b.type = 'L' + ) AS last_log_backup +FROM sys.databases d +LEFT JOIN sys.master_files f ON d.database_id = f.database_id +GROUP BY d.name, d.state_desc, d.recovery_model_desc +"@ + + $adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd) + $table = New-Object System.Data.DataTable + try { + $null = $adapter.Fill($table) + $hasBackups = $true + } catch { + # Fallback to query without backups if we don't have access to msdb or backupset table + $cmd.CommandText = @" SELECT d.name, d.state_desc, @@ -99,10 +126,10 @@ FROM sys.databases d LEFT JOIN sys.master_files f ON d.database_id = f.database_id GROUP BY d.name, d.state_desc, d.recovery_model_desc "@ - - $adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd) - $table = New-Object System.Data.DataTable - $null = $adapter.Fill($table) + $table.Clear() + $null = $adapter.Fill($table) + $hasBackups = $false + } $dbList = @() foreach ($row in $table.Rows) { @@ -115,11 +142,37 @@ GROUP BY d.name, d.state_desc, d.recovery_model_desc $dbStatus = if ($row.state_desc -ne [System.DBNull]::Value -and $row.state_desc -ne $null) { $row.state_desc.ToString() } else { "Unknown" } $dbRecovery = if ($row.recovery_model_desc -ne [System.DBNull]::Value -and $row.recovery_model_desc -ne $null) { $row.recovery_model_desc.ToString() } else { "Simple" } + $fullBackup = "Unknown" + $logBackup = "Unknown" + + if ($hasBackups) { + $fullBackup = "Never" + if ($row.Table.Columns.Contains("last_full_backup") -and $row.last_full_backup -ne [System.DBNull]::Value -and $row.last_full_backup -ne $null) { + $fullBackup = ([datetime]$row.last_full_backup).ToString("yyyy-MM-dd HH:mm:ss") + } + + $logBackup = "Never" + if ($dbRecovery -eq "SIMPLE") { + $logBackup = "N/A (Simple)" + } elseif ($row.Table.Columns.Contains("last_log_backup") -and $row.last_log_backup -ne [System.DBNull]::Value -and $row.last_log_backup -ne $null) { + $logBackup = ([datetime]$row.last_log_backup).ToString("yyyy-MM-dd HH:mm:ss") + } + } else { + $fullBackup = "N/A (No Access)" + if ($dbRecovery -eq "SIMPLE") { + $logBackup = "N/A (Simple)" + } else { + $logBackup = "N/A (No Access)" + } + } + $dbList += [pscustomobject]@{ Name = $dbName Status = $dbStatus Size = "$([Math]::Round($sizeVal, 2)) MB" RecoveryModel = $dbRecovery + LastFullBackup = $fullBackup + LastLogBackup = $logBackup } } return $dbList diff --git a/toolbox/UI/MainWindow.xaml b/toolbox/UI/MainWindow.xaml index dde30d4..0300e42 100644 --- a/toolbox/UI/MainWindow.xaml +++ b/toolbox/UI/MainWindow.xaml @@ -72,6 +72,9 @@ + + +