SysTrayHost/SysTrayHost.ps1.txt

 
class SysTrayHost {
 
    hidden [System.Drawing.Bitmap] $_systrayBitmap = $null
    hidden [System.Drawing.Bitmap] $_powershellBitmap = $null
    hidden [System.Drawing.Bitmap] $_cloudBitmap = $null
    hidden [System.Drawing.Bitmap] $_hierarchyBitmap = $null
    hidden [System.Drawing.Bitmap] $_folderBitmap = $null
    hidden [System.Drawing.Bitmap] $_sharepointBitmap = $null
 
    hidden [System.Windows.Forms.NotifyIcon] $_systray = $null
    hidden [System.Windows.Forms.ContextMenuStrip] $_contextmenu = $null
 
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemOperations = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemEcoSystem = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemShell = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemShellVerbose = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemScheduler = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemProfile = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemLib = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemCache = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemDocs = $null
    hidden [System.Windows.Forms.ToolStripMenuItem] $_menuitemExit = $null
 
    hidden [System.Collections.ArrayList] $_processes = [System.Collections.ArrayList]::new()
    hidden [System.Collections.ArrayList] $_hiddenprocesses = [System.Collections.ArrayList]::new()
 
    hidden $_procId = $null
    hidden [Boolean] $_procHide = $false
    hidden [Boolean] $_procTerminate = $false
 
    hidden $_command = $null
    hidden $_filter = $null
    hidden [System.Collections.Stack] $_nodesStack = $null
 
    hidden $_pipeSysTray = $null
    hidden $_pipeExplorer = $null
    hidden $_pipeCustomizer = $null
    hidden $_explorer = $null
    hidden $_customizer = $null
 
    #--------------------
    # Constructor
    #--------------------
    SysTrayHost() {
        $this._systrayBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-engineering-50.png")
        $this._powershellBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-powershell-filled-50.png")
        $this._cloudBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-clouds-30.png")
        $this._hierarchyBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-hierarchy-32.png")
        $this._folderBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-folder-48.png")
        $this._sharepointBitmap = [System.Drawing.Bitmap]::FromFile("$PSScriptRoot\Images\icons8-microsoft-sharepoint-48.png")
 
        $this.initializeControls()
        $this.buildFormLayout()
        $this.initializePipes()
        if([boolean]($global:SRxEnv.Program.EnabledExplorer)) { $this.newExplorer() }
    }
    hidden [void] initializeControls() {
        $this._systray = [System.Windows.Forms.NotifyIcon]::new()
        $this._contextmenu = [System.Windows.Forms.ContextMenuStrip]::new()
        $this._menuitemShell = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemOperations = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemEcoSystem = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemShell = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemShellVerbose = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemScheduler = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemProfile = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemLib = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemCache = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemDocs = [System.Windows.Forms.ToolStripMenuItem]::new()
        $this._menuitemExit = [System.Windows.Forms.ToolStripMenuItem]::new()
     
    }
    hidden [void] buildFormLayout() {
        #
        # _systray
        #
        $this._systray.Icon = [System.Drawing.Icon]::FromHandle($this._systrayBitmap.GetHicon())
        $this._systray.Text = $global:SRxEnv.DefaultSettings.EcoSystem.SysTrayUI.ts_Title #
        $this._systray.Visible = $global:SRxEnv.Program.EnabledSysTray
        $this._systray.Tag = $this
        $this._systray.add_MouseDown({
            param($source, $e)
            $_this = $source.Tag
 
            $connected = Get-SRxIsConnected
            $_this._menuitemOperations.Enabled = $connected
            $_this._menuitemEcoSystem.Enabled = $connected
            $_this._menuitemShell.Enabled = $connected
            $_this._menuitemShellVerbose.Enabled = $connected
            $_this._menuitemScheduler.Enabled = $false
            $_this._menuitemLib.Enabled = $connected
        })
        $this._systray.add_DoubleClick({
            param($source, $e)
            $_this = $source.Tag
 
            $_this.newExplorer()
        })
        $this._systray.add_MouseMove({
            param($source, $e)
            $_this = $source.Tag
 
            if( Get-SRxIsConnected ) { $_this._systray.Text = "$($global:SRxEnv.DefaultSettings.EcoSystem.SysTrayUI.ts_Title) - $($global:SRxEnv.Tenancy.TermGroupName)" }
            else { $_this._systray.Text = "$($global:SRxEnv.DefaultSettings.EcoSystem.SysTrayUI.ts_Title) - offline" }
        })
 
        $this._contextmenu.Items.Add($this._menuitemOperations)
        $this._contextmenu.Items.Add($this._menuitemEcoSystem)
        $this._contextmenu.Items.Add("-")
        $this._contextmenu.Items.Add($this._menuitemShell)
        $this._contextmenu.Items.Add($this._menuitemShellVerbose)
        $this._contextmenu.Items.Add("-")
        $this._contextmenu.Items.Add($this._menuitemScheduler)
        $this._contextmenu.Items.Add("-")
        $this._contextmenu.Items.Add($this._menuitemProfile)
        $this._contextmenu.Items.Add($this._menuitemLib)
        $this._contextmenu.Items.Add($this._menuitemCache)
        $this._contextmenu.Items.Add("-")
        $this._contextmenu.Items.Add($this._menuitemDocs)
        $this._contextmenu.Items.Add("-")
        $this._contextmenu.Items.Add($this._menuitemExit)
 
        $this._systray.ContextMenuStrip = $this._contextmenu
        #
        # _menuitemOperations
        #
        $this._menuitemOperations.Text = "Operations"
        #$this._menuitemOperations.DefaultItem = $true
        $this._menuitemOperations.Image = $this._hierarchyBitmap
        $this._menuitemOperations.Tag = $this
        $this._menuitemOperations.add_Click({
            param($source, $e)
            $_this = $source.Tag
 
            $_this.newExplorer()
        })
        #
        # _menuitemEcoSystem
        #
        $this._menuitemEcoSystem.Text = "EcoSystem"
        $this._menuitemEcoSystem.Tag = $this
        $this._menuitemEcoSystem.Image = $this._sharepointBitmap
        $this._menuitemEcoSystem.add_Click({
            param($source, $e)
            #$_this = $source.Tag
            #try { $global:_EcoSystem.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
            if( Get-SRxIsConnected ) {
                $str = $global:SRxEnv.DefaultSettings.EcoSystem.ts_URL
                $str = $str.replace('{tenancy}', $global:SRxEnv.Tenancy.Name)
                $ecosystemURL = $str.replace('{ecosystem}', $global:SRxEnv.Tenancy.EcoSystem)
                Start-Process $ecosystemURL | Out-Null
            }
        })
        #
        # _menuitemShell
        #
        $this._menuitemShell.Text = "Shell"
        $this._menuitemShell.Image = $this._powershellBitmap
        $this._menuitemShell.Tag = $this
        $this._menuitemShell.add_Click({
            param($source, $e)
            $_this = $source.Tag
            try { $global:_Shell.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
        })
        #
        # _menuitemShellVerbose
        #
        $this._menuitemShellVerbose.Text = "Shell Verbose"
        $this._menuitemShellVerbose.Tag = $this
        $this._menuitemShellVerbose.Image = $this._powershellBitmap
        $this._menuitemShellVerbose.add_Click({
            param($source, $e)
            $_this = $source.Tag
            try { $global:_Shell_Verbose.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
        })
        #
        # _menuitemScheduler
        #
        $this._menuitemScheduler.Text = "Scheduler"
        $this._menuitemScheduler.Tag = $this
        $this._menuitemScheduler.add_Click({ _Scheduler })
        #
        # _menuitemProfile
        #
        $this._menuitemProfile.Text = "O365 connection"
        $this._menuitemProfile.Image = $this._cloudBitmap
        $this._menuitemProfile.Tag = $this
        $this._menuitemProfile.add_Click({
            param($source, $e)
            $_this = $source.Tag
            try { $global:_Profile.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
        })
        #
        # _menuitemLib
        #
        $this._menuitemLib.Text = "PowerShell Library"
        $this._menuitemLib.Image = $this._folderBitmap
        $this._menuitemLib.Tag = $this
        $this._menuitemLib.add_Click({ Start-Process explorer $(Join-Path $global:SRxEnv.paths.SRxRoot "Lib") })
        #
        # _menuitemCache
        #
        $this._menuitemCache.Text = "Local cache"
        $this._menuitemCache.Image = $this._folderBitmap
        $this._menuitemCache.Tag = $this
        $this._menuitemCache.add_Click({
            param($source, $e)
            $_this = $source.Tag
            try { $global:_Cache.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
        })
        #
        # _menuitemDocs
        #
        $this._menuitemDocs.Text = "Help"
        $this._menuitemDocs.Tag = $this
        $this._menuitemDocs.add_Click({
            param($source, $e)
            $_this = $source.Tag
            try { $global:_Docs.Invoke( $_this ) } catch { Write-Host ($_.Exception.Message) }
        })
        #
        # _menuitemExit
        #
        $this._menuitemExit.Text = "Quit"
        $this._menuitemExit.Tag = $this
        $this._menuitemExit.add_Click({
            param($source, $e)
            $_this = $source.Tag
            $_this.Close()
        })
    }
    hidden [void] initializePipes() {
 
        $this._pipeSysTray = New-SRxPipeServer -PipeName "pipe-Shell" -DoEvents $true
        $this._pipeSysTray.Caller = $this
        $this._pipeSysTray.OnMessage($this._onSysTrayMessage)
        $this._pipeSysTray.OnClose({
            $_this = $($args[0]).Caller
            if($_this._procTerminate) {
                $_this.terminateProcesses( $false)
                $_this._procTerminate = $false
            }
        })
 
        $this._pipeExplorer = New-SRxPipeServer -PipeName "pipe-Explorer" -DoEvents $false
        $this._pipeExplorer.Caller = $this
        $this._pipeExplorer.OnMessage($this._onExplorerMessage)
        $this._pipeExplorer.OnClose({
            $_this = $($args[0]).Caller
            #Powershell window was closed interactively by user
            if( $_this._explorer ) {
                $_this._explorer.Complete()
            }
        })
 
        $this._pipeCustomizer = New-SRxPipeServer -PipeName "pipe-Customizer" -DoEvents $false
        $this._pipeCustomizer.Caller = $this
        $this._pipeCustomizer.OnMessage($this._onCustomizerMessage)
        $this._pipeCustomizer.OnClose({
            $_this = $($args[0]).Caller
            #Powershell window was closed interactively by user
            if( $_this._customizer ) {
                $_this._customizer.Complete()
            }
        })
    }
    hidden [void] startPowerShell($arguments) {
        if( $this._command.ts_HideWindow ) {
            [System.Diagnostics.Process]$proc = Start-Process PowerShell -WindowStyle Minimized -ArgumentList $arguments -PassThru
            $this._hiddenprocesses.Add($proc)
            $this._procId = $proc.Id
            $this._procHide = $true
        }
        else {
            [System.Diagnostics.Process]$proc = Start-Process PowerShell -WindowStyle Normal -ArgumentList $arguments -PassThru
            $this._processes.Add($proc)
            $this._procId = $proc.Id
            $this._procHide = $false
        }
    }
    hidden [void] newExplorer() {
        try {
            if( $global:SRxEnv.Tenancy.TermGroupID -ne "") {
                if( -not $this._explorer) {
                    Write-Host "new schema explorer"
 
                    $sgroupName = Remove-SRxStringSpecialCharacter -String $global:SRxEnv.Tenancy.TermGroupName
                    $schema = $global:SRxEnv.CachePath + "\Profiles\" + $sgroupName + "\termSet.xml"
                    if ($(Test-Path $schema)) {
 
                        Connect-SRxProvisioningDatabase_JSON
 
                        $this._explorer = Explorer -schema $schema -source $($global:SRxEnv.Paths.SRxRoot)
                        $this._explorer.Caller = $this
                         
                        $this._explorer.OnCommand({
                            $_this = $($args[0]).Caller
                            $_this._command = $($args[1])
                            $_this._procId = $null
                            $_this.startPowerShell($($args[2]))
                        })
                        #begin Terminal Host - Iteration 2 (bugfix)
                        # Minimal counterpart to OnCommand above, used ONLY when
                        # Explorer's "Enable Terminal" redirect is active (see
                        # Explorer.onBeginCommand()). Populates ONLY this host's
                        # own "current command" state - the same field the
                        # "Get-Command" pipe-Shell message handler serves up to
                        # whatever shell.ps1 process asks for it - WITHOUT calling
                        # startPowerShell(), since there is no external process to
                        # track for a Terminal-redirected run.
                        #
                        # _procId IS still reset to $null here (mirroring OnCommand
                        # above) - this is a second bugfix on top of the original
                        # "bugfix" comment block: _procId is a single SysTrayHost-
                        # wide field, not scoped per-run. If an EARLIER run in this
                        # same session left it pointing at a since-exited/closed
                        # external process (e.g. the user manually closed that
                        # console window - see _pipeExplorer.OnClose, which calls
                        # Explorer.Complete() but never resets _procId), the
                        # existing "Complete-Explorer" guard below
                        # ("if ($_this._procId) { ... }") would still see that
                        # STALE-but-truthy PID as if it belonged to the CURRENT
                        # (Terminal-redirected) run, and call
                        # Get-Process -PID <dead-pid> - which finds nothing and
                        # returns nothing, so ".MainWindowHandle" also returns
                        # nothing, and casting that to [IntPtr] for IsIconic(...)
                        # throws exactly: 'Cannot convert argument "hWnd", with
                        # value: "", ... to type "System.IntPtr"'. Resetting _procId
                        # to $null here - exactly like OnCommand already does for
                        # every REAL external-process run - guarantees a clean slate
                        # for this run's own completion handling, regardless of what
                        # any earlier run left behind.
                        $this._explorer.OnCommandTerminal({
                            $_this = $($args[0]).Caller
                            $_this._command = $($args[1])
                            $_this._procId = $null
                        })
                        #end Terminal Host - Iteration 2 (bugfix)
                        $this._explorer.OnClose({
                            $_this = $($args[0]).Caller
                            $_this.terminateProcesses( $true)
                            $_this._explorer = $null
                            if( -not [boolean]($global:SRxEnv.Program.EnabledSysTray) ) { $_this.Close() }
                        })
                        $this._explorer.OnCancel({
                            $_this = $($args[0]).Caller
                            $_this.terminateProcesses( $false)
                            $_this._pipeExplorer.Close()
                        })
                        $this._explorer.OnHideProcess({
                            $_this = $($args[0]).Caller
                            $_this._procHide = $($args[1])
                            #https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
                            if($_this._procHide) { $null = [Win32Functions.Win32Windows]::ShowWindowAsync((Get-Process -PID $($_this._procId)).MainWindowHandle, 2) }
                            else { $null = [Win32Functions.Win32Windows]::ShowWindowAsync((Get-Process -PID $($_this._procId)).MainWindowHandle, 10) }
                        })
                        $this._explorer.OnTemplateCommand({
                            $_this = $($args[0]).Caller
                            $_this._customizer = $_this._explorer
                            #$_this._command = $($args[1])
                            #$_this._nodesStack = $_this._command.nodesStack
 
                        })
                        $this._explorer.OnTemplateClose({
                            $_this = $($args[0]).Caller
                            $_this._customizer = $null
                        })
                        #Write-Host "----- _explorer.OnEnableSysTray -----"
                        $this._explorer.OnEnableSysTray({
                            $_this = $($args[0]).Caller
                            $enabled = [boolean]$($args[1])
                            $_this._systray.Visible = $enabled
                            $global:SRxEnv.Program.EnabledSysTray = $enabled
                            $global:SRxEnv.PersistCustomProperty("Program", $global:SRxEnv.Program)
                        })
 
                        $this._explorer.Run()
                    }
                    else {
                        Write-Host " ? Can't find source schema file $schema"
                    }
                }
                else {
                    Write-Host "activate schema explorer"
                    $this._explorer.Activate()
                }
            }
        }
        catch {
            Write-Host($_.Exception.Message)
            Write-Host($_.Exception)
        }
    }
    hidden [void] terminateProcesses([boolean]$all) {
 
        function terminateProcs ($procs) {
            if($procs.Count -gt 0) {
                for( [int]$i = $($procs.Count - 1); $i -ge 0; $i--) {
                    $proc = $procs[$i]
                    Write-Host "Stop-Process $($proc.Id)"
                    Stop-Process $proc
                    [void]$procs.RemoveAt($i)
                }
            }
        }
        if( $all) {
            terminateProcs($this._hiddenprocesses)
            terminateProcs($this._processes)
        }
        else {
            #
            $procs = $this._hiddenprocesses
            if($procs.Count -gt 0) {
                for( [int]$i = $($procs.Count - 1); $i -ge 0; $i--) {
                    $proc = $procs[$i]
                    [boolean]$isMinimized = [Win32Functions.Win32Windows]::IsIconic((Get-Process -PID $($proc.Id)).MainWindowHandle)
                    if( -not $isMinimized) {
                        Write-Host "Don't terminate Process $($proc.Id)"
                        $this._processes.Add($proc)
                        [void]$procs.RemoveAt($i)
                    }
                }
            }
            terminateProcs($procs)
        }
    }
    hidden [System.Management.Automation.ScriptBlock] $_onSysTrayMessage = {
        try {
            $message = $($args[1])
            $pipe = $($args[0])
            $_this = $pipe.Caller
 
            #Write-Host "[this] $ths"
            Write-Host "[server] $($pipe.GetPipeName()) received ($($message.GetType().Name)) from client: $message"
 
            if( $($message.GetType().Name) -eq "String") {
                #Write-Host "switch [string]"
                #string
                #Write-Host "[server] $($pipe.GetPipeName()) received (text) from client: $message"
                switch($message) {
                    "Terminate" { $_this.Close() }
                    "Receive" { $pipe.Write("Hello client !") }
                    "getObject" { $obj = [PSCustomObject]@{
                                            ts_Environment = "Production"
                                            ts_Title = "Title"
                                        }
                                        $pipe.Write($obj)
                                    }
                    "Get-Command" { if($_this._command) {
                                            $pipe.Write($_this._command)
                                            $_this._command = $null
                                        }
                                        else { $pipe.Write([pscustomobject]$false) }
                                    }
                    "Get-NodeStack" { <#if($_this._customizer) {
                                            if($_this._nodesStack) {
                                                $pipe.Write($_this._nodesStack)
                                                $_this._nodesStack = $null
                                            }
                                            else { $pipe.Write([pscustomobject]$false) }
                                        }
                                        #>
                                        if($_this._customizer ) { #$_this._customizer === $_this._explorer
                                            Write-Host "_this._procId = $($_this._procId)"
                                            $ns = $_this._customizer.NodesPack( $_this._procId)
 
                                            if($ns) {
                                                Write-Host ($ns | FT | Out-String)
                                                $pipe.Write($ns)
                                            }
                                            else {
                                                Write-Host ("no nodestack available")
                                                $pipe.Write([pscustomobject]$false)
                                            } #explor will pause process $_this._procId
                                        }
                                        else {
                                            $exit ="invalid"
                                            Write-Host ("invalid")
                                            $pipe.Write([pscustomobject]$exit)
                                        }
                                    }
                    "Complete" {
                                        if( $_this._explorer ) {
                                            $pipe.Write([pscustomobject]$true)
                                            $_this._explorer.Complete()
                                            #terminateChildProcesses -all $false
                                            #$pipe.Restart()
                                        }
                                        else {
                                            $exit ="invalid"
                                            $pipe.Write([pscustomobject]$exit)
                                        }
                                    }
                    default { $pipe.Write([pscustomobject]$false) }
                }
            }
            else {
                #Write-Host "[server] $($pipe.GetPipeName()) received (pscustomobject) from client:"
                Write-Host ( $message | Format-Table | Out-String )
                 
                Write-Host "$($message.ts_RuleSet)"
                 
                if( $message.ts_RuleSet ) {
                    switch($message.ts_RuleSet)
                    {
                        "Open-Explorer" {
                                                $_this.newExplorer() #-command $command
                                                $pipe.Write([pscustomobject]$true)
                                            }
                        "Open-Customizer" {
                                                $_this._explorer.ShowCustomizer($true)
                                                $_this._customizer = $_this._explorer
                                                $pipe.Write([pscustomobject]$true)
                                            }
                        "Update-Customizer" {
                                                if($_this._customizer) {
                                                    $_this._customizer.SetFilter2($message.filter)
                                                    $_this._customizer.StatusBar("Ready")
                                                    $pipe.Write([pscustomobject]$true)
                                                }
                                                else {
                                                    #$pipe.Write([pscustomobject]$false)
                                                    $exit ="invalid"
                                                    $pipe.Write([pscustomobject]$exit)
         
                                                }
 
                                            }
                        "Complete-Explorer" {
                                                if( $_this._explorer ) {
                                                    Connect-SRxProvisioningDatabase_JSON
                                                    $_this._explorer.Complete()
                                                    #begin Terminal Host - Iteration 2
                                                    # $_this._procId is ONLY ever set inside startPowerShell()
                                                    # (i.e. when a command was launched as a real EXTERNAL
                                                    # Start-Process window). When "Enable Terminal" redirects
                                                    # a command into the embedded Terminal instead (see
                                                    # Explorer.onBeginCommand()), startPowerShell() is never
                                                    # called, so _procId stays $null/stale here - yet this
                                                    # "Complete-Explorer" pipe message still arrives normally,
                                                    # since it is sent by shell.ps1's own ruleset-completion
                                                    # code over the SAME "pipe-Shell" named pipe regardless of
                                                    # whether that shell.ps1 process was started via
                                                    # Start-Process or hosted inside the Terminal's ConPTY
                                                    # session. Without this guard, Get-Process -PID $null
                                                    # below would throw every time a Terminal-redirected
                                                    # command completes.
                                                    if ($_this._procId) {
                                                    #end Terminal Host - Iteration 2
                                                    [boolean]$isMinimized = [Win32Functions.Win32Windows]::IsIconic((Get-Process -PID $($_this._procId)).MainWindowHandle)
                                                    Write-Host ("Complete pid = $($_this._procId) Window Minimized = $isMinimized")
                                                    #Write-Host ("Complete - startup window hidden = $($_this._procHide)")
 
                                                    if($_this._procHide -and $isMinimized) { $_this._procTerminate = $true }
                                                    $_this._procHide = $false
                                                    $_this._procId = $null
                                                    #begin Terminal Host - Iteration 2
                                                    }
                                                    else {
                                                        Write-Host ("Complete - no external process (Terminal-redirected or already cleared)")
                                                    }
                                                    #end Terminal Host - Iteration 2
                                                 
                                                    $pipe.Write([pscustomobject]$true)
                                                }
                                                else {
                                                    $exit ="invalid"
                                                    $pipe.Write([pscustomobject]$exit)
                                                }
                                            }
         
                        "Status-Explorer" {
                                                if( $_this._explorer ) {
                                                    $_this._explorer.StatusBar($message.ts_Title)
                                                    $pipe.Write([pscustomobject]$true)
                                                }
                                                else { $pipe.Write([pscustomobject]$false) }
                                            }
                        "Status-Customizer" {
                                                if( $_this._customizer ) {
                                                    $_this._customizer.StatusBar($message.ts_Title)
                                                    $pipe.Write([pscustomobject]$true)
                                                }
                                                else { $pipe.Write([pscustomobject]$false) }
                                            }
 
                        default {
                                                $exit ="invalid"
                                                $pipe.Write([pscustomobject]$exit)
                                            }
                    }
                }
                else {
                    Write-Host ( $message | Format-List | Out-String )
                    $pipe.Write([pscustomobject]$true)
                }
            }
        }
        catch {
            Write-Host($_.Exception.Message)
            Write-Host($_.Exception)
        }
    }
    hidden [System.Management.Automation.ScriptBlock] $_onExplorerMessage = {
        $message = $($args[1])
        $pipe = $($args[0])
        $_this = $pipe.Caller
 
        Write-Host "[server] $($pipe.GetPipeName()) received ($($message.GetType().Name)) from client: $message"
        if( $_this._explorer ) { $_this._explorer.StatusBar($message) }
 
        $pipe.Write([pscustomobject]$true)
    }
    hidden [System.Management.Automation.ScriptBlock] $_onCustomizerMessage = {
        $message = $($args[1])
        $pipe = $($args[0])
        $_this = $pipe.Caller
 
        Write-Host "[server] $($pipe.GetPipeName()) received ($($message.GetType().Name)) from client: $message"
        if( $_this._customizer ) { $_this._customizer.StatusBar($message) }
         
        $pipe.Write([pscustomobject]$true)
    }
    [void] Run2() {
        # Create an application context for it to all run within.
        # This helps with responsiveness, especially when clicking Exit.
        $appContext = New-Object System.Windows.Forms.ApplicationContext
        [void][System.Windows.Forms.Application]::Run($appContext)
    }
    [void] Close() {
        if($this._pipeSysTray) { $this._pipeSysTray.Dispose() }
        if($this._pipeExplorer) { $this._pipeExplorer.Dispose() }
        if($this._pipeCustomizer) { $this._pipeCustomizer.Dispose() }
 
        $this._systray.Visible = $false
        [System.Windows.Forms.Application]::Exit() #close any opened forms
 
        $this._systray.dispose()
        #$window.Close()
        #[System.Diagnostics.Process]::CloseMainWindow()
        [int]$pid = [System.Diagnostics.Process]::GetCurrentProcess().Id
        Stop-Process $pid
    }
}