diff --git a/toolbox/DBAToolBox.ps1 b/toolbox/DBAToolBox.ps1 index 384feb7..a42e33b 100644 --- a/toolbox/DBAToolBox.ps1 +++ b/toolbox/DBAToolBox.ps1 @@ -10,7 +10,25 @@ $configPath = Join-Path $PSScriptRoot "Config\settings.json" if (Test-Path $configPath) { $settings = Get-Content $configPath | ConvertFrom-Json } else { - $settings = [pscustomobject]@{ LastServer = ""; Theme = "Light" } + $settings = [pscustomobject]@{ LastServer = ""; Theme = "Light"; Servers = @("localhost") } +} + +# Ensure Servers is a collection and initialized +if ($null -eq $settings.Servers) { + if ($settings -is [System.Management.Automation.PSCustomObject]) { + $props = [ordered]@{ + LastServer = $settings.LastServer + Theme = $settings.Theme + Servers = @("localhost") + } + $settings = [pscustomobject]$props + } else { + $settings = [pscustomobject]@{ LastServer = ""; Theme = "Light"; Servers = @("localhost") } + } +} + +if ($settings.Servers -is [string]) { + $settings.Servers = @($settings.Servers) } # 1. Path to XAML @@ -26,6 +44,9 @@ $btnConnect = $Window.FindName("btnConnect") $txtServerName = $Window.FindName("txtServerName") $txtLogs = $Window.FindName("txtLogs") $dgServerInfo = $Window.FindName("dgServerInfo") +$tvServers = $Window.FindName("tvServers") +$btnAddServer = $Window.FindName("btnAddServer") +$btnRemoveServer = $Window.FindName("btnRemoveServer") # Set initial values from settings $txtServerName.Text = $settings.LastServer @@ -40,6 +61,150 @@ function Write-Log { }) } +# 4.1. TreeView population and refresh +function Refresh-ServerTree { + $tvServers.Dispatcher.Invoke({ + $tvServers.Items.Clear() + + $rootNode = New-Object System.Windows.Controls.TreeViewItem + $rootNode.Header = "SQL Servers" + $rootNode.IsExpanded = $true + $rootNode.Tag = "__ROOT__" + + foreach ($server in $settings.Servers) { + $serverNode = New-Object System.Windows.Controls.TreeViewItem + $serverNode.Header = $server + $serverNode.Tag = $server + + # Add a dummy loading node so the expansion arrow is displayed + $dummyNode = New-Object System.Windows.Controls.TreeViewItem + $dummyNode.Header = "Loading..." + $serverNode.Items.Add($dummyNode) + + # Event handler when a server node is expanded + $serverNode.Add_Expanded({ + param($sender, $e) + # Prevent this handler from running for nested child expansions (bubbling) + if ($e.OriginalSource -ne $sender) { return } + + $node = $sender + if ($node.Items.Count -eq 1 -and $node.Items[0].Header -eq "Loading...") { + $node.Items.Clear() + $srvName = $node.Tag + + Write-Log "Fetching databases for $srvName..." + # Force UI to process the pending write-log before connection blocks the thread + [System.Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke( + [System.Windows.Threading.DispatcherPriority]::Background, + [System.Action]{} + ) + + try { + $dbs = Get-SQLDatabases -ServerInstance $srvName + if ($dbs -and $dbs.Count -gt 0) { + foreach ($db in $dbs) { + $dbNode = New-Object System.Windows.Controls.TreeViewItem + $dbNode.Header = "$($db.Name) ($($db.Status))" + $dbNode.Tag = $db + $node.Items.Add($dbNode) + } + Write-Log "Successfully loaded $($dbs.Count) databases for $srvName." + } else { + $noDbNode = New-Object System.Windows.Controls.TreeViewItem + $noDbNode.Header = "(No databases found)" + $node.Items.Add($noDbNode) + Write-Log "No databases found on $srvName." + } + } catch { + Write-Log "Error fetching databases: $($_.Exception.Message)" + $errNode = New-Object System.Windows.Controls.TreeViewItem + $errNode.Header = "(Error loading databases)" + $node.Items.Add($errNode) + } + } + }) + + $rootNode.Items.Add($serverNode) + } + + $tvServers.Items.Add($rootNode) + }) +} + +# Handle TreeView selection +$tvServers.Add_SelectedItemChanged({ + param($sender, $e) + $selectedItem = $tvServers.SelectedItem + if ($null -ne $selectedItem -and $null -ne $selectedItem.Tag) { + if ($selectedItem.Tag -is [string]) { + if ($selectedItem.Tag -eq "__ROOT__") { return } + + # It's a server node, copy its name to TextBox + $txtServerName.Text = $selectedItem.Tag + Write-Log "Selected server: $($selectedItem.Tag)" + } elseif ($selectedItem.Tag -is [System.Management.Automation.PSCustomObject]) { + # It's a database node + $db = $selectedItem.Tag + Write-Log "Selected database: $($db.Name) (Size: $($db.Size), Status: $($db.Status))" + } + } +}) + +# Handle Add Server button +$btnAddServer.Add_Click({ + $server = "" + try { + Add-Type -AssemblyName Microsoft.VisualBasic + $server = [Microsoft.VisualBasic.Interaction]::InputBox("Enter SQL Server Instance Name (e.g., localhost, srv\SQLEXPRESS):", "Add Server", "") + } catch { + # Fallback using current textbox value + $server = $txtServerName.Text + if ([string]::IsNullOrWhiteSpace($server)) { + [System.Windows.MessageBox]::Show("Visual Basic assembly not found. Please type the server name in 'Server Name' textbox first, and then click 'Add Server'.", "Add Server Fallback", "OK", "Information") + return + } + } + + if ([string]::IsNullOrWhiteSpace($server)) { return } + $server = $server.Trim() + + if ($settings.Servers -contains $server) { + [System.Windows.MessageBox]::Show("Server '$server' is already in the list.", "Server Already Exists", "OK", "Information") + return + } + + # Save to settings + $settings.Servers = $settings.Servers + $server + $settings | ConvertTo-Json | Set-Content $configPath + + Refresh-ServerTree + Write-Log "Successfully added server to list: $server" +}) + +# Handle Remove Server button +$btnRemoveServer.Add_Click({ + $selectedItem = $tvServers.SelectedItem + if ($null -eq $selectedItem -or $selectedItem.Tag -eq "__ROOT__") { + [System.Windows.MessageBox]::Show("Please select a server node in the tree to remove.", "Select Server", "OK", "Warning") + return + } + + $serverName = $selectedItem.Tag + # Verify that we selected a server node (it has a string Tag and is in settings.Servers) + if ($serverName -is [string] -and ($settings.Servers -contains $serverName)) { + $confirm = [System.Windows.MessageBox]::Show("Are you sure you want to remove '$serverName' from the list?", "Confirm Server Removal", "YesNo", "Question") + if ($confirm -eq "Yes") { + $settings.Servers = $settings.Servers | Where-Object { $_ -ne $serverName } + $settings | ConvertTo-Json | Set-Content $configPath + + Refresh-ServerTree + Write-Log "Removed server from list: $serverName" + } + } else { + [System.Windows.MessageBox]::Show("Only server nodes can be removed.", "Invalid Selection", "OK", "Warning") + } +}) + # 5. Connection logic $btnConnect.Add_Click({ $server = $txtServerName.Text @@ -71,13 +236,13 @@ $btnConnect.Add_Click({ } }) -# Startup Dependency Check +# Startup Dependency Check and Tree Initialization try { Import-DBAToolBoxDependencies Write-Log "DBAToolBox started. dbatools module loaded." + Refresh-ServerTree } catch { [System.Windows.MessageBox]::Show($_.Exception.Message, "Dependency Error", "OK", "Error") - # In some environments ShowDialog might fail if no UI thread, but for WPF it should work. $Window.Close() } diff --git a/toolbox/Modules/SQLManager.psm1 b/toolbox/Modules/SQLManager.psm1 index 1a44c77..d8d7102 100644 --- a/toolbox/Modules/SQLManager.psm1 +++ b/toolbox/Modules/SQLManager.psm1 @@ -44,4 +44,32 @@ function Test-SQLConnection { } } -Export-ModuleMember -Function Import-DBAToolBoxDependencies, Test-SQLConnection \ No newline at end of file +function Get-SQLDatabases { + <# + .SYNOPSIS + Gets database information for a SQL Server instance. + #> + param( + [Parameter(Mandatory=$true)] + [string]$ServerInstance + ) + + try { + # Using Get-DbaDatabase from dbatools + $databases = Get-DbaDatabase -SqlInstance $ServerInstance -ErrorAction Stop + $dbList = @() + foreach ($db in $databases) { + $dbList += [pscustomobject]@{ + Name = $db.Name + Status = $db.Status + Size = $db.Size + RecoveryModel = $db.RecoveryModel + } + } + return $dbList + } catch { + throw "Failed to get databases: $($_.Exception.Message)" + } +} + +Export-ModuleMember -Function Import-DBAToolBoxDependencies, Test-SQLConnection, Get-SQLDatabases \ No newline at end of file diff --git a/toolbox/UI/MainWindow.xaml b/toolbox/UI/MainWindow.xaml index e38ab03..2405c5f 100644 --- a/toolbox/UI/MainWindow.xaml +++ b/toolbox/UI/MainWindow.xaml @@ -1,30 +1,73 @@ + Title="DBAToolBox - MS SQL Management" Height="550" Width="850" Background="#F0F0F0"> - - - - -