functions/Invoke-SystemStatus.ps1

using namespace Terminal.Gui

function Invoke-SystemStatus {
    [cmdletbinding()]
    [Alias('TuiStatus')]
    param(
        [Parameter(Position = 0, HelpMessage = 'The name of the computer to monitor. You must have admin rights.')]
        [Alias('CN')]
        [ValidateNotNullOrEmpty()]
        [string]$Computername = $env:COMPUTERNAME,

        [Parameter(HelpMessage = 'Alternate credentials for a remote computer.')]
        [Alias('RunAs')]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,

        [Parameter(HelpMessage = 'Specify the window foreground color. The background will be Black.')]
        [Alias('color')]
        [ValidateNotNullOrEmpty()]
        [Terminal.Gui.Color]$WindowColor = 'BrightYellow'
    )

    if ($IsMacOS -OR $IsLinux) {
        Write-Warning "This command requires a Windows platform."
        return
    }
    If ($host.name -ne 'ConsoleHost') {
        Write-Warning 'This should be run in a PowerShell console host.'
        Return
    }
    #region initialize
    #define the main Window title here
    $windowTitle = 'System Status Report'

    #Emojis don't always display as expected.
    #Not all of these will be used in this function
    $gear = '⚙'
    $wrench = '🔧'
    $info = 'ℹ'
    $disk = '💽'

    #helper functions
    function ConvertTo-DataTable {
        [cmdletbinding()]
        [OutputType('System.Data.DataTable')]
        param(
            [Parameter(
                Mandatory,
                Position = 0,
                ValueFromPipeline
            )]
            [ValidateNotNullOrEmpty()]
            [object]$InputObject
        )

        begin {
            Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
            Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"
            $data = [System.Collections.Generic.List[object]]::New()
            $Table = [System.Data.DataTable]::New('PSData')
        } #begin

        process {
            $Data.Add($InputObject)
        } #process

        end {
            Write-Verbose "[$((Get-Date).TimeOfDay) END ] Building a table of $($data.count) items"
            #define columns
            foreach ($item in $data[0].PSObject.Properties) {
                Write-Verbose "[$((Get-Date).TimeOfDay) END ] Defining column $($item.name)"
                [void]$table.Columns.Add($item.Name, $item.TypeNameOfValue)
            }
            #add rows
            for ($i = 0; $i -lt $Data.count; $i++) {
                $row = $table.NewRow()
                foreach ($item in $Data[$i].PSObject.Properties) {
                    $row.Item($item.name) = $item.Value
                }
                [void]$table.Rows.Add($row)
            }
            #This is a trick to return the table object
            #as the output and not the rows
            , $table
            Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
        } #end

    } #close ConvertTo-DataTable
    function GetData {
        param()

        $splat = @{
            Computername = $txtCN.Text.ToString()
            ErrorAction  = 'Stop'
        }
        if ($credential) {
            $splat.Add('Credential', $Credential)
        }
        elseif (($txtUser.Text.length -gt 0) -and ($txtPass.Text.length -gt 0)) {
            $pw = ConvertTo-SecureString -String $($txtPass.Text.ToString()) -AsPlainText -Force
            $username = $txtUser.Text.ToString()
            $tmpCred = [PSCredential]::new($username, $pw)
            $splat.Add('Credential', $tmpCred)
        }

        try {
            $cimSess = New-CimSession @splat

            $script:online = $True
            $script:cDrive = Get-CimInstance Win32_LogicalDisk -Property Size, Freespace -Filter "DeviceID='C:'" -CimSession $cimSess
            $script:os = Get-CimInstance Win32_OperatingSystem -Property FreePhysicalMemory, TotalVisibleMemorySize, LastBootUpTime, Caption, OSArchitecture -CimSession $cimSess
            $script:svc = Get-CimInstance Win32_Service -Filter "State='Running'" -Property Name -CimSession $cimSess
            $script:cs = Get-CimInstance Win32_ComputerSystem -Property SystemFamily, Manufacturer, Model -CimSession $cimSess
            $script:proc = Get-CimInstance Win32_Process -Property ProcessID, Name, WorkingSetSize, CreationDate -CimSession $cimSess
            $script:topProc = $script:proc | Sort-Object -Property WorkingSetSize -Descending | Select-Object -First 10 -Property @{Name = 'ID'; Expression = { $_.ProcessID } },
            Name,
            @{Name = 'Runtime'; Expression = { '{0:dd\.hh\:mm\:ss}' -f (New-TimeSpan -Start $_.CreationDate -End (Get-Date)) } },
            @{Name = 'WS(M)'; Expression = { $_.WorkingSetSize / 1MB -as [int] } }
            $script:hardware = $script:cs.SystemFamily ? $script:cs.SystemFamily : "$($script:cs.Manufacturer): $($script:cs.Model)"
            $script:uptime = New-TimeSpan -Start $script:os.LastBootUpTime -End (Get-Date)
            #this code has not been validated against a machine with multiple physical processors
            $script:cpu = Get-CimInstance Win32_Processor -Property LoadPercentage, Name, NumberOfCores, NumberOfLogicalProcessors -CimSession $cimSess

            Remove-CimSession $cimSess

        }
        catch {
            $script:online = $false
            $StatusBar.Items[2].Title = "Failed to get system information from $($splat.Computername)"
            $tvInfo.Text = 'Error: {0}' -f $_.Exception.Message
            $n = [Terminal.Gui.Attribute]::new('BrightRed', 'Black')
            $cs = [ColorScheme]::new()
            $cs.disabled = $n
            $cs.HotNormal = $n
            $tvInfo.ColorScheme = $cs
            [Application]::Refresh()
        }

        #clear credentials
        if ($txtUser.Text.Length -gt 0) {
            $txtUser.Text = ''
        }
        if ($txtPass.Text.Length -gt 0) {
            $txtPass.Text = ''
        }
    }
    function refresh {
        param()

        $CN = $txtCN.Text.ToString()
        $StatusBar.Items[2].Title = "Refreshing system information from $CN"

        $tvInfo.Text = ''
        $lblRun.Text = ''
        $lblUsed.Text = ''
        $progC.Fraction = 0
        $progC.Visible = $False
        $progMem.Visible = $False
        $progCPU.Visible = $false
        $lblCPU.Text = ''
        $lblCPULoad.Text = ''
        $lblMemUsed.Text = ''
        $tableView.Table = ''
        $TableView.SetNeedsDisplay()
        $procFrame.SetNeedsDisplay()

        [application]::Refresh()
        GetData
        if ($script:online) {

            $tvInfo.Text = @"
 
$($script:os.Caption)
$($script:hardware)
$($script:os.OSArchitecture)
"@


            $n = [Terminal.Gui.Attribute]::new('BrightGreen', 'Black')
            $cs = [ColorScheme]::new()
            $cs.normal = $n
            $cs.Disabled = $n
            $tvInfo.ColorScheme = $cs

            $lblRun.Text = @'
 
 Running Processes: {0}
 Running Services : {1}
 System Uptime : {2:dd\.hh\:mm\:ss}
'@
 -f $script:proc.count, $script:svc.count, $script:Uptime

            $TableView.table = $script:topProc | ConvertTo-DataTable
            #set table style
            $tableStyle = [TableView+TableStyle]@{
                ShowVerticalCellLines         = $False
                ShowVerticalHeaderLines       = $False
                AlwaysShowHeaders             = $True
                ShowHorizontalHeaderOverline  = $False
                ShowHorizontalHeaderUnderline = $True
                ExpandLastColumn              = $False
            }
            $tableStyle.ColumnStyles.Add(
                $TableView.Table.Columns['ID'],
                [TableView+ColumnStyle]@{
                    Alignment = 'Left'
                    MinWidth  = 6
                }
            )
            $tableStyle.ColumnStyles.Add(
                $TableView.Table.Columns['Name'],
                [TableView+ColumnStyle]@{
                    Alignment = 'Left'
                    MinWidth  = 15
                    MaxWidth  = 30
                }
            )
            $tableStyle.ColumnStyles.Add(
                $TableView.Table.Columns['WS(M)'],
                [TableView+ColumnStyle]@{
                    Alignment = 'Right'
                    MinWidth  = 5
                }
            )
            $TableView.Style = $tableStyle
            $TableView.SetNeedsDisplay()
            $procFrame.SetNeedsDisplay()

            $cUsed = $script:cDrive.size - $Script:cDrive.Freespace
            $progC.Fraction = $cUsed / $script:cDrive.Size
            $lblUsed.Text = 'Used: {0:N2}GB Free: {1:n2}GB' -f ($cUsed / 1gb), ($script:cDrive.Freespace / 1gb)
            $progC.Visible = $True

            #FreePhysicalMemory, TotalVisibleMemorySize
            $memUsed = $script:os.TotalVisibleMemorySize - $script:os.FreePhysicalMemory
            $progMem.Fraction = $memUsed / $script:os.TotalVisibleMemorySize
            $lblMemUsed.Text = 'Used: {0:N2}GB Free: {1:n2}GB' -f ($memUsed / 1mb), ($script:os.FreePhysicalMemory / 1mb)
            $progMem.Visible = $True

            $progCPU.Visible = $True
            $progCPU.Fraction = $script:cpu.LoadPercentage / 100
            $lblCPU.Text = $script:cpu.Name
            $lblCPULoad.Text = 'Load: {0}% Cores: {1} Logical CPUs: {2}' -f $script:cpu.LoadPercentage, $script:cpu.NumberOfCores, $script:cpu.NumberOfLogicalProcessors
        } #online
        else {
            #items should have already been cleared
        }

        [Application]::Refresh()
    } #refresh

    function Stop-RefreshTimer {
        if ($script:refreshToken) {
            [Application]::MainLoop.RemoveTimeout($script:refreshToken)
            $script:refreshToken = $null
        }
    }

    function Start-RefreshTimer {
        $seconds = $script:refreshDefaultSeconds

        $script:refreshToken = [Application]::MainLoop.AddTimeout(
            [TimeSpan]::FromSeconds($Seconds),
            {
                refresh
                $StatusBar.Items[2].Title = 'Ready'
                $StatusBar.Items[0].Title = "Last update: $(Get-Date -Format T)"
                [Application]::Refresh()
                $true
            }
        )
    }

    [Application]::Init()
    [Application]::QuitKey = 'Esc'

    #endregion

    #region create the main window and status bar
    $window = [Window]@{
        Title = $windowTitle
    }
    $window.add_Loaded({
            refresh
            $StatusBar.Items[2].Title = 'Ready'
            $script:refreshToken = $null
            $script:refreshDefaultSeconds = $txtInterval.text.ToString()
        })

    #customize the Window color
    $n = [Terminal.Gui.Attribute]::new($WindowColor, 'Black')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $Window.ColorScheme = $cs

    $StatusBar = [StatusBar]::New(
        @(
            [StatusItem]::New('Unknown', "Last update: $(Get-Date -Format T)", {}),
            [StatusItem]::New('Unknown', 'ESC to quit', {}),
            [StatusItem]::New('Unknown', 'Ready', {})
        )
    )

    #Set the status bar color the same as the window
    $StatusBar.ColorScheme = $cs

    [Application]::Top.add($StatusBar)

    #endregion

    #region add controls

    #region computername
    $lblCN = [Label]@{
        X    = 1
        Y    = 1
        Text = 'Computername:'
    }
    $window.Add($lblCN)

    $txtCN = [TextField]@{
        X     = $lblCN.Frame.Width + 2
        Y     = 1
        Width = 20
        Text  = $Computername.ToUpper()
    }

    $txtCN.Add_TextChanged({
            $txtCN.Text = $txtCN.Text.ToUpper()
        })

    $window.Add($txtCN)
    #endregion

    #region alternate credentials
    $CredentialFrame = [FrameView]@{
        X      = 1
        Y      = 2
        width  = 35
        Height = 4
        Title  = 'Credentials'
    }

    $lblUser = [Label]@{
        Text = 'Username:'
        X    = 1
        Y    = 0
    }
    $CredentialFrame.Add($lblUser)

    $txtUser = [TextField]@{
        X     = $lblUser.Frame.Width + 2
        Y     = $lblUser.Y
        Width = 25
    }
    $CredentialFrame.Add($txtUser)

    $lblPass = [Label]@{
        Text = 'Password:'
        X    = 1
        Y    = 1
    }
    $CredentialFrame.Add($lblPass)
    $txtPass = [TextField]@{
        X      = $lblUser.Frame.Width + 2
        Y      = $lblPass.Y
        Width  = 25
        Secret = $True
    }
    $CredentialFrame.Add($txtPass)

    $Window.Add($CredentialFrame)
    #endregion

    #region info view
    $tvInfo = [TextView]@{
        X        = $CredentialFrame.Frame.Width + 2
        Y        = 0
        Height   = 5
        Width    = 50
        WordWrap = $True
        ReadOnly = $True
        AutoSize = $True
        Text     = ''
    }

    #set the info text to Green
    $n = [Terminal.Gui.Attribute]::new('Green', 'Black')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $tvInfo.ColorScheme = $cs
    $window.Add($tvInfo)
    #endregion

    #region run info
    $runFrame = [FrameView]@{
        X      = 1
        Y      = $Credential.Frame.Height + 6
        Width  = 35
        Height = 7
        Title  = 'ℹ Run Information'
    }

    $lblRun = [Label]@{
        Text = @'
 
 Running Processes: {0}
 Running Services : {1}
 System Uptime : {2:dd\.hh\:mm\:ss}
'@
 -f 0, 0, 0
    }
    $n = [Terminal.Gui.Attribute]::new('BrightCyan', 'Black')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $lblRun.ColorScheme = $cs
    $runFrame.Add($lblRun)
    $window.Add($runFrame)
    #endregion

    #region process info
    $procFrame = [FrameView]@{
        X        = 1
        Y        = $runFrame.Frame.Height + 6
        Width    = 60
        Height   = 15
        AutoSize = $True
        Title    = "$gear Top 10 Processes"
    }

    #define a table
    $TableView = [TableView]@{
        X             = 1
        Y             = 1
        Width         = [Dim]::Fill()
        Height        = [Dim]::Fill()
        TabStop       = $False
        MultiSelect   = $False
        FullRowSelect = $True
        TextAlignment = 'Center'
    }

    $n = [Terminal.Gui.Attribute]::new('BrightMagenta', 'Black')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $TableView.ColorScheme = $cs

    $procFrame.Add($TableView)

    $window.Add($procFrame)
    #endregion

    #region physical info
    $physFrame = [FrameView]@{
        X      = $procFrame.Frame.Width + 2
        Y      = $runFrame.Y - 3
        Width  = 52
        Height = 15
        Title  = '🌟Physical Information'
    }

    $lblDisk = [Label]@{
        X    = 1
        Y    = 1
        Text = 'Disk Usage C:\'
    }
    $physFrame.Add($lblDisk)

    $progC = [ProgressBar]@{
        X                 = 1
        Y                 = 2
        Text              = 'C:\'
        Fraction          = 1
        # Simple, SimplePlusPercentage, Framed, FramedPlusPercentage, FramedProgressPadded
        ProgressBarFormat = 'simple'
        #Blocks, Continuous, MarqueeBlocks, MarqueeContinuous""
        ProgressBarStyle  = 'continuous'
        Width             = $physFrame.Frame.width - 5
    }
    $n = [Terminal.Gui.Attribute]::new('red', 'green')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $progC.ColorScheme = $cs
    $physFrame.Add($progC)

    $lblUsed = [Label]@{
        X    = 5 #$progC.X + 1
        Y    = $progC.y + 1
        Text = 'Used: {0}GB Free: {1}GB' -f 0, 0
    }
    $physFrame.Add($lblUsed)

    $lblMem = [Label]@{
        X    = $lblDisk.X
        Y    = $lblUsed.Y + 2
        Text = 'Memory Usage'
    }
    $physFrame.Add($lblMem)

    $progMem = [ProgressBar]@{
        X                 = 1
        Y                 = $lblMem.Y + 1
        Text              = 'Memory'
        Fraction          = 1
        # Simple, SimplePlusPercentage, Framed, FramedPlusPercentage, FramedProgressPadded
        ProgressBarFormat = 'simple'
        #Blocks, Continuous, MarqueeBlocks, MarqueeContinuous""
        ProgressBarStyle  = 'continuous'
        Width             = $physFrame.Frame.width - 5
    }
    $n = [Terminal.Gui.Attribute]::new('red', 'green')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $progMem.ColorScheme = $cs

    $physFrame.Add($progMem)
    $lblMemUsed = [Label]@{
        X    = 5
        Y    = $progMem.y + 1
        Text = 'Used: {0}GB Free: {1}GB' -f 0, 0
    }
    $physFrame.Add($lblMemUsed)

    $lblCPU = [Label]@{
        X    = 1
        Y    = $lblMemUsed.Y + 2
        Text = 'CPU Load:'
    }

    $physFrame.Add($lblCPU)
    $progCPU = [ProgressBar]@{
        X                 = 1
        Y                 = $lblCpu.Y + 1
        Text              = 'LoadPercentage'
        Fraction          = 0
        # Simple, SimplePlusPercentage, Framed, FramedPlusPercentage, FramedProgressPadded
        ProgressBarFormat = 'simple'
        #Blocks, Continuous, MarqueeBlocks, MarqueeContinuous""
        ProgressBarStyle  = 'continuous'
        Width             = $physFrame.Frame.width - 5
    }
    $n = [Terminal.Gui.Attribute]::new('red', 'green')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $progCPU.ColorScheme = $cs
    $physFrame.Add($progCPU)

    $lblCPULoad = [Label]@{
        X    = 5
        Y    = $progCPU.Y + 1
        Text = 'LoadPercentage'
    }
    $physFrame.Add($lblCPULoad)
    $window.Add($physFrame)
    #endregion

    #region help info
    $usage = @'
 Instructions:
 Enter a computer name and alternate credentials
 if necessary. Click the Refresh button or the
 Alt+R shortcut to manually refresh information.
 
 You can also set an automatic refresh interval.
 Click the Timer button to stop and start. If you
 change the computer, you should stop the timer
 first. Restart it after changing the computer name.
 
 Use the Quit button or Alt+Q to exit.
 
 Click these instructions to hide them.
'@


    $showUsage = @'
 
 
 
 
 
                Click to show help
'@

    $txtHelp = [TextView]@{
        X        = $procFrame.Frame.Right + 1
        Y        = $physFrame.Frame.Bottom
        Width    = [Dim]::Percent(80)
        Height   = 14
        ReadOnly = $True
        Text     = $usage
    }
    $script:showUsage = $True
    $window.Add($txtHelp)

    $txtHelp.Add_MouseClick({
            if ($script:showUsage) {
                $txtHelp.Text = $showUsage
                $script:showUsage = $False
            }
            else {
                $txtHelp.Text = $usage
                $script:showUsage = $True
            }
            [Application]::Refresh()
        })

    #endregion

    #region buttons
    $btnRefresh = [Button]@{
        X        = 1
        Y        = 30
        Text     = 'Refresh'
        TabIndex = 0
    }

    $btnRefresh.Add_Clicked({
            $cn = $txtCN.Text.ToString()
            refresh $cn
            if ($script:online) {
                $StatusBar.Items[2].Title = 'Ready'
            }
            $StatusBar.Items[0].Title = "Last update: $(Get-Date -Format T)"
            [Application]::Refresh()
        })
    $window.Add($btnRefresh)

    $btnQuit = [Button]@{
        X        = $btnRefresh.Frame.Width + 2
        Y        = 30
        Text     = '_Quit'
        TabIndex = 1
    }

    $btnQuit.Add_Clicked({
            [Application]::RequestStop()
        })
    $window.Add($btnQuit)
    #endregion

    #region timer controls
    $lblInterval = [Label]@{
        X    = $runFrame.Frame.Width + 2
        Y    = $runFrame.Frame.Top - 1
        Text = 'Auto refresh (sec):'
    }
    $window.Add($lblInterval)

    $txtInterval = [TextField]@{
        X     = $lblInterval.Frame.Right + 1
        Y     = $lblInterval.Y
        Width = 4
        Text  = '30'
    }
    $script:refreshDefaultSeconds = $txtInterval.Text.ToString()

    $txtInterval.Add_Leave({
            $script:refreshDefaultSeconds = $txtInterval.Text.ToString()
            $StatusBar.Items[2].Title = "Auto refresh set to $script:refreshDefaultSeconds seconds"
            [Application]::Refresh()
            if ($script:refreshToken) {
                $StatusBar.Items[2].Title = 'Restarting Timer'
                Stop-RefreshTimer
                Start-RefreshTimer
            }
            else {
                Start-Sleep -Seconds 2
                $StatusBar.Items[2].Title = 'Ready'
            }
            [Application]::Refresh()
        })
    $window.Add($txtInterval)

    $btnSetTimer = [Button]@{
        X        = $lblInterval.X
        Y        = $txtInterval.Y + 1
        Text     = 'Start Timer'
        TabIndex = 2
    }
    $btnSetTimer.Add_Clicked({
            #toggle the button text
            if ($btnSetTimer.Text.ToString() -match 'Start') {
                $btnSetTimer.Text = 'Stop Timer'
                $seconds = $txtInterval.Text.ToString()
                $txtInterval.Text = $seconds.ToString()
                Start-RefreshTimer
                $StatusBar.Items[2].Title = "Starting $seconds seconds refresh timer"
            }
            else {
                $btnSetTimer.Text = 'Start Timer'
                Stop-RefreshTimer
            }
            [Application]::Refresh()
        })
    $window.Add($btnSetTimer)

    #endregion

    #endregion controls

    #region display

    [Application]::Top.Add($window)
    [Application]::Run()
    [Application]::ShutDown()

    #endregion
}