PowerShell – Minidash

Mini Dashboard for support management that i thought might help, trying to create more ways to help support systems

Here is the script
<#
Dash.ps1 - Server Management Dashboard
PowerShell 5.1 / Windows Forms
Requires: .\lists\servers.txt, .\lists\links.txt, .\scripts\*.ps1
#>
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Speech
Add-Type -AssemblyName Microsoft.VisualBasic
[System.Windows.Forms.Application]::EnableVisualStyles()
#region ---- Paths / Init ----
$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$ListsPath = Join-Path $ScriptRoot "lists"
$ScriptsPath = Join-Path $ScriptRoot "scripts"
$ServersFile = Join-Path $ListsPath "servers.txt"
$LinksFile = Join-Path $ListsPath "links.txt"
$LogRoot = "C:\logs\Dash"
foreach ($p in @($ListsPath, $ScriptsPath)) {
if (-not (Test-Path $p)) { New-Item -Path $p -ItemType Directory -Force | Out-Null }
}
if (-not (Test-Path $ServersFile)) { New-Item -Path $ServersFile -ItemType File -Force | Out-Null }
if (-not (Test-Path $LinksFile)) { New-Item -Path $LinksFile -ItemType File -Force | Out-Null }
$Global:ActiveJobs = @{}
$Global:LinksMap = [ordered]@{}
#endregion
#region ---- Logging ----
function Write-Log {
param(
[string]$Message,
[string]$Level = "INFO",
[string]$ProcessName = "Dash"
)
$dateFolder = Get-Date -Format "yyyyMMdd"
$logDir = Join-Path $LogRoot $dateFolder
if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null }
$logFile = Join-Path $logDir "$ProcessName.txt"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"$timestamp [$Level] $Message" | Out-File -FilePath $logFile -Append -Encoding UTF8
}
#endregion
#region ---- Startup Greeting (Voice) ----
Start-Job -ScriptBlock {
param($UserName)
Start-Sleep -Seconds 5 # delay so Bluetooth speakers have time to connect
Add-Type -AssemblyName System.Speech
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
$femaleVoice = $synth.GetInstalledVoices() |
Where-Object { $_.VoiceInfo.Gender -eq 'Female' } |
Select-Object -First 1
if ($femaleVoice) { $synth.SelectVoice($femaleVoice.VoiceInfo.Name) }
$synth.Volume = 100
$hour = (Get-Date).Hour
$timeOfDay = if ($hour -lt 12) { "morning" } elseif ($hour -lt 17) { "afternoon" } else { "evening" }
$currentTime = Get-Date -Format "hh:mm tt"
$greeting = "Good $timeOfDay, $UserName. The current time is $currentTime."
$synth.Speak($greeting)
$synth.Dispose()
} -ArgumentList $env:USERNAME | Out-Null
#endregion
#region ---- Colors (Dark Mode) ----
$bgColor = [System.Drawing.Color]::FromArgb(32,32,32)
$panelColor = [System.Drawing.Color]::FromArgb(45,45,48)
$fgColor = [System.Drawing.Color]::White
$limeGreen = [System.Drawing.Color]::LimeGreen
#endregion
#region ---- Form ----
$form = New-Object System.Windows.Forms.Form
$form.Text = "Dash - Server Management Console"
$form.BackColor = $bgColor
$form.ForeColor = $fgColor
$form.FormBorderStyle = 'FixedSingle'
$form.MaximizeBox = $false
$form.TopMost = $true
$formWidth = 440
$formHeight = 900
$form.Size = New-Object System.Drawing.Size($formWidth, $formHeight)
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
$form.StartPosition = 'Manual'
$form.Location = New-Object System.Drawing.Point(($screen.Right - $formWidth), $screen.Top)
# --- Menu Strip ---
$menuStrip = New-Object System.Windows.Forms.MenuStrip
$menuStrip.BackColor = $panelColor
$menuStrip.ForeColor = $fgColor
$menuStrip.Renderer = New-Object System.Windows.Forms.ToolStripProfessionalRenderer
$fileMenu = New-Object System.Windows.Forms.ToolStripMenuItem("File")
$miExplorer = New-Object System.Windows.Forms.ToolStripMenuItem("Open File Explorer")
$miExit = New-Object System.Windows.Forms.ToolStripMenuItem("Exit")
$fileMenu.DropDownItems.AddRange(@($miExplorer, $miExit))
$helpMenu = New-Object System.Windows.Forms.ToolStripMenuItem("Help")
$miAbout = New-Object System.Windows.Forms.ToolStripMenuItem("About")
$helpMenu.DropDownItems.Add($miAbout) | Out-Null
$menuStrip.Items.AddRange(@($fileMenu, $helpMenu))
$form.MainMenuStrip = $menuStrip
$form.Controls.Add($menuStrip)
# --- Date/Time Display ---
$txtDateTime = New-Object System.Windows.Forms.TextBox
$txtDateTime.Font = New-Object System.Drawing.Font("Consolas", 12, [System.Drawing.FontStyle]::Bold)
$txtDateTime.ForeColor = $limeGreen
$txtDateTime.BackColor = $bgColor
$txtDateTime.ReadOnly = $true
$txtDateTime.BorderStyle = 'FixedSingle'
$txtDateTime.TextAlign = 'Center'
$txtDateTime.Location = New-Object System.Drawing.Point(10, 28)
$txtDateTime.Size = New-Object System.Drawing.Size(400, 30)
$form.Controls.Add($txtDateTime)
# --- Remote Execution Group ---
$grpRemote = New-Object System.Windows.Forms.GroupBox
$grpRemote.Text = "Remote Script Execution"
$grpRemote.ForeColor = $fgColor
$grpRemote.Location = New-Object System.Drawing.Point(10, 65)
$grpRemote.Size = New-Object System.Drawing.Size(400, 140)
$lblServer = New-Object System.Windows.Forms.Label
$lblServer.Text = "Server:"
$lblServer.ForeColor = $fgColor
$lblServer.Location = New-Object System.Drawing.Point(10, 25)
$lblServer.Size = New-Object System.Drawing.Size(55, 20)
$cboServers = New-Object System.Windows.Forms.ComboBox
$cboServers.BackColor = $panelColor
$cboServers.ForeColor = $fgColor
$cboServers.DropDownStyle = 'DropDownList'
$cboServers.Location = New-Object System.Drawing.Point(70, 22)
$cboServers.Size = New-Object System.Drawing.Size(230, 22)
$btnAddServer = New-Object System.Windows.Forms.Button
$btnAddServer.Text = "+"
$btnAddServer.BackColor = [System.Drawing.Color]::FromArgb(0,120,80)
$btnAddServer.ForeColor = $fgColor
$btnAddServer.FlatStyle = 'Flat'
$btnAddServer.Location = New-Object System.Drawing.Point(305, 21)
$btnAddServer.Size = New-Object System.Drawing.Size(40, 24)
$btnRemoveServer = New-Object System.Windows.Forms.Button
$btnRemoveServer.Text = "-"
$btnRemoveServer.BackColor = [System.Drawing.Color]::FromArgb(150,40,40)
$btnRemoveServer.ForeColor = $fgColor
$btnRemoveServer.FlatStyle = 'Flat'
$btnRemoveServer.Location = New-Object System.Drawing.Point(350, 21)
$btnRemoveServer.Size = New-Object System.Drawing.Size(40, 24)
$lblScript = New-Object System.Windows.Forms.Label
$lblScript.Text = "Script:"
$lblScript.ForeColor = $fgColor
$lblScript.Location = New-Object System.Drawing.Point(10, 60)
$lblScript.Size = New-Object System.Drawing.Size(55, 20)
$cboScripts = New-Object System.Windows.Forms.ComboBox
$cboScripts.BackColor = $panelColor
$cboScripts.ForeColor = $fgColor
$cboScripts.DropDownStyle = 'DropDownList'
$cboScripts.Location = New-Object System.Drawing.Point(70, 57)
$cboScripts.Size = New-Object System.Drawing.Size(230, 22)
$btnAddScript = New-Object System.Windows.Forms.Button
$btnAddScript.Text = "+"
$btnAddScript.BackColor = [System.Drawing.Color]::FromArgb(0,120,80)
$btnAddScript.ForeColor = $fgColor
$btnAddScript.FlatStyle = 'Flat'
$btnAddScript.Location = New-Object System.Drawing.Point(305, 56)
$btnAddScript.Size = New-Object System.Drawing.Size(40, 24)
$btnRemoveScript = New-Object System.Windows.Forms.Button
$btnRemoveScript.Text = "-"
$btnRemoveScript.BackColor = [System.Drawing.Color]::FromArgb(150,40,40)
$btnRemoveScript.ForeColor = $fgColor
$btnRemoveScript.FlatStyle = 'Flat'
$btnRemoveScript.Location = New-Object System.Drawing.Point(350, 56)
$btnRemoveScript.Size = New-Object System.Drawing.Size(40, 24)
$btnRunScript = New-Object System.Windows.Forms.Button
$btnRunScript.Text = "Run Script on Selected Server"
$btnRunScript.BackColor = [System.Drawing.Color]::FromArgb(0,150,136)
$btnRunScript.ForeColor = $fgColor
$btnRunScript.FlatStyle = 'Flat'
$btnRunScript.Location = New-Object System.Drawing.Point(10, 95)
$btnRunScript.Size = New-Object System.Drawing.Size(380, 32)
$grpRemote.Controls.AddRange(@($lblServer,$cboServers,$btnAddServer,$btnRemoveServer,$lblScript,$cboScripts,$btnAddScript,$btnRemoveScript,$btnRunScript))
$form.Controls.Add($grpRemote)
# --- System Actions Group ---
$grpActions = New-Object System.Windows.Forms.GroupBox
$grpActions.Text = "System Actions"
$grpActions.ForeColor = $fgColor
$grpActions.Location = New-Object System.Drawing.Point(10, 215)
$grpActions.Size = New-Object System.Drawing.Size(400, 175)
$btnRestart = New-Object System.Windows.Forms.Button
$btnRestart.Text = "Restart System"
$btnRestart.BackColor = [System.Drawing.Color]::FromArgb(178,34,34)
$btnRestart.ForeColor = $fgColor
$btnRestart.FlatStyle = 'Flat'
$btnRestart.Location = New-Object System.Drawing.Point(10, 25)
$btnRestart.Size = New-Object System.Drawing.Size(180, 35)
$btnRDP = New-Object System.Windows.Forms.Button
$btnRDP.Text = "RDP"
$btnRDP.BackColor = [System.Drawing.Color]::FromArgb(30,90,160)
$btnRDP.ForeColor = $fgColor
$btnRDP.FlatStyle = 'Flat'
$btnRDP.Location = New-Object System.Drawing.Point(200, 25)
$btnRDP.Size = New-Object System.Drawing.Size(180, 35)
$btnPingTracert = New-Object System.Windows.Forms.Button
$btnPingTracert.Text = "Ping / Tracert"
$btnPingTracert.BackColor = [System.Drawing.Color]::FromArgb(204,120,0)
$btnPingTracert.ForeColor = $fgColor
$btnPingTracert.FlatStyle = 'Flat'
$btnPingTracert.Location = New-Object System.Drawing.Point(10, 70)
$btnPingTracert.Size = New-Object System.Drawing.Size(180, 35)
$btnRemotePS = New-Object System.Windows.Forms.Button
$btnRemotePS.Text = "Remote PowerShell"
$btnRemotePS.BackColor = [System.Drawing.Color]::FromArgb(102,51,153)
$btnRemotePS.ForeColor = $fgColor
$btnRemotePS.FlatStyle = 'Flat'
$btnRemotePS.Location = New-Object System.Drawing.Point(200, 70)
$btnRemotePS.Size = New-Object System.Drawing.Size(180, 35)
$btnCleanup = New-Object System.Windows.Forms.Button
$btnCleanup.Text = "Extensive System Cleanup"
$btnCleanup.BackColor = [System.Drawing.Color]::FromArgb(85,107,47)
$btnCleanup.ForeColor = $fgColor
$btnCleanup.FlatStyle = 'Flat'
$btnCleanup.Location = New-Object System.Drawing.Point(10, 115)
$btnCleanup.Size = New-Object System.Drawing.Size(370, 35)
$grpActions.Controls.AddRange(@($btnRestart,$btnRDP,$btnPingTracert,$btnRemotePS,$btnCleanup))
$form.Controls.Add($grpActions)
# --- Quick Links Group ---
$grpLinks = New-Object System.Windows.Forms.GroupBox
$grpLinks.Text = "Quick Links"
$grpLinks.ForeColor = $fgColor
$grpLinks.Location = New-Object System.Drawing.Point(10, 400)
$grpLinks.Size = New-Object System.Drawing.Size(400, 65)
$cboLinks = New-Object System.Windows.Forms.ComboBox
$cboLinks.BackColor = $panelColor
$cboLinks.ForeColor = $fgColor
$cboLinks.DropDownStyle = 'DropDownList'
$cboLinks.Location = New-Object System.Drawing.Point(10, 25)
$cboLinks.Size = New-Object System.Drawing.Size(280, 22)
$btnOpenLink = New-Object System.Windows.Forms.Button
$btnOpenLink.Text = "Open"
$btnOpenLink.BackColor = [System.Drawing.Color]::FromArgb(0,90,140)
$btnOpenLink.ForeColor = $fgColor
$btnOpenLink.FlatStyle = 'Flat'
$btnOpenLink.Location = New-Object System.Drawing.Point(300, 24)
$btnOpenLink.Size = New-Object System.Drawing.Size(90, 24)
$grpLinks.Controls.AddRange(@($cboLinks,$btnOpenLink))
$form.Controls.Add($grpLinks)
# --- Event Status ---
$lblEventStatus = New-Object System.Windows.Forms.Label
$lblEventStatus.Text = "Event Status:"
$lblEventStatus.ForeColor = $fgColor
$lblEventStatus.Location = New-Object System.Drawing.Point(10, 475)
$lblEventStatus.Size = New-Object System.Drawing.Size(200, 20)
$form.Controls.Add($lblEventStatus)
$txtEventStatus = New-Object System.Windows.Forms.TextBox
$txtEventStatus.Multiline = $true
$txtEventStatus.ScrollBars = 'Vertical'
$txtEventStatus.ReadOnly = $true
$txtEventStatus.BackColor = [System.Drawing.Color]::Black
$txtEventStatus.ForeColor = [System.Drawing.Color]::LightGray
$txtEventStatus.Font = New-Object System.Drawing.Font("Consolas", 9)
$txtEventStatus.Location = New-Object System.Drawing.Point(10, 498)
$txtEventStatus.Size = New-Object System.Drawing.Size(400, 220)
$form.Controls.Add($txtEventStatus)
# --- Current Status Box ---
$lblStatusHeader = New-Object System.Windows.Forms.Label
$lblStatusHeader.Text = "Current Status:"
$lblStatusHeader.ForeColor = $fgColor
$lblStatusHeader.Location = New-Object System.Drawing.Point(10, 728)
$lblStatusHeader.Size = New-Object System.Drawing.Size(200, 20)
$form.Controls.Add($lblStatusHeader)
$lblCurrentStatus = New-Object System.Windows.Forms.Label
$lblCurrentStatus.Text = "Idle"
$lblCurrentStatus.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$lblCurrentStatus.ForeColor = $limeGreen
$lblCurrentStatus.BackColor = $panelColor
$lblCurrentStatus.BorderStyle = 'FixedSingle'
$lblCurrentStatus.TextAlign = 'MiddleCenter'
$lblCurrentStatus.Location = New-Object System.Drawing.Point(10, 750)
$lblCurrentStatus.Size = New-Object System.Drawing.Size(400, 35)
$form.Controls.Add($lblCurrentStatus)
# --- Status Bar ---
$statusStrip = New-Object System.Windows.Forms.StatusStrip
$statusStrip.BackColor = $panelColor
$statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
$statusLabel.Text = "Idle"
$statusLabel.ForeColor = $fgColor
$statusUserLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
$statusUserLabel.Text = "User: $env:USERNAME Host: $env:COMPUTERNAME"
$statusUserLabel.ForeColor = $fgColor
$statusStrip.Items.AddRange(@($statusLabel, $statusUserLabel))
$form.Controls.Add($statusStrip)
#endregion
#region ---- Helper Functions ----
function Add-EventStatus {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "HH:mm:ss"
$txtEventStatus.AppendText("[$timestamp] [$Level] $Message`r`n")
Write-Log -Message $Message -Level $Level -ProcessName "Dash"
}
function Set-CurrentStatus {
param([string]$Status)
$lblCurrentStatus.Text = $Status
$statusLabel.Text = $Status
}
function Refresh-ServerList {
$cboServers.Items.Clear()
if (Test-Path $ServersFile) {
Get-Content $ServersFile | Where-Object { $_.Trim() -ne "" } |
ForEach-Object { $cboServers.Items.Add($_.Trim()) | Out-Null }
}
if ($cboServers.Items.Count -gt 0) { $cboServers.SelectedIndex = 0 }
}
function Refresh-ScriptList {
$cboScripts.Items.Clear()
if (Test-Path $ScriptsPath) {
Get-ChildItem -Path $ScriptsPath -Filter *.ps1 -File |
ForEach-Object { $cboScripts.Items.Add($_.Name) | Out-Null }
}
if ($cboScripts.Items.Count -gt 0) { $cboScripts.SelectedIndex = 0 }
}
function Get-LinksMap {
$map = [ordered]@{}
if (Test-Path $LinksFile) {
Get-Content $LinksFile | Where-Object { $_.Trim() -ne "" } | ForEach-Object {
$parts = $_ -split ',', 2
if ($parts.Count -eq 2) {
$name = $parts[0].Trim()
$url = $parts[1].Trim()
if ($name -and $url) { $map[$name] = $url }
}
}
}
return $map
}
function Refresh-LinksList {
$Global:LinksMap = Get-LinksMap
$cboLinks.Items.Clear()
foreach ($key in $Global:LinksMap.Keys) { $cboLinks.Items.Add($key) | Out-Null }
if ($cboLinks.Items.Count -gt 0) { $cboLinks.SelectedIndex = 0 }
}
function Start-RemoteJob {
param(
[string]$Server,
[scriptblock]$ScriptBlock,
[string]$Description,
[switch]$RequireCredential
)
Set-CurrentStatus "$Description..."
Add-EventStatus "$Description started on $Server."
$cred = $null
if ($RequireCredential) {
$cred = Get-Credential -Message "Admin credentials for $Server"
if (-not $cred) {
Add-EventStatus "$Description cancelled - no credentials provided." "WARN"
Set-CurrentStatus "Idle"
return
}
}
try {
if ($cred) {
$job = Invoke-Command -ComputerName $Server -Credential $cred -ScriptBlock $ScriptBlock -AsJob -JobName $Description
} else {
$job = Invoke-Command -ComputerName $Server -ScriptBlock $ScriptBlock -AsJob -JobName $Description
}
$Global:ActiveJobs[$job.Id] = @{ Job = $job; Description = $Description; Server = $Server }
} catch {
Add-EventStatus "Failed to start '$Description' on $Server : $_" "ERROR"
Set-CurrentStatus "Idle"
}
}
#endregion
#region ---- Action Functions ----
function Invoke-RunScript {
$server = $cboServers.SelectedItem
$script = $cboScripts.SelectedItem
if (-not $server -or -not $script) { Add-EventStatus "Select a server and a script first." "WARN"; return }
$scriptPath = Join-Path $ScriptsPath $script
if (-not (Test-Path $scriptPath)) { Add-EventStatus "Script not found: $script" "ERROR"; return }
$sb = [scriptblock]::Create((Get-Content $scriptPath -Raw))
Start-RemoteJob -Server $server -ScriptBlock $sb -Description "Run $script" -RequireCredential
}
function Invoke-RestartSystem {
$server = $cboServers.SelectedItem
if (-not $server) { Add-EventStatus "Select a server first." "WARN"; return }
$confirm = [System.Windows.Forms.MessageBox]::Show("Restart $server now?", "Confirm Restart", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Warning)
if ($confirm -ne [System.Windows.Forms.DialogResult]::Yes) { return }
$cred = Get-Credential -Message "Admin credentials for $server"
if (-not $cred) { return }
try {
Restart-Computer -ComputerName $server -Credential $cred -Force -ErrorAction Stop
Add-EventStatus "Restart command sent to $server."
} catch {
Add-EventStatus "Restart failed on $server : $_" "ERROR"
}
}
function Invoke-RDP {
$server = $cboServers.SelectedItem
if (-not $server) { Add-EventStatus "Select a server first." "WARN"; return }
Start-Process "mstsc.exe" -ArgumentList "/v:$server"
Add-EventStatus "RDP session launched for $server."
}
function Invoke-PingTracert {
$server = $cboServers.SelectedItem
if (-not $server) { Add-EventStatus "Select a server first." "WARN"; return }
Set-CurrentStatus "Ping/Tracert $server..."
$job = Start-Job -ScriptBlock {
param($t)
$out = @()
$out += "--- PING $t ---"
$out += (ping -n 4 $t | Out-String)
$out += "--- TRACERT $t ---"
$out += (tracert -h 20 $t | Out-String)
return ($out -join "`r`n")
} -ArgumentList $server
$Global:ActiveJobs[$job.Id] = @{ Job = $job; Description = "Ping/Tracert"; Server = $server }
Add-EventStatus "Ping/Tracert started for $server."
}
function Invoke-RemotePS {
$server = $cboServers.SelectedItem
if (-not $server) { Add-EventStatus "Select a server first." "WARN"; return }
$cmd = "Enter-PSSession -ComputerName $server -Credential (Get-Credential -Message 'Admin credentials for $server')"
Start-Process "powershell.exe" -ArgumentList "-NoExit -Command $cmd"
Add-EventStatus "Remote PowerShell session launched for $server."
}
function Invoke-Cleanup {
$server = $cboServers.SelectedItem
$sb = {
$results = @()
$paths = @(
"$env:WINDIR\Temp\*",
"$env:TEMP\*",
"$env:WINDIR\SoftwareDistribution\Download\*",
"$env:WINDIR\Prefetch\*"
)
foreach ($p in $paths) {
try { Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue; $results += "Cleared: $p" }
catch { $results += "Failed: $p" }
}
try { Clear-RecycleBin -Force -ErrorAction SilentlyContinue; $results += "Recycle Bin cleared." } catch {}
$results += "Cleanup completed on $env:COMPUTERNAME at $(Get-Date)"
return ($results -join "`r`n")
}
if ($server) {
Start-RemoteJob -Server $server -ScriptBlock $sb -Description "Extensive Cleanup" -RequireCredential
} else {
Set-CurrentStatus "Running local cleanup..."
Add-EventStatus "Extensive cleanup started on local machine."
$job = Start-Job -ScriptBlock $sb
$Global:ActiveJobs[$job.Id] = @{ Job = $job; Description = "Extensive Cleanup"; Server = $env:COMPUTERNAME }
}
}
function Invoke-OpenLink {
$name = $cboLinks.SelectedItem
if (-not $name) { Add-EventStatus "Select a website first." "WARN"; return }
$url = $Global:LinksMap[$name]
if ($url) {
Start-Process $url
Add-EventStatus "Opened link: $name"
}
}
#endregion
#region ---- Event Wiring ----
$miExplorer.Add_Click({ Start-Process explorer.exe })
$miExit.Add_Click({ $form.Close() })
$miAbout.Add_Click({
[System.Windows.Forms.MessageBox]::Show("Dash - Server Management Console`r`nPowerShell 5.1 / WinForms`r`n$(Get-Date -Format yyyy)", "About Dash", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
})
$btnAddServer.Add_Click({
$newServer = [Microsoft.VisualBasic.Interaction]::InputBox("Enter server name to add:", "Add Server", "")
if ($newServer) {
Add-Content -Path $ServersFile -Value $newServer.Trim()
Refresh-ServerList
Add-EventStatus "Added server: $newServer"
}
})
$btnRemoveServer.Add_Click({
$sel = $cboServers.SelectedItem
if ($sel) {
$confirm = [System.Windows.Forms.MessageBox]::Show("Remove '$sel' from server list?", "Confirm", [System.Windows.Forms.MessageBoxButtons]::YesNo)
if ($confirm -eq [System.Windows.Forms.DialogResult]::Yes) {
(Get-Content $ServersFile) | Where-Object { $_.Trim() -ne $sel } | Set-Content $ServersFile
Refresh-ServerList
Add-EventStatus "Removed server: $sel"
}
}
})
$btnAddScript.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "PowerShell Scripts (*.ps1)|*.ps1"
$ofd.Title = "Select PowerShell script to add"
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
Copy-Item -Path $ofd.FileName -Destination $ScriptsPath -Force
Refresh-ScriptList
Add-EventStatus "Added script: $(Split-Path $ofd.FileName -Leaf)"
}
})
$btnRemoveScript.Add_Click({
$sel = $cboScripts.SelectedItem
if ($sel) {
$confirm = [System.Windows.Forms.MessageBox]::Show("Delete script '$sel'?", "Confirm", [System.Windows.Forms.MessageBoxButtons]::YesNo)
if ($confirm -eq [System.Windows.Forms.DialogResult]::Yes) {
Remove-Item -Path (Join-Path $ScriptsPath $sel) -Force
Refresh-ScriptList
Add-EventStatus "Removed script: $sel"
}
}
})
$btnRunScript.Add_Click({ Invoke-RunScript })
$btnRestart.Add_Click({ Invoke-RestartSystem })
$btnRDP.Add_Click({ Invoke-RDP })
$btnPingTracert.Add_Click({ Invoke-PingTracert })
$btnRemotePS.Add_Click({ Invoke-RemotePS })
$btnCleanup.Add_Click({ Invoke-Cleanup })
$btnOpenLink.Add_Click({ Invoke-OpenLink })
#endregion
#region ---- Timers ----
$clockTimer = New-Object System.Windows.Forms.Timer
$clockTimer.Interval = 1000
$clockTimer.Add_Tick({
$txtDateTime.Text = Get-Date -Format "dddd, MMMM dd, yyyy hh:mm:ss tt"
})
$jobTimer = New-Object System.Windows.Forms.Timer
$jobTimer.Interval = 1000
$jobTimer.Add_Tick({
if ($Global:ActiveJobs.Count -eq 0) { return }
$doneIds = @()
foreach ($id in @($Global:ActiveJobs.Keys)) {
$info = $Global:ActiveJobs[$id]
$job = $info.Job
if ($job.State -in @('Completed','Failed','Stopped')) {
try {
$output = Receive-Job -Job $job -ErrorAction SilentlyContinue
if ($output) {
$outText = ($output | Out-String).Trim()
$txtEventStatus.AppendText("`r`n--- $($info.Description) on $($info.Server) ---`r`n$outText`r`n")
}
if ($job.State -eq 'Failed') {
Add-EventStatus "$($info.Description) on $($info.Server) FAILED." "ERROR"
} else {
Add-EventStatus "$($info.Description) on $($info.Server) completed."
}
} catch {
Add-EventStatus "Error retrieving job output: $_" "ERROR"
}
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
$doneIds += $id
}
}
foreach ($id in $doneIds) { $Global:ActiveJobs.Remove($id) }
if ($Global:ActiveJobs.Count -eq 0) { Set-CurrentStatus "Idle" }
})
#endregion
#region ---- Startup / Shutdown ----
$form.Add_Shown({
Refresh-ServerList
Refresh-ScriptList
Refresh-LinksList
$clockTimer.Start()
$jobTimer.Start()
Add-EventStatus "Dash started."
})
$form.Add_FormClosing({
$clockTimer.Stop()
$jobTimer.Stop()
Get-Job | Where-Object { $Global:ActiveJobs.ContainsKey($_.Id) } | Remove-Job -Force -ErrorAction SilentlyContinue
Write-Log -Message "Dash closed." -ProcessName "Dash"
})
[void]$form.ShowDialog()
#endregion