Private/Wissen/B_Basic/B18_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
 
#>


#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.:

Start-Process -FilePath "$env:WinDir\System32\WindowsPowerShell\v1.0\FileSystem.format.ps1xml"

# ! 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*'

# 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)

# ! 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

# ! Benutzerdefinierte Spalten hinzufügen Basis-Template: @{ Label='' ; Expression={ $_ } }:

Get-Process | Format-Table -Property 'Name', @{ Label='WS (MB)'; Expression={ '{0,7:0.0}' -f ($_.WorkingSet64 / 1MB) } }

#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 | Sort-Object -Property 'StartType' | Format-List -Property 'Name' -GroupBy '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 -AutoSize

Get-Alias | Select-Object -Property 'Name' | 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'

# ! ACHTUNG - 'Write-Host' nicht mehr benutzen, da dieses Cmdlet als veraltet gekennzeichnet ist und in PowerShell 7 durch den bereits vorhanden Nachfolger 'Write-Output' ersetzt wurde.

 # ! 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:

'Zusätzliche Informationen.' | Write-Information
# TIPP - Diese Meldungen interagieren mit dem Common-Parameter '-InformationAction Stop' und der Systemvariable $InformationPreference

# ! Weitreichende Informationen ausgeben:

'Weitreichende Informationen' | Write-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

& {
    '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

# TODO ....

#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:

New-ItemProperty -Path 'HKCU:\Console' -Name 'VirtualTerminalLevel' -Value 1 -PropertyType 'DWord'
Set-PSReadlineOption -EditMode Vi

# ! 2. ESC-Zeichen festlegen:

New-Variable -Name 'ESC' -Value ([char]0x1b) -Description 'Escape sequences' -Option 'ReadOnly' -Visibility 'Public' -Scope 'Global' -Force

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

if ($host.UI.SupportsVirtualTerminal) {
    "$esc[1mHallo$esc[0m $esc[31mWürzburg!"
}
else {
    'Hallo Würzburg!'
}

# ! Kombinationen sind möglich:

"$esc[1;4mBold and Underlined$esc[24m Bold Only $esc[0mNormal Text"

#region Mögliche VT100 Sequenzen

# ! Allgemein

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

# ! Vordergrund Farbe

"$ESC[30m" # Black
"$ESC[31m" # Red
"$ESC[32m" # Green
"$ESC[33m" # Yellow
"$ESC[34m" # Blue
"$ESC[35m" # Magenta
"$ESC[36m" # Cyan
"$ESC[37m" # White
"$ESC[38m" # Extended
"$ESC[39m" # Default

# ! Vordergrund Farbe strahlend

"$ESC[90m" # Black
"$ESC[91m" # Red
"$ESC[92m" # Green
"$ESC[93m" # Yellow
"$ESC[94m" # Blue
"$ESC[95m" # Magenta
"$ESC[96m" # Cyan
"$ESC[97m" # White

# ! Hintergrund Farbe

"$ESC[40m" # Black
"$ESC[41m" # Red
"$ESC[42m" # Green
"$ESC[43m" # Yellow
"$ESC[44m" # Blue
"$ESC[45m" # Magenta
"$ESC[46m" # Cyan
"$ESC[47m" # White
"$ESC[48m" # Extended
"$ESC[49m" # Default

# ! Hintergrund Farbe strahlend

"$ESC[100m" # Black
"$ESC[101m" # Red
"$ESC[102m" # Green
"$ESC[103m" # Yellow
"$ESC[104m" # Blue
"$ESC[105m" # Magenta
"$ESC[106m" # Cyan
"$ESC[107m" # White

# ! Musterfarben:

@(
    "$ESC[30m30m Foreground Black$ESC[0m"
    "$ESC[90m90m Foreground Bright Black$ESC[0m"
    "$ESC[31m31m Foreground Red$ESC[0m"
    "$ESC[91m91m Foreground Bright Red$ESC[0m"
    "$ESC[32m32m Foreground Green$ESC[0m"
    "$ESC[92m92m Foreground Bright Green$ESC[0m"
    "$ESC[33m33m Foreground Yellow$ESC[0m"
    "$ESC[93m93m Foreground Bright Yellow$ESC[0m"
    "$ESC[34m34m Foreground Blue$ESC[0m"
    "$ESC[94m94m Foreground Bright Blue$ESC[0m"
    "$ESC[35m35m Foreground Magenta$ESC[0m"
    "$ESC[95m95m Foreground Bright Magenta$ESC[0m"
    "$ESC[36m36m Foreground Cyan$ESC[0m"
    "$ESC[96m96m Foreground Bright Cyan$ESC[0m"
    "$ESC[37m37m Foreground White$ESC[0m"
    "$ESC[97m97m Foreground Bright White$ESC[0m"
    "$ESC[97m$ESC[40m 40m Background Black $ESC[0m"
    "$ESC[97m$ESC[41m 41m Background Red $ESC[0m"
    "$ESC[97m$ESC[42m 42m Background Green $ESC[0m"
    "$ESC[97m$ESC[43m 43m Background Yellow $ESC[0m"
    "$ESC[97m$ESC[44m 44m Background Blue $ESC[0m"
    "$ESC[97m$ESC[45m 45m Background Magenta $ESC[0m"
    "$ESC[97m$ESC[46m 46m Background Cyan $ESC[0m"
    "$ESC[97m$ESC[47m 47m Background White $ESC[0m"
    "$ESC[30m$ESC[100m 100m Background Bright Black $ESC[0m"
    "$ESC[30m$ESC[101m 101m Background Bright Red $ESC[0m"
    "$ESC[30m$ESC[102m 102m Background Bright Green $ESC[0m"
    "$ESC[30m$ESC[103m 103m Background Bright Yellow $ESC[0m"
    "$ESC[30m$ESC[104m 104m Background Bright Blue $ESC[0m"
    "$ESC[30m$ESC[105m 105m Background Bright Magenta $ESC[0m"
    "$ESC[30m$ESC[106m 106m Background Bright Cyan $ESC[0m"
    "$ESC[30m$ESC[107m 107m Background Bright White $ESC[0m"
) -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

# Ü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 -Show -PivotRows 'Company' -PivotData 'PM'
Start-Process -FilePath 'C:\Temp\CurrentProcesses.XlsX' -Wait

#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/Editor

#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