Private/Wissen/B_Basic/B16b_Pipelining.ps1

<#
 
# Pipelining
 
Die Pipeline-Mechanik ist ein zentrales Element der PowerShell. Das Quell-Cmdlet erzeugt Objekte die über die Pipeline an das Ziel-Cmdlet über zwei unterschiedlichen Verfahren gebunden werden. Wenn die Objekte in der Pipeline entsprechend aufgearbeitet werden, kann jedes Cmdlet an jedes beliebige Cmdlet über die Pipeline gebunden werden.
 
Die Objekte von ```Get-Process``` können offensichtlich über die Pipeline an das Cmdlet ```Stop-Porcess``` gebunden werden. Es findet aber im Vorfeld keine Definition zwischen diesen Cmdlets statt. Einzig alleine greifen immer die zwei unterschiedlichen Bindungs-Verfahren.
 
Wenn Sie wissen wie diese Verfahren **ByValue** und **ByPropertyName** arbeiten, können Sie dieses Wissen dazu benutzen um sämtliche Cmdlets zu Ihrem Nutzen über die Pipeline zu verbinden.
 
- **Hashtags** Zeilenumbruch Just-in-time Pipelining Pipeline ByValue ValueFromPipeline ByPropertyName ValueFromPipelineByPropertyName Parameter Binding Splatting
 
- **Version** 2020.05.18
 
#>


[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param()

#region 1. Zeilenumbrüche in der Pipeline-Verarbeitung

# ! Nach einer Pipeline können Zeilenumbrüche (CRLF) eingefügt werden um die **Lesbarkeit** zu erhöhen:

Get-Process |
    Where-Object -Property 'Company' -ILike -Value 'Microsoft*' |
    Sort-Object -Property 'Name' |
    Select-Object -Property 'Name', 'Company' |
    Out-File -FilePath 'C:\temp\Process.txt' -Force

# TIPP ACHTUNG - Ab jetzt muss der auszuführende Block selektiert und erst dann mit F8 zur Ausführung gebracht werden. Das ständige Markieren könnte auf die Dauer als Handling-Nachteil ausgelegt werden. Dieser Nachteil kann über ALT+Z in VSCode relativiert werden, da zwischen einem visuellen Umbruch gewechselt werden kann:

Get-Process | Where-Object  -Property 'Company' -ILike -Value 'Microsoft*' | Sort-Object -Property 'Name' | Select-Object -Property 'Name', 'Company' | Out-File -FilePath 'C:\temp\Process.txt' -Force

<# ## TIPP Dieser 'visuellen Umbruch' kann in den Einstellungen an die eigenen Bedürfnisse angepasst werden:
```
{
    "editor.rulers": [
        { "column" : 80 , "color" : "#2c2c2c" },
        { "column" : 160 , "color" : "#ff0000" },
    ],
    "editor.wordWrap" : "wordWrapColumn",
    "editor.wordWrapColumn" : 80,
    "editor.wrappingIndent" : "indent",
}
```
#>


#endregion

#region 2. Objekte in der Pipeline

# ! Die PowerShell überträgt über die Pipeline von Quell-Cmdlet zu Ziel-Cmdlet **KEINE TEXTE**. Es werden immer **OBJEKT** über die Pipeline übertragen.

Get-Process | Where-Object -Property 'Company' -IMatch -Value 'Microsoft' | ForEach-Object -Process { "Ist der Prozess '$($_.Name)' ein Objekt? $($_ -is [System.Object])" }

#endregion

#region 3. just-in-time Objekt-Weitergaben

# ! Das Quell-Cmdlet übergibt die Objekte an die Pipeline, diese werden unmittelbar an das nächste Cmdlet gebunden wird:

Get-ChildItem -Path 'C:\' -File -Recurse -PipelineVariable 'Datei' -ErrorAction 'SilentlyContinue' | ForEach-Object -Process { "Empfange Datei-Objekt $($Datei.Name) und gebe dessen .Name-Eigenschafts-Wert als [string] in der Pipeline weiter." | Write-Warning; $Datei.Name } | Out-GridView

#endregion

#region 4. Das Pipelining

Start-Process -FilePath 'notepad.exe'
# ? Würde dieser Befehl die notepad-Prozesse beenden, was denken Sie? (JA | NEIN)
Get-Process -Name 'notepad' | Stop-Process

Start-Process -FilePath 'notepad.exe'
# ? Würde dieser Befehl die notepad-Prozesse beenden, was denken Sie? (JA | NEIN | ERROR)
Get-ChildItem -Path 'C:\Temp\DL' | Stop-Process

# READ Siehe FlipChart *_Pipelining_FlipChart.png
# READ Siehe Video https://youtu.be/UQH1wMztOeo

# ! 1. Cmdlets können nur über deren Parameter angesprochen und gesteuert werden. Diese Regel gilt auch für das Pipeline-Objekte und dessen Bindung an das nächste Cmdlet.

# ! 2. Für diese Bindung zwischen Pipeline-Objekt und den Parametern des Ziel-Cmdlets stehen **ZWEI** Verfahren zur Verfügung:

# ! 2.1. Verfahren **ByValue** - Hier findet eine Bindung des **GANZEN PIPELINE-OBJEKTES** statt, wenn der Pipeline-Objekt-TYP KOMPATIBEL (s.u. 1. Anmerkung) mit dem Parameter-TYP ist.

# ! 2.2. Verfahren **ByPropertyName** - Hier findet eine Bindung **EINER EIGENSCHAFT** des Pipeline-Objektes an den Ziel-Cmdlet-Parameter statt, wenn a) der Eigenschafts-Name des Pipeline-Objektes identisch ist mit dem Parameter-Name, oder dessen Alias-Name des Ziel-Cmdlets und b) der Pipeline-Objekt-Eigenschafts-TYP KOMPATIBEL (s.u. 1. Anmerkung) mit Ziel-Cmdlet-Parameter-TYP.

# ! 2.3. Ob ByValue oder ByPropertyName oder beide Verfahren oder kein Verfahren angewendet werden kann, können Sie in der Parameter-Beschreibung der Ziel-Cmdlet-Hilfe-Seite entnehmen ().

# ! 3. Ein Mehrfach-Binden bzw. Mehrfach-Verfahren an das Ziel-Cmdlet ist möglich.

# ? Aufklärung des Einführungsbeispiels:

# ? 1. Welche Parameter (Name, Type) des Ziel-Cmdlets lassen die Pipeline-Bindung zu und nach welchem Verfahren (ByValue, ByPropertyName)?

Get-Help -Name 'Stop-Process' -ShowWindow

# ! ANTWORT:
# ! -InputObject <Process[]> ByValue
# ! -Name <string[]> ByPropertyName
# ! -Id <int32[]> ByPropertyName

# ? 2.a. GET-PROCESS :: Analysieren des Rückgabe-Objektes vom Quell-Cmdlet bzw. dem Pipeline-Objekt bzgl. Object-Type & Object-Properties:

Get-Process -Name 'notepad' | Get-Member

# ! ANTWORT:
# ! Object-Type = System.Diagnostics.Process >>> TREFFER (ByValue)
# ! Object-Property = Name <String> >>> TREFFER (ByPropertyName)
# ! Object-Property = ID <Int32> >>> TREFFER (ByPropertyName)

# ? 2.b. GET-CHILDITEM :: Analysieren des Rückgabe-Objektes von Quell-Cmdlet bzw. dem Pipeline-Objekt bzgl. Object-Type & Object-Properties:
                                       
Get-ChildItem -Path 'C:\temp\DL' | Get-Member

# ! ANTWORT:
# ! Object-Type = System.IO.FileInfo >>> Kein Binden möglich
# ! Object-Property = Name <String> >>> TREFFER (ByPropertyName)
# ! Ein Binden an den Id-Parameter ist nicht möglich, weil dieses Objekt keine Eigenschaft ID besitzt.

#region 1. ANMERKUNG: Kompatibilität von Objekt-Typen

# ! Objekte sind kompatibel wenn ...

# ! A) ... die Typennamen 100% identisch sind, d.h. inkl. Namespace:

[System.String]       -ceq [System.String]              # * Kompatibel
[System.Timers.Timer] -ceq [System.Windows.Forms.Timer] # ! NICHT Kompatibel

# ! B) ... jedes Objekt ist kompatibel mit [System.Object]:

'Hallo Köln!'    -is [System.Object] # * Kompatibel
(Get-Process)[0] -is [System.Object] # * Kompatibel
12               -is [System.Object] # * Kompatibel

# ! C) ... jedes komplexe Objekt ist kompatibel mit [PSCustomObject] (Alias: PSObject):

(Get-Process)[0]   -is [PSCustomObject] # * Kompatibel
(Get-Service)[0]   -is [PSCustomObject] # * Kompatibel
(Get-ChildItem)[0] -is [PSCustomObject] # * Kompatibel
12                 -is [PSCustomObject] # ! NICHT Kompatibel, da primitiver Typ

# ! D) ... jedes Objekt ist kompatibel mit Typen die in der Vererbungshierarchie vorkommen:

(Get-Process)[0] -is [System.Diagnostics.Process]      # * Kompatibel
(Get-Process)[0] -is [System.ComponentModel.Component] # * Kompatibel
(Get-Process)[0] -is [System.MarshalByRefObject]       # * Kompatibel
(Get-Process)[0] -is [System.Object]                   # * Kompatibel

# ! 1. Eine Klasse kann von EINER anderen Basisklasse abgeleitet sein und erbt so dessen Funktionen (Member).

# ! 2. Jede Klasse ist früher oder später von der Klasse 'Object' abgeleitet worden.

# ! 3. Eine abgeleitete Klasse A ist daher mit der Basisklasse B kompatibel, daraus ergibt sich, das ein Objekt von Typ A an einen Parameter vom Typ B übergeben werden.

# ? Welche Typ A von welchem Typ B abgeleitet wurde erfahren Sie z.B. wie folgt:
'Köln!'.PSTypeNames
(Get-Process)[0].PSTypeNames
(Get-Process).PSTypeNames

#endregion

#region 2. ANMERKUNG: Parameter-Bindungs-Analyse mit Trace-Command

Trace-Command -Name 'ParameterBinding' -Expression { Get-Process -Name 'notepad'      | Stop-Process } -PSHost
Trace-Command -Name 'ParameterBinding' -Expression { Get-ChildItem -Path 'C:\Temp\DL' | Stop-Process } -PSHost

#endregion

# TIPP Der praktische Nutzen anhand folgendem Beispiel:

# TODO Über die folgende CSV-Datei sollen neue Benutzer (Lokal / AD) anlegen werden:

@'
Benutzername;Passwort;Beschreibung
p.lustig;P@ssw0rd;Peter Lustig (IT)
e.gruen;Geh1imAbc;Eva Grün (HR)
'@
 | Set-Content -Path 'C:\Temp\NewUsers.csv'

# ! LÖSUNG:

Import-WinModule -Name 'Microsoft.PowerShell.LocalAccounts' # ! Nötig für PowerShell 7

# ? So leider noch nicht:

Get-ChildItem -Path 'C:\Temp\NewUsers.csv' | New-LocalUser

# TODO New-LocalUser :: Welche Parameter (Name, Type) des Ziel-Cmdlets lassen die Pipeline-Bindung zu und nach welchem Verfahren (ByValue, ByPropertyName)?

Get-Help -Name 'New-LocalUser' -ShowWindow

# ! ANTWORT:

# -Name <String> ByPropertyName
# -Password <SecureString> ByPropertyName
# -Description <String> ByPropertyName

# TODO Nun muss die CSV-Datei so aufbereitet werden das am Ende Pipeline-Objekte raus kommen, die die Anforderungen (.Name-Property vom Typ [String], .Password-Property vom Typ [SecureString], .Description-Property vom Typ [String],) erfüllen:

Get-ChildItem -Path 'C:\Temp\NewUsers.csv' | Get-Content | ConvertFrom-Csv -Delimiter ';' -Header 'Name', 'Password', 'Description' | Select-Object -Skip 1 -Property 'Name', 'Description', @{Label='Password'; Expression={ $_.Password | ConvertTo-SecureString -AsPlainText -Force }} | New-LocalUser -WhatIf

#endregion

#region 5. Parameter-Bindung per Splatting

# ! Splatting ist eine Methode eine Sammlung von Parameterwerten an ein Cmdlet als Einheit zu übergeben. Das @-Symbol teilt PowerShell mit, dass Sie eine Sammlung von Werten anstelle eines einzelnen Werts übergeben soll.

Get-Help -Name "about_Splatting" -ShowWindow # READ Splatting beschreibt die Möglichkeit mehrere Argumente diverser Parameter gleichzeitig an ein Cmdlet zu übergeben.

# Ohne Splatting:
Get-EventLog -LogName 'System' -Newest 5 -EntryType 'Error', 'Warning'

# ! vs.

# Mit Splatting:
$argument = @{
    LogName   = 'System'
    Newest    = 5
    EntryType = 'Error', 'Warning'
}
Get-EventLog @argument

#endregion

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

<# TODO ÜBUNG
 
# 1. Erstellen Sie folgende Dateien:
 
New-Item -Path 'C:\temp' -Name 'Kunde A.txt' -ItemType 'File'
New-Item -Path 'C:\temp' -Name 'Kunde B.txt' -ItemType 'File'
New-Item -Path 'C:\temp' -Name 'Kunde C.txt' -ItemType 'File'
New-Item -Path 'C:\temp' -Name 'Kunde X.txt' -ItemType 'File'
 
# 2. Erstellen Sie folgende .CSV-Datei:
 
"Dateiname;Löschen`nKunde A.txt;Nein`nKunde B.txt;Ja`nKunde C.txt;Nein" | Set-Content -Path 'C:\Temp\Files.csv'
 
# 3. Passen Sie den folgenden ???-Teil so an, das nur die Dateien gelöscht werden deren Property Löschen ein Ja enthält. D.h. aktuell nur die Datei 'Kunde B.txt' darf aus dem Temp-Ordner entfernt werden.
 
Get-ChildItem -Path 'C:\temp\Files.csv' | ??? | Remove-Item
 
# TIPPS :: Get-Content ; ConvertFrom-Csv ; Where-Object ; Select-Object
 
#>