Private/Wissen/B_Basic/B61_Ausgabe.ps1

<#
 
# Objekt-Ausgabe
 
In der PowerShell erzeugte Objekte ausgeben um diese in anderen System weiter zu verarbeiten.
 
- **Hashtags** Format Write Out Excel CSV XML HTML WinForms Chart Diagram VT100
 
- **Version** 2020.05.28
 
#>


# TODO siehe auch B63_Konvertierungen.ps1

#region Default-Ausgabe

# ! Die visuelle Ausgabe in der PowerShell-Console zeigt nur einen Teil der tatsächlichen Objekt-Informationen. Diese Default-Ausgabe wird über .PS1XML-Dateien gesteuert, die entweder in Modules enthalten sind oder direkt im PowerShell-PFad, z.B.:

Get-ChildItem -Path 'C:\' -Force
Start-Process -FilePath "$env:WinDir\System32\WindowsPowerShell\v1.0\FileSystem.format.ps1xml"
Start-Process -FilePath "$env:WinDir\System32\WindowsPowerShell\v1.0\"

# ! ACHTUNG: Eine Spaltenüberschrift dieser Default-Ausgabe muss nicht immer identisch sein mit dem Eigenschaftsnamen des Objektes, z.B. die Default-Ausgabe von Registry-Key. Hier wird der Inhalt der Objekt-Property 'PSChildName' unter der Überschrift 'Name' angezeigt. 'Name' jedoch gibt es auch als Objekt-Property. Was zu Folge hat das folgende Filterung nicht funktioniert:

Get-ChildItem -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion' | Where-Object -Property 'Name' -iLike -Value 'Run*'
Get-ChildItem -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion' | Select-Object -Property Name, PSChildName

# TIPP - Korrekt müsste die Filterung wie folgt lauten:

Get-ChildItem -Path HKCU:\Software\Microsoft\Windows\CurrentVersion | Where-Object -Property 'PSChildName' -iLike -Value 'Run*'
# * Siehe dazu:
Start-Process -FilePath "$env:WinDir\System32\WindowsPowerShell\v1.0\Registry.format.ps1xml"

#endregion

#region Format-* Cmdlets

# Die Format-*-Cmdlets bereitet die Objekte für eine End-Ausgabe auf.

# ! ACHTUNG => I.d.R. ist danach ein weiteres Bearbeiten von Objekten nicht mehr möglich!

# READ Weiterführende und Nachschlage-Informationen:
Get-Command -Verb 'Format' -Module 'Microsoft.PowerShell.*'

#region Besonderheiten zu Format-Table (Tabellarische Anzeige)

# ! Seit Windows PowerShell 5wird aber der 5 Property in die Listen-Darstellung gewechselt:

Get-Process | Select-Object -Property 'Name', 'Company', 'CPU', 'TotalProcessorTime', 'WorkingSet64'
Get-Process | Format-Table  -Property 'Name', 'Company', 'CPU', 'TotalProcessorTime', 'WorkingSet64' | gm

# ! Spalten-Breite optimal anpassen:

Get-Process | Format-Table -Property 'Name', 'Company', 'CPU', 'TotalProcessorTime', 'WorkingSet64' -AutoSize

# ! Inhalt der auf '...' endet in weitere Zeilen umbrechen:

Get-EventLog -LogName 'System' -Newest 8 -EntryType 'Error' | Format-Table -Wrap

#endregion

#region Besonderheiten zu Format-List (Listen Anzeige)

# ! Den Inhalt ALLER Properties übersichtlich anzeigen:

Get-Process | Sort-Object -Property 'WorkingSet64' -Descending | Select-Object -First 1 | Format-List -Property '*'
Get-Process | Sort-Object -Property 'WorkingSet64' -Descending | Select-Object -First 1 -Property '*' # TIPP

# ! Eine gruppierte Anzeige:

Get-Service | Format-List -Property 'Name' -GroupBy 'StartType'

# TIPP - Besser so:

Get-Service | Group-Object -Property 'StartType'

# ! Berechnungsfehler anzeigen:

Get-Date | Format-List -Property 'DayOfWeek', { $_ / $null } -DisplayError

#endregion

#region Besonderheiten zu Format-Wide, fw (Kurzübersicht)

# ! Eine visuell komprimierte Anzeige von vielen kurzen Property-Werten

Get-ChildItem -Path 'C:\Windows\System32' -File -Force -Recurse -ErrorAction 'Ignore' | Select-Object -Property 'Extension' -Unique | Sort-Object -Property 'Extension' | Format-Wide -Column 10 # -AutoSize

Get-Alias | Select-Object -Property 'DisplayName' | Format-Wide -AutoSize

#endregion

#endregion

#region Write-* Cmdlets

# Die Write-*-Cmdlets erzeugen eine Ausgabe im Informations-Stream. Diese Ausgabe stört nicht die Objekt-Weitergabe bzw. vermischt sich mit dieser. Diese Cmdlets hab i.d.R. noch weitere Sonderaufgaben.

# READ Weiterführende und Nachschlage-Informationen:
Get-Command -Verb 'Write' -Module 'Microsoft.PowerShell.Utility'

# ! Fehler-Meldung ausgeben:

'Achtung ich bin ein Fehler.' | Write-Error
# TIPP - Diese Meldungen interagieren mit dem Common-Parameter '-ErrorAction Stop' und der Systemvariable $ErrorActionPreference

# ! Warn-Meldung ausgeben:

'Upps ich bin eine Warnung' | Write-Warning
# TIPP - Diese Meldungen interagieren mit dem Common-Parameter '-WarningAction Stop' und der Systemvariable $WarningPreference

# ! Zusätzliche Informationen ausgeben:

Write-Information -MessageData 'Zusätzliche Informationen.'
# TIPP - Diese Meldungen interagieren mit dem Common-Parameter '-InformationAction Stop' und der Systemvariable $InformationPreference
$InformationPreference = [System.Management.Automation.ActionPreference]::Continue

# ! Weitreichende Informationen ausgeben:

'Weitreichende Informationen' | Write-Verbose # $VerbosePreference
Test-NetConnection -Verbose

# ! Bei langer Laufzeit den Fortschritt visualisieren:

0..100 | ForEach-Object -Process {
    Write-Progress -Activity 'Verarbeite Daten' -Status "$_ Prozent" -PercentComplete $_
    Start-Sleep -Milliseconds 100
}

#endregion

#region Ausgabe-Streams verstehen

# ! Stream-Überblick:

<#
| STROM | ID | SYS-VARIABLE-NAME | DEFAULT-VALUE | CMDLET |
| :---------- | :---: | :--------------------- | :--------------- | :---------------- |
| Debug | 5 | $DebugPreference | SilentlyContinue | Write-Debug |
| Error | 2 | $ErrorActionPreference | Continue | Write-Error |
| Information | 6 | $InformationPreference | SilentlyContinue | Write-Information |
| Verbose | 4 | $VerbosePreference | SilentlyContinue | Write-Verbose |
| Warning | 3 | $WarningPreference | Continue | Write-Warning |
| Output | 1 | - | - | Write-Output |
| Host | 6 | - | - | Write-Host |
#>


# ! Streams umleiten - Standardmäßig wird Variablen nur der Ausgabe-Stream zugewiesen. Alle anderen Streams werden entweder direkt an die Konsole gesendet oder ausgeblendet.

# TODO ID 2 und 3 auf 1 umleiten:
$AllErrorsAndWarnings = Get-Process -FileVersionInfo 2>&1 3>&1
$AllErrorsAndWarnings

& {
    'Write-Error' | Write-Error
    'Write-Warning' | Write-Warning
    'Write-Verbose' | Write-Verbose -Verbose
    Write-Information -MessageData 'Write-Information' -InformationAction 'Continue'
    'Write-Output' | Write-Output
    'Write-Host' | Write-Host
} *>$null # 2>$null 3>$null 4>$null 5>$null 6>$null

# ! Write-Output vs. Write-Information:

# Write-Output - Verwenden Sie dieses Cmdlet für Nachrichten, die ein Benutzer immer **sehen sollte**. Obwohl Write-Output den Informations-Stream verwendet, wird er von keiner Voreinstellungs-Variable beeinflusst:
'Ich bin eine Write-Output-Meldung' | Write-Output

# Write-Information - Verwenden Sie dieses Cmdlet für Nachrichten, die ein Benutzer standardmäßig **nicht sehen sollte**, die ein Benutzer jedoch möglicherweise aktivieren möchte:
Write-Information -MessageData 'Ich bin eine Write-Information-Meldung' # ! -InformationAction 'Continue'

# ! Streams verwerfen:

Write-Warning 'A Warning' 3>$null

# ? Warum manche Befehle hässlich sind:

# Gelegentlich stoßen Sie auf Befehle die nicht den Regeln entsprechen und schreiben trotz dem unterdrücken in den Ausgaben-Stream. Zum Beispiel Get-WindowsUpdateLog ist ein Cmdlet der Informationen von .ETL-Dateien extrahiert und in eine Protokolldatei schreibt:

Get-WindowsUpdateLog

# Wenn Sie diesen Befehl ausführen wird eine Menge an Informationen ausgegeben und es scheint keine Möglichkeit zu geben, diese Informationen auszublenden oder zu verwerfen:

$null = Get-WindowsUpdateLog *>&1

# Selbst wenn Sie alle Streams (*) umleiten und alles an $null senden, werden die Nachrichten weiterhin in der Konsole angezeigt. Dieses Problem tritt immer dann auf, wenn Befehle direkt in die Konsole schreiben und so den Stream-Mechanismus umgehen:

& { [Console]::WriteLine('Hallo Würzburg!') } *>$null

# ! Sämtliche Ausgaben stumm schalten:

# Um die direkte Konsolenausgabe stummzuschalten und sämtliche Ausgaben zu verwerfen, können Sie das Cmdlet Out-Default vorübergehend deaktivieren:
function Out-Default {  }

# TODO Test:
Get-WindowsUpdateLog

# TODO Out-Default wiederherstellen:
Remove-Item -Path 'function:\Out-Default'

#endregion

#region OUT-*

# Diese Cmdlets leiten die Ausgabe um.

# READ Weiterführende und Nachschlage-Informationen:
Get-Command -Verb 'Out' -Module 'Microsoft.PowerShell.*'

# ? Ausgabe seitenweise anzeigen:

Get-ChildItem -Path 'C:\' -Recurse | Out-Host -Paging # ! 'more' ist unbrauchbar

# ? Ausgabe in eine Datei umleiten:

Get-Process | Out-File -FilePath 'C:\Temp\Process.txt' -Encoding 'ASCII' -Width '50'

# ? Ausgabe zum Drucker umleiten:

Get-ChildItem -Path 'C:\Windows' -File | Select-Object -First 10 | Out-Printer

# ? Ausgabe in ein Windows-Fenster umleiten:

Get-Process | Out-GridView

# ? Ausgabe unterbinden:

$al = New-Object -TypeName 'System.Collections.ArrayList'
$al.Add('Hallo')
$al.Add('Würzburg') | Out-Null

#endregion

#region Set-Content, Add-Content

# VT zu Out-File ist 1. Objekt i.d.R. besser Richtung Text umgewandelt wird und 2. können diverse PSProvider/PSDrive genutzt werden.

Get-Process | Set-Content -Path 'C:\Temp\lfdProcs.txt'

'Protokoll-Meldung ....' | Add-Content -Path 'C:\temp\Xyz.log'

#endregion

#region VT100 Host Format

# READ Weiterführende und Nachschlage-Informationen:
# ANSI escape code https://en.wikipedia.org/wiki/ANSI_escape_code

# ! 1. Virtual Terminal aktivieren:

Get-PSReadLineOption
Set-PSReadLineOption -EditMode ([Microsoft.PowerShell.EditMode]::Vi)

# ! 2. Prüfen ob eine VT100-Ausgabe möglich ist:

if ($host.UI.SupportsVirtualTerminal) {
    "Hallo Würzburg!"
}
else {
    'Hallo Würzburg!'
}

# ! Kombinationen sind möglich:

"Bold and Underlined Bold Only Normal Text"

#region Mögliche VT100 Sequenzen

# ! Allgemein

"[!p"  # Soft Reset Reset certain terminal settings to their defaults.
""  # Default Returns all attributes to the default state prior to modification
""  # Bold Applies brightness/intensity flag to foreground color
"" # Bold Reset
""  # Underline Adds underline
"" # No underline Removes underline
""  # Negative Swaps foreground and background colors
"" # No negative Returns foreground/background to normal

# ! Vordergrund Farbe

"" # Black
"" # Red
"" # Green
"" # Yellow
"" # Blue
"" # Magenta
"" # Cyan
"" # White
"" # Extended
"" # Default

# ! Vordergrund Farbe strahlend

"" # Black
"" # Red
"" # Green
"" # Yellow
"" # Blue
"" # Magenta
"" # Cyan
"" # White

# ! Hintergrund Farbe

"" # Black
"" # Red
"" # Green
"" # Yellow
"" # Blue
"" # Magenta
"" # Cyan
"" # White
"" # Extended
"" # Default

# ! Hintergrund Farbe strahlen
"" # Black
"" # Red
"" # Green
"" # Yellow
"" # Blue
"" # Magenta
"" # Cyan
"" # White

# ! Musterfarben:

@(
    "30m Foreground Black"
    "90m Foreground Bright Black"
    "31m Foreground Red"
    "91m Foreground Bright Red"
    "32m Foreground Green"
    "92m Foreground Bright Green"
    "33m Foreground Yellow"
    "93m Foreground Bright Yellow"
    "34m Foreground Blue"
    "94m Foreground Bright Blue"
    "35m Foreground Magenta"
    "95m Foreground Bright Magenta"
    "36m Foreground Cyan"
    "96m Foreground Bright Cyan"
    "37m Foreground White"
    "97m Foreground Bright White"
    " 40m Background Black "
    " 41m Background Red "
    " 42m Background Green "
    " 43m Background Yellow "
    " 44m Background Blue "
    " 45m Background Magenta "
    " 46m Background Cyan "
    " 47m Background White "
    " 100m Background Bright Black "
    " 101m Background Bright Red "
    " 102m Background Bright Green "
    " 103m Background Bright Yellow "
    " 104m Background Bright Blue "
    " 105m Background Bright Magenta "
    " 106m Background Bright Cyan "
    " 107m Background Bright White "
) -join " | "


# ! Der Rest:

# Console Virtual Terminal Sequences https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

#endregion

#endregion

#region Select-String

# ! PowerShell 7 :: Verbesserung von Select-String um eine visuelle Hervorhebung von Fundstellen:

Get-ChildItem -Path 'C:\Windows\Logs\DISM\dism.log' | Select-String -Pattern 'Error' | Select-Object -First 10
# READ Der Switch-Parameter -NoEmphasis deaktiviert die Hervorhebung.

#endregion

#region Console-GUI

# Wie Out-GridView nur in der Console.

# TODO - Erst ab PowerShell 6.2

Install-Module -Name 'Microsoft.PowerShell.ConsoleGuiTools' -Scope 'CurrentUser' -Force -Verbose
Import-Module -Name 'Microsoft.PowerShell.ConsoleGuiTools' -Verbose
Get-Command -Module 'Microsoft.PowerShell.ConsoleGuiTools'
Get-Process | Out-ConsoleGridView

#endregion

#region PowerShell 7-Module 'Microsoft.PowerShell.GraphicalTools'

# ! PowerShell 7 :: Ein neues Module (Microsoft.PowerShell.GraphicalTools, Version 0.2.0) für OS-Übergreifende GUI's (Out-GridView, Show-Command, Get-Help -ShowWindow):

Install-Module -Name 'Microsoft.PowerShell.GraphicalTools' -Scope 'CurrentUser' -AllowClobber -SkipPublisherCheck -AllowPrerelease -AcceptLicense -Force

Remove-Alias -Name 'ogv' -Force

Import-Module -Name 'Microsoft.PowerShell.GraphicalTools' -Force

Get-Command -Module 'Microsoft.PowerShell.GraphicalTools'

Get-Process | Out-GridView

#endregion

#region Export in eine Excel-Mappe

# Über das Module ImportExcel können Daten nach oder von Excel importiert / exportiert werden OHNE das Excel selber installiert sein muss.

# TIPP - Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5uoqS92stXioZw-u-ze_NtvSo0k0K0kq

Install-Module -Name 'ImportExcel' -Scope 'CurrentUser' -SkipPublisherCheck -Force
Get-Command -Module 'ImportExcel'

Remove-Item -Path 'C:\Temp\CurrentProcesses.XlsX' -Force
Get-Process | Export-Excel 'C:\Temp\CurrentProcesses.XlsX' -WorksheetName 'Processes' -ChartType 'PieExploded3D' -IncludePivotChart -IncludePivotTable -PivotRows 'Company' -PivotData 'PM' -Show
Start-Process -FilePath 'C:\Temp\CurrentProcesses.XlsX' -Wait

#endregion

#region 🔥 Emojis ❗ und ASCII-Art

# https://emojipedia.org/ 🌵 🔥 🚒 😁 ❗ 🙋‍♀️ 🕹 🥳

<#
  ____ ____ ____ ____
 /\ \/\ \/\ \/\ \
/ \___\ \___\ \___\ \___\
\ / __/_/ / / / / /
 \/_/\ \__/\/___/\/___/
   / \___\ / \___\
   \ / __/_ _\ / /
    \/_/\ \/\ \/___/
      / \__/ \___\
      \ / _\ / /
       \/_/\ \/___/
         / \___\
         \ / /
          \/___/
 
  ______________________
 /\ \
/ \ _________________\
\ \ \ /
 \ \ \__________ /
  \ \ \ / / /
   \ \ \ / / /
    \ \ \/ / /
     \ \ / / /
      \ \/ / /
       \ / /
        \ / /
         \ / /
          \/___/
#>


#endregion

# ! Einfache grafische Benutzeroberflächen (GUI) sind mit der PowerShell und .NET mit geringen Aufwand möglich. Bei komplexen GUI's stößt die PowerShell schnell an ihre Grenzen da der Aufwand unverhältnismäßig groß ist.

# TIPP - WinForm-Grundgerüst per GUI bauen über https://poshgui.com

#region Eine WPF ALARM-Meldung

using namespace 'System.Xml'
using namespace 'System.IO'
using namespace 'System.Windows.Markup'
using namespace 'System.Speech.Synthesis'

Add-Type -AssemblyName 'PresentationFramework'
Add-Type -AssemblyName 'System.Speech'
$xaml = @'
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="MainWindow" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" SizeToContent="WidthAndHeight">
    <Grid Background="Red">
        <TextBlock Name="MyFadingText" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="50" FontFamily="Consolas" FontSize="100" FontWeight="ExtraBold" Foreground="White" Text="A L A R M !">
            <TextBlock.Triggers>
                <EventTrigger RoutedEvent="TextBlock.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetName="MyFadingText" Storyboard.TargetProperty="(TextBlock.Opacity)" From="1.0" To="0.0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </TextBlock.Triggers>
        </TextBlock>
    </Grid>
</Window>
'@

$reader = [XmlReader]::Create([StringReader]$xaml)
$window = [XamlReader]::Load($reader)
$window.Topmost = $true
$window.Add_Loaded({
    $speaker = New-Object -TypeName 'SpeechSynthesizer' -Property @{ Rate = 2; Volume = 100 }
    $speaker.SpeakAsync('ALARM! ALARM! ALARM! ALARM!')
})
$window.ShowDialog() | Out-Null

#endregion

#region Diagramm erstellen per DataVisualization

using namespace System.Windows.Forms.DataVisualization.Charting
using namespace System.Windows.Forms
using namespace System.Drawing

Add-Type -AssemblyName "System.Windows.Forms.DataVisualization"

$chartTitle = "Top Memory Usage"
$axisYTitle = "Memory (MB)"
$axisXTitle = "Process Name"
$dataSource = Get-Process | Sort-Object "PrivateMemorySize" -Descending  | Select-Object -First 8 | ForEach-Object -Process {
    [PSCustomObject]@{
        Name                = $_.Name
        PrivateMemorySizeMB = [int]($_.PrivateMemorySize / 1MB)
        VirtualMemorySizeMB = [int]($_.VirtualMemorySize / 1MB)
    }
}

$chart1           = New-Object -TypeName "Chart"
$chart1.Width     = 1200
$chart1.Height    = 1000
$chart1.BackColor = [Color]::White
$chart1.Dock      = [DockStyle]::Fill

$chart1.Titles.Add($chartTitle) | Out-Null
$chart1.Titles[0].Font      = "Arial,13pt"
$chart1.Titles[0].Alignment = [ContentAlignment]::TopCenter

$chartArea                = New-Object -TypeName "ChartArea"
$chartArea.Name           = "ChartArea1"
$chartArea.AxisY.Title    = $axisYTitle
$chartArea.AxisX.Title    = $axisXTitle
$chartArea.AxisY.Interval = 100
$chartArea.AxisX.Interval = 1
$chart1.ChartAreas.Add($chartArea)

$legend      = New-Object -TypeName "Legend"
$legend.name = "Legend1"
$chart1.Legends.Add($legend)

$chart1.Series.Add("VirtualMem") | Out-Null
$chart1.Series["VirtualMem"].ChartType         = "Column"
$chart1.Series["VirtualMem"].BorderWidth       = 3
$chart1.Series["VirtualMem"].IsVisibleInLegend = $true
$chart1.Series["VirtualMem"].ChartArea         = "ChartArea1"
$chart1.Series["VirtualMem"].Legend            = "Legend1"
$chart1.Series["VirtualMem"].color             = [Color]::LightGreen
$dataSource | ForEach-Object -Process {
    $chart1.Series["VirtualMem"].Points.AddXY( $_.Name, $_.VirtualMemorySizeMB) | Out-Null
}

$chart1.Series.Add("PrivateMem") | Out-Null
$chart1.Series["PrivateMem"].ChartType         = "Column"
$chart1.Series["PrivateMem"].IsVisibleInLegend = $true
$chart1.Series["PrivateMem"].BorderWidth       = 3
$chart1.Series["PrivateMem"].ChartArea         = "ChartArea1"
$chart1.Series["PrivateMem"].Legend            = "Legend1"
$chart1.Series["PrivateMem"].color             = [Color]::Orange
$dataSource | ForEach-Object -Process {
    $chart1.Series["PrivateMem"].Points.AddXY( $_.Name, $_.PrivateMemorySizeMB) | Out-Null
}

# ! z.B. zusätzlich als PNG speichern:
$chart1.SaveImage("c:\temp\$chartTitle.png", "png")

# ! z.B. im Fenster anzeigen:
$Form               = New-Object -TypeName "Form"
$Form.Width         = 1300
$Form.Height        = 800
$Form.StartPosition = [FormStartPosition]::CenterScreen
$Form.controls.add($chart1)
$Form.Add_Shown({
    $Form.Activate()
})
$Form.ShowDialog()

#endregion

# TODO QUIZ - https://forms.office.com/Pages/ResponsePage.aspx?id=DQSIkWdsW0yxEjajBLZtrQAAAAAAAAAAAAa__Yp1xwFUNldPRUxFSExFTDkzQVRQUENQMFFTMUhTMi4u