Jun 12, 2026, 8:29 AM
This commit is contained in:
+51
-2
@@ -44,6 +44,7 @@ $btnConnect = $Window.FindName("btnConnect")
|
|||||||
$txtServerName = $Window.FindName("txtServerName")
|
$txtServerName = $Window.FindName("txtServerName")
|
||||||
$txtLogs = $Window.FindName("txtLogs")
|
$txtLogs = $Window.FindName("txtLogs")
|
||||||
$dgServerInfo = $Window.FindName("dgServerInfo")
|
$dgServerInfo = $Window.FindName("dgServerInfo")
|
||||||
|
$dgDatabases = $Window.FindName("dgDatabases")
|
||||||
$dgServerErrors = $Window.FindName("dgServerErrors")
|
$dgServerErrors = $Window.FindName("dgServerErrors")
|
||||||
$tvServers = $Window.FindName("tvServers")
|
$tvServers = $Window.FindName("tvServers")
|
||||||
$btnAddServer = $Window.FindName("btnAddServer")
|
$btnAddServer = $Window.FindName("btnAddServer")
|
||||||
@@ -107,6 +108,21 @@ function Refresh-ServerTree {
|
|||||||
try {
|
try {
|
||||||
$dbs = Get-SQLDatabases -ServerInstance $srvName
|
$dbs = Get-SQLDatabases -ServerInstance $srvName
|
||||||
if ($dbs -and $dbs.Count -gt 0) {
|
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) {
|
foreach ($db in $dbs) {
|
||||||
$dbNode = New-Object System.Windows.Controls.TreeViewItem
|
$dbNode = New-Object System.Windows.Controls.TreeViewItem
|
||||||
$dbNode.Header = "$($db.Name) ($($db.Status))"
|
$dbNode.Header = "$($db.Name) ($($db.Status))"
|
||||||
@@ -156,6 +172,13 @@ $tvServers.Add_SelectedItemChanged({
|
|||||||
# Server is connected: show its version/details in the DataGrid
|
# Server is connected: show its version/details in the DataGrid
|
||||||
$dgServerInfo.ItemsSource = @([PSCustomObject]$connState | Select-Object Instance, Version, Edition, Status)
|
$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
|
# Show cached error logs if available
|
||||||
if ($null -ne $connState.Errors) {
|
if ($null -ne $connState.Errors) {
|
||||||
$dgServerErrors.ItemsSource = @($connState.Errors)
|
$dgServerErrors.ItemsSource = @($connState.Errors)
|
||||||
@@ -165,6 +188,7 @@ $tvServers.Add_SelectedItemChanged({
|
|||||||
} else {
|
} else {
|
||||||
# Server is not connected: clear the DataGrids so no old server details/errors are visible
|
# Server is not connected: clear the DataGrids so no old server details/errors are visible
|
||||||
$dgServerInfo.ItemsSource = $null
|
$dgServerInfo.ItemsSource = $null
|
||||||
|
$dgDatabases.ItemsSource = $null
|
||||||
$dgServerErrors.ItemsSource = $null
|
$dgServerErrors.ItemsSource = $null
|
||||||
}
|
}
|
||||||
} elseif ($selectedItem.Tag -is [System.Management.Automation.PSCustomObject]) {
|
} 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))"
|
Write-Log "Selected database: $($db.Name) (Size: $($db.Size), Status: $($db.Status))"
|
||||||
|
|
||||||
# Show database details in the main information grid
|
# 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
|
$dgServerErrors.ItemsSource = $null
|
||||||
|
|
||||||
# Also, update txtServerName with the parent server's tag so they see which server it belongs to
|
# 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
|
# Hide detail panel if selection is null or ROOT
|
||||||
$gridDetails.Visibility = "Collapsed"
|
$gridDetails.Visibility = "Collapsed"
|
||||||
$dgServerInfo.ItemsSource = $null
|
$dgServerInfo.ItemsSource = $null
|
||||||
|
$dgDatabases.ItemsSource = $null
|
||||||
$dgServerErrors.ItemsSource = $null
|
$dgServerErrors.ItemsSource = $null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -267,6 +293,25 @@ $btnConnect.Add_Click({
|
|||||||
Write-Log "Connected successfully to $($result.Instance)!"
|
Write-Log "Connected successfully to $($result.Instance)!"
|
||||||
$dgServerInfo.ItemsSource = @([PSCustomObject]$result | Select-Object Instance, Version, Edition, Status)
|
$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
|
# Fetch errors from the last hour using Get-SQLErrors
|
||||||
Write-Log "Fetching error logs (last hour) for $server..."
|
Write-Log "Fetching error logs (last hour) for $server..."
|
||||||
try {
|
try {
|
||||||
@@ -277,25 +322,29 @@ $btnConnect.Add_Click({
|
|||||||
Write-Log "Loaded $($errors.Count) error log entries."
|
Write-Log "Loaded $($errors.Count) error log entries."
|
||||||
} else {
|
} else {
|
||||||
$dgServerErrors.ItemsSource = $null
|
$dgServerErrors.ItemsSource = $null
|
||||||
|
$result.Errors = $null
|
||||||
Write-Log "No errors found in the last hour."
|
Write-Log "No errors found in the last hour."
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Write-Log "Warning: Could not load error log: $($_.Exception.Message)"
|
Write-Log "Warning: Could not load error log: $($_.Exception.Message)"
|
||||||
$dgServerErrors.ItemsSource = $null
|
$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
|
$script:ServerConnectionStates[$server] = $result
|
||||||
} else {
|
} else {
|
||||||
Write-Log "Connection failed: $($result.Error)"
|
Write-Log "Connection failed: $($result.Error)"
|
||||||
$script:ServerConnectionStates[$server] = $null
|
$script:ServerConnectionStates[$server] = $null
|
||||||
$dgServerInfo.ItemsSource = $null
|
$dgServerInfo.ItemsSource = $null
|
||||||
|
$dgDatabases.ItemsSource = $null
|
||||||
$dgServerErrors.ItemsSource = $null
|
$dgServerErrors.ItemsSource = $null
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Write-Log "Critical Error: $($_.Exception.Message)"
|
Write-Log "Critical Error: $($_.Exception.Message)"
|
||||||
$script:ServerConnectionStates[$server] = $null
|
$script:ServerConnectionStates[$server] = $null
|
||||||
$dgServerInfo.ItemsSource = $null
|
$dgServerInfo.ItemsSource = $null
|
||||||
|
$dgDatabases.ItemsSource = $null
|
||||||
$dgServerErrors.ItemsSource = $null
|
$dgServerErrors.ItemsSource = $null
|
||||||
} finally {
|
} finally {
|
||||||
$btnConnect.IsEnabled = $true
|
$btnConnect.IsEnabled = $true
|
||||||
|
|||||||
@@ -86,10 +86,37 @@ function Get-SQLDatabases {
|
|||||||
try {
|
try {
|
||||||
$connection.Open()
|
$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()
|
$cmd = $connection.CreateCommand()
|
||||||
|
# Primary query including backups
|
||||||
$cmd.CommandText = @"
|
$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
|
SELECT
|
||||||
d.name,
|
d.name,
|
||||||
d.state_desc,
|
d.state_desc,
|
||||||
@@ -99,10 +126,10 @@ FROM sys.databases d
|
|||||||
LEFT JOIN sys.master_files f ON d.database_id = f.database_id
|
LEFT JOIN sys.master_files f ON d.database_id = f.database_id
|
||||||
GROUP BY d.name, d.state_desc, d.recovery_model_desc
|
GROUP BY d.name, d.state_desc, d.recovery_model_desc
|
||||||
"@
|
"@
|
||||||
|
$table.Clear()
|
||||||
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
|
$null = $adapter.Fill($table)
|
||||||
$table = New-Object System.Data.DataTable
|
$hasBackups = $false
|
||||||
$null = $adapter.Fill($table)
|
}
|
||||||
|
|
||||||
$dbList = @()
|
$dbList = @()
|
||||||
foreach ($row in $table.Rows) {
|
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" }
|
$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" }
|
$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]@{
|
$dbList += [pscustomobject]@{
|
||||||
Name = $dbName
|
Name = $dbName
|
||||||
Status = $dbStatus
|
Status = $dbStatus
|
||||||
Size = "$([Math]::Round($sizeVal, 2)) MB"
|
Size = "$([Math]::Round($sizeVal, 2)) MB"
|
||||||
RecoveryModel = $dbRecovery
|
RecoveryModel = $dbRecovery
|
||||||
|
LastFullBackup = $fullBackup
|
||||||
|
LastLogBackup = $logBackup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $dbList
|
return $dbList
|
||||||
|
|||||||
@@ -72,6 +72,9 @@
|
|||||||
<TabItem Header="Details">
|
<TabItem Header="Details">
|
||||||
<DataGrid x:Name="dgServerInfo" AutoGenerateColumns="True" IsReadOnly="True" Background="White" BorderBrush="Transparent" Margin="5"/>
|
<DataGrid x:Name="dgServerInfo" AutoGenerateColumns="True" IsReadOnly="True" Background="White" BorderBrush="Transparent" Margin="5"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="Databases">
|
||||||
|
<DataGrid x:Name="dgDatabases" AutoGenerateColumns="True" IsReadOnly="True" Background="White" BorderBrush="Transparent" Margin="5"/>
|
||||||
|
</TabItem>
|
||||||
<TabItem Header="Errors (Last Hour)">
|
<TabItem Header="Errors (Last Hour)">
|
||||||
<DataGrid x:Name="dgServerErrors" AutoGenerateColumns="True" IsReadOnly="True" Background="White" BorderBrush="Transparent" Margin="5"/>
|
<DataGrid x:Name="dgServerErrors" AutoGenerateColumns="True" IsReadOnly="True" Background="White" BorderBrush="Transparent" Margin="5"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
Reference in New Issue
Block a user