Private/Wissen/C_Advance/C07_UserCmdlets.ps1

<#
 
# Benutzerdefinierte Cmdlets schreiben
 
Eigene Cmdlets entwickeln
 
- **Hashtags** Dynamic Parameter WhatIf Confirm Validation Function
 
- **Version** 2020.6.12
 
#>


# TODO Weiterführende und Nachschlage-Informationen
Get-Help about_Comment_Based_Help               -ShowWindow # Beschreibung der eigenen Cmdlet-Hilfe für das PS-Hilfesystem.
Get-Help about_Functions                        -ShowWindow # Grundlagen zu benutzerdefinierten Funktion
Get-Help about_Functions_Advanced               -ShowWindow # Grundlagen zu benutzerdefinierten Cmdlet
Get-Help about_Functions_Advanced_Methods       -ShowWindow 
Get-Help about_Functions_Advanced_Parameters    -ShowWindow # Beschreibung der eigenen Cmdlet-Parameter u.a. Validierung
Get-Help about_Functions_CmdletBindingAttribute -ShowWindow # Beschreibung der eigenen Cmdlet-Parameter bzgl. Pipeline-Verarbeitung
Get-Help about_Functions_OutputTypeAttribute    -ShowWindow # Beschreibung von Rückgabe-Objekte wie PSCustomObject

# ! "Alte Scripte" sollen durch den neuen PowerShell-Ansatz ersetzt werden,
# ! d.h. es werden keine "langen" Skripte mehr entwickelt sonder der Inhalt
# ! dieser auf viel kleine Cmdlets aufgeteilt. Das hat folgende Vorteile:
# ! A) Zum Beispiel wird ein Anmelde-Skript durch Aufruf dieser kleinen Cmdlets
# ! übersichtlicher. (=> Lesbarkeit)
# ! B) Durch Parametrisierung und die Kombinationsmöglichkeiten von
# ! Cmdlets, kann es dann unterschiedliche Anmelde-Skripte gebe.
# ! C) Diese Cmdlets können mit anderen Cmdlets
# ! per Pipeline kombiniert werden können. (=> Wiederverwendbarkeit)
# ! D) Mittels Show-Command/Out-GridView können Cmdlets über eine GUI
# ! bedient werden.
# ! E) Mittels Pester können Cmdlets automatisch getestet werden.

#region C#- vs. PowerShell-Cmdlets

# ! Eigene Cmdlets können auf zwei Arten definiert werden:
# ! 1. per C# oder VisualBasic.NET-Code über MS VisualStudio => MeineCmdlets.DLL
Get-Command -CommandType Cmdlet | Measure-Object
# ! 2. per PowerShell-Funktion mit dem Namen VERB-NOUN => MeineCmdlets.ps1
Get-Command -Name *-* -CommandType Function | Measure-Object

# ? Cmdlets mittels CLR schreiben
Start-Process https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/how-to-write-a-simple-cmdlet?view=powershell-6

#endregion

# ! WICHTIG: Diese Cmdlets können erst genutzt werden, wenn sie im PSDrive 'function:' enthalten sind.
# ! Dies geschieht z.B. durch das AUSFÜHREN der Funktions-Definition
Get-ChildItem -Path Function:\Clear-Host | Format-List -Property Name, ScriptBlock

# ! Wird die Session beendet wird die Funktion automatisch gelöscht
Remove-Item -Path Function:\Clear-Host -Force

#region Lernen von vorhanden Cmdlets anderer Programmierer

D:
Get-Alias -Name D:
Get-Command -Name D:
Get-ChildItem -Path Function:\D: | Format-List -Property Name, ScriptBlock
Get-ChildItem -Path Function:\ | Where-Object ScriptBlock -Match 'Set-Location \$MyInvocation\.MyCommand\.Name' | Format-List -Property Name, ScriptBlock

Get-ChildItem Function:\New-Guid | Select-Object -ExpandProperty ScriptBlock
[GUID]::NewGuid() # Ah-ha, das führt das Cmdlet New-Guid aus.

# ! ACHTUNG: Passwörter haben im Code nichts verloren!!!!!

#endregion

#region Herangehensweise eigene Cmdlets schreiben (4. Punkte-System)

# ? 1. Das Problem/Lösung statisch lösen
Get-Help -Name about_* | 
    Sort-Object -Property Name | 
    Out-GridView -Title "Bitte about-Seiten auswählen (CTRL + Mausklick)" -OutputMode Multiple |
    Get-Help -ShowWindow

# ? 2. Die statische Lösung in eine dynamische (bzgl. Parameter) Lösung umwandeln
$Keyword = "function"
Get-Help -Name about_*$Keyword* | 
    Sort-Object -Property Name | 
    Out-GridView -Title "Bitte about-Seiten auswählen (CTRL + Mausklick)" -OutputMode Multiple |
    Get-Help -ShowWindow

# ? 3. Aus dynamischen Lösung ein Cmdlet machen

# TODO Cmdlet-Grundgerüst kann per GUI zusammen gebaut werden über https://poshgui.com/CmdletBuilder

function Get-About {
    <#
    .SYNOPSIS
        about-Seiten über eine GUI benutzen.
    .DESCRIPTION
        Die about-Seiten über eine GUI benutzen.
    .EXAMPLE
        Get-About
        Alle about-Seiten werden angezeigt
    .EXAMPLE
        Get-About -Keyword remote
        Alle about-Seiten werden angezeigt die das Schlüsselwort Remote enthalten
    .INPUTS
        Nix
    .OUTPUTS
        Nix
    .PARAMETER Keyword
        Schlüsselwort nach dem die about-Seiten gefiltert werden.
    #>

    param (
        [string]$Keyword
    )
    Get-Help -Name about_*$Keyword* | 
        Sort-Object -Property Name | 
        Out-GridView -Title "Bitte about-Seiten auswählen (CTRL + Mausklick)" -OutputMode Multiple |
        Get-Help -ShowWindow
}

# ? 4. Das neue Cmdlet testen
Get-About 
Get-About -Keyword remote
Get-Help -Name Get-About -ShowWindow
Show-Command -Name Get-About -NoCommonParameter -ErrorPopup
Get-ChildItem -Path Function:\Get-About | Format-List -Property Name, ScriptBlock

# ! Selektion der Definition + F8; Selektion des Test-Codes + F8 => NERVT!
# ! LÖSUNG: Je Cmdlet eine .ps1-Datei mit nur noch dem Code aus 3. + eine Test-Zeile
# ! und mit F5 die Datei ausführen!

#endregion

#region -WhatIf (Simulation) und -Confirm (Einzelbestätigung) in Cmdlets integrieren
function Test-WhatIfConfirm {
    
    [CmdletBinding(SupportsShouldProcess = $true,
                   ConfirmImpact         = [System.Management.Automation.ConfirmImpact]::High # High => Risikostufe Gefährlich, Undo unmöglich
                                                                                              # Medium => Risikostufe Durchschnittlich, Undo bedingt unmöglich
                                                                                              # Low => Risikostufe Ungefährlich, Undo möglich
                                                                                              # None => Risikostufe unbekannt
    )]
    param()

    $target = Get-Service -Name Audiosrv
    $action = [System.ServiceProcess.ServiceControllerStatus]::Stopped
    $canDo = $PSCmdlet.ShouldProcess($target.Name, $action)
    if($canDo) {
        $target | Stop-Service -Force -PassThru
    }
}
Test-WhatIfConfirm
Test-WhatIfConfirm -Confirm:$false
Test-WhatIfConfirm -WhatIf

$ConfirmPreference = [System.Management.Automation.ConfirmImpact]::None
Test-WhatIfConfirm

$WhatIfPreference = $true
Test-WhatIfConfirm

#endregion

#region Autovervollständigung für Parameter

enum Richtung {
    Unbekannt
    West
    Nord
    Ost
    Süd
}
function Test-ParameterAutoVervollständigung {
    param (
        # Autovervollständigung per Datentyp
        [System.Management.Automation.ActionPreference]$ParameterA,

        # Autovervollständigung per ValidateSet
        [ValidateSet("Erde", "Mond", "Sonne", "Mars")]
        [String]$ParameterB,

        # Autovervollständigung per UserEnum
        [Richtung]$ParameterC,

        # Argumenten-Vervollständigung wie ValidateSet nur ohne Einschränkung, d.h. es können auch andere Argumente übergeben werden.
        [ArgumentCompleter({'Microsoft','Amazon','Google'})]
        [string]
        $ParameterD

        # Autovervollständigung per "Dynamik", s. weiter unten.
    )
}

Test-ParameterAutoVervollständigung
Test-ParameterAutoVervollständigung -ParameterA Continue -ParameterB Mond -ParameterC Süden -ParameterC Amazon
Show-Command -Name Test-ParameterAutoVervollständigung

#endregion

#region Proxy-Funktionen
$metadata = New-Object -TypeName System.Management.Automation.CommandMetadata -ArgumentList (Get-Command -Name Stop-Process)
[System.Management.Automation.ProxyCommand]::Create($metadata) | Set-Clipboard
#endregion

#region Dynamische Parameter

# ! Die Herausforderung besteht darin die dynamischen Parameter programmatisch abzubilden

function Test-DynamicParameter {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Normal', 'Dynamic')]
        [string]$Mode
    )
    
    dynamicparam {
        $parameters = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

        if ($Mode -ceq "Dynamic") {
            # 1. dynamischer Parameter
            $attributes = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            $parameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
            $parameterAttribute.Mandatory = $true
            $attributes.Add($parameterAttribute)
            $parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList 'ID', System.Int32, $attributes
            $parameters.Add('ID', $parameter)

            # 2. dynamischer Parameter
            $attributes = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            $parameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
            $parameterAttribute.Mandatory = $false
            $attributes.Add($parameterAttribute)
            $vorschlagsliste = 'Erde', 'Mond', 'Sonne', 'Mars' # TODO z.B. aus dem AD, Exchange, Internet generieren
            $validateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute -ArgumentList ($vorschlagsliste)
            $attributes.Add($validateSetAttribute)
            $parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList 'Planet', System.String, $attributes
            $parameters.Add('Planet', $parameter)
        }

        return $parameters
    }
    begin {}
    process {
        Set-Variable -Name Id     -Value $PSBoundParameters.Id
        Set-Variable -Name Planet -Value $PSBoundParameters.Planet
        "Festgelegt wurde: Plant = $Planet | ID = $ID"
    }
    end {}
}

Test-DynamicParameter -Mode Normal -ID 4711 -Planet Mond
Test-DynamicParameter -Mode Dynamic -ID 4711 -Planet Mond

function Test-DynamicSoundParameter {
    [CmdletBinding()]
    param(
    )
    
    dynamicparam {
        # ! ACHTUNG die PowerShell ruft die Auswertung dieses Bereiches automatisch und oft auf daher beachte Performance evtl. Daten Cachen!
        $player=New-Object -TypeName System.Media.SoundPlayer -ArgumentList C:\Windows\media\tada.wav
        $player.Play()
    }
}
Test-DynamicSoundParameter

#endregion

#region Beispiele

#region BEISPIEL Get-Bios
function Get-Bios {
    Param (
        [String[]]$ComputerName = "localhost"
    )
    Get-CimInstance -ClassName Win32_BIOS -ComputerName $ComputerName |
        ForEach-Object -Process {
            return [PSCustomObject]@{
                PSComputerName    = $_.PSComputerName
                Status            = $_.Status
                Name              = $_.Name
                Caption           = $_.Caption
                SMBiosPresent     = $_.SMBiosPresent
                BIOSVersion       = $_.BIOSVersion
                CurrentLanguage   = $_.CurrentLanguage
                Description       = $_.Description
                Manufacturer      = $_.Manufacturer
                PrimaryBIOS       = $_.PrimaryBIOS
                SMBiosBiosVersion = $_.SMBiosBiosVersion
                Version           = $_.Version
            }
        }
}

#endregion

#region BEISPIEL Get-EuroExchange

# ! Siehe Modul AKPT

#endregion

#region BEISPIEL Get-News

function Get-News
{
    <#
        .Synopsis
           Zeigt RSS-Feeds an.
     
        .DESCRIPTION
           Zeigt ATOM Version 1.0 RRS-Feeds aus dem Internet oder Dateisystem an.
     
        .EXAMPLE
            Get-News -Uri http://rss.golem.de/rss.php?feed=ATOM1.0
 
            Liefert alle RSS-News-Feed von Golem.de.
 
        .EXAMPLE
            Get-News -Uri http://rss.golem.de/rss.php?feed=ATOM1.0 -First 3
         
        .EXAMPLE
            "http://www.heise.de/newsticker/heise-top-atom.xml", "http://rss.golem.de/rss.php?feed=ATOM1.0", "http://rss.golem.de/rss.php?tp=wirtschaft&feed=ATOM1.0" | Get-News -First 3
         
        .EXAMPLE
            Get-News -Uri "http://rss.golem.de/rss.php?feed=ATOM1.0" | ft
         
        .EXAMPLE
            Get-News -Uri "http://rss.golem.de/rss.php?feed=ATOM1.0" | where Titel -Like "E*" | ft
         
        .EXAMPLE
            Get-News -Uri "http://rss.golem.de/rss.php?feed=ATOM1.0" | Out-GridView
         
        .EXAMPLE
            Get-News -Uri "http://rss.golem.de/rss.php?feed=ATOM1.0" -First 2 -OpenLinkInBrowser
    #>

    Param
    (
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeLine = $true)]
        [string]
        $Uri,
        
        [UInt32]
        $First = [System.UInt32]::MaxValue,

        [switch]
        [bool]
        $OpenLinkInBrowser = $false
    )

    Begin # Zum Beginn 1x
    {
        $enUS        = New-Object -TypeName System.Globalization.CultureInfo -ArgumentList "en-US"
        $xmlDokument = New-Object -TypeName System.Xml.XmlDocument
    }
    Process # Je Objekt was über die Pip kommt
    {
        $FirstTemp = $First
        $xmlDokument.Load($Uri)
        foreach ($item in $xmlDokument.feed.entry)
        {
            if ($FirstTemp -gt 0)
            {
                $FirstTemp--
                $link = $item.link.href
                if($OpenLinkInBrowser)
                {
                    [System.Diagnostics.Process]::Start($link)
                    continue
                }

                $title     = $item.title.InnerText
                $published = [datetime]::Parse($item.published, $enUS)
                $author    = $item.author.name
                $summery   = $item.summary.InnerText
                
                $result = @{"Titel"=$title; "StandUTC"=$published; "Autor"=$author; "Link"=$link; "Beschreibung"=$summery}
                New-Object PSCustomObject -Property $result | Select-Object Titel, StandUTC, Autor, Link, Beschreibung
            }
            else
            {
                break
            }   
        }
    }
    End # Zum Ende 1x
    {
    }
}

#endregion

#region BEISPIEL Get-BigFile

function Get-BigFile
{
    <#
        .SYNOPSIS
            Zeigt eine Übersicht von großen Dateien und deren Besitzer.
         
        .EXAMPLE
            Get-BigFile -Path C:\ -GE 100MB
         
        .EXAMPLE
            "C:\Users", "C:\Program Files" | Get-BigFile -GE 100MB
    #>

    [CmdletBinding( HelpUri = 'http://www.gfu.net/')]
    [Alias("gbf")]
    [OutputType([PSCustomObject])]
    Param
    (
        # Pfad an dem große Dateien gefunden werden sollen
        [Parameter(ValueFromPipeLine = $true)]
        [string]
        $Path = ".",

        # Eine Datei ist Groß wenn sie größer gleich diesem Wert ist
        [Parameter(Mandatory = $true)]
        [UInt32]
        $GE = 10MB,
        
        [switch]
        [bool]
        $Recurse = $false
    )

    Process
    {
        Get-ChildItem -Path $Path -Recurse:$Recurse -File -Force -ea SilentlyContinue | 
            Where-Object -Property Length -GE -Value $GE | 
            ForEach-Object -Process {
                [PSCustomObject]@{ 
                    Name           = $_.Name;
                    Length         = $_.Length;
                    Owner          = $_ | Get-Acl -ea SilentlyContinue | Select-Object -exp Owner;
                    LastAccessTime = $_.LastAccessTime;
                    FullName       = $_.FullName;
                }
            }
    }
}
New-Alias -Name gbf -Value Get-BigFile -Force

#Get-Help Get-BigFile -ShowWindow
#Show-Command Get-BigFile
#Get-BigFile -Path c:\ -GE 100MB -Recurse
#Get-BigFile -GE 100MB
#"C:\Users", "C:\Program Files" | Get-BigFile -GE 100MB -Recurse | Out-GridView

#endregion

#region BEISPIEL Get-OldFile

function Get-OldFile
{
    <#
        .SYNOPSIS
            Ermittelt alte Dateien.
                          
        .DESCRIPTION
            Ermittelt alte Dateien nach Anzahl Tage.
 
        .INPUTS
 
        .OUTPUTS
            Liefert ein ein PSObject mit folgenden Eigenschaften.....
 
        .EXAMPLE
            Get-OldFile -Path C:\Windows -OlderThanDays 180
            Alle alte Datei aus c:\windows die älter sind als 180 Tage.
 
        .EXAMPLE
            Get-OldFile -Path C:\Windows -OlderThanDays 180 -Recurse
            Alle alte Datei aus c:\windows und Unterordner die älter sind als 180 Tage.
    #>

    param(
        [Parameter(ParameterSetName  = "Days", 
                   ValueFromPipeline = $true)]
        [Parameter(ParameterSetName  = "DateTime", 
                   ValueFromPipeline = $true)]
        [ValidateScript({Test-Path $_})]
        [string]$Path = ".", 
        
        # Gültige Werte für Tag sind 1 bis 4000
        [Parameter(Mandatory        = $true, 
                   ParameterSetName = "Days")]
        [ValidateRange(1, 4000)]
        [UInt32]$OlderThanDays, 
        
        [Parameter(ParameterSetName = "Days")]
        [Parameter(ParameterSetName = "DateTime")]
        [switch]
        [bool]$Recurse,

        [Parameter(Mandatory        = $true, 
                   ParameterSetName = "DateTime")]
        [ValidateScript({$_ -le (Get-Date)})]
        [datetime]$OlderThan
    )

    begin { # Initialisierungs-Code
        $lastAccessTime = $null
        switch ($psCmdlet.ParameterSetName)
        {
            'Days'     {$lastAccessTime = (Get-Date).AddDays($OlderThanDays * -1)}
            'DateTime' {$lastAccessTime = $OlderThan}
        }
    }
    
    process
    {
        Get-ChildItem -Path $Path -Recurse:$Recurse -File -ErrorAction SilentlyContinue | 
            Where-Object -Property LastAccessTime -lt $lastAccessTime | 
            ForEach-Object -Process {
            $now = Get-Date
            $owner = $_ | Get-Acl | Select-Object -ExpandProperty Owner
            $result = [ordered]@{
                Name           = $_.Name; 
                AgeInDays      = [Math]::Round(($now - $_.LastAccessTime).TotalDays, 2); 
                Owner          = $owner;
                LastAccessTime = $_.LastAccessTime; 
                FullName       = $_.FullName}
            New-Object PSObject -Property $result
        }
    }
}
#Get-Help Get-OldFile -ShowWindow
#Show-Command Get-OldFile
Get-OldFile -Path c:\ -Recurse -OlderThanDays 1200 | Out-GridView
Get-OldFile -Path c:\ -Recurse -OlderThan "2013-12-31" | Out-GridView

#
# KOMPONENTENTEST
#

#Get-OldFiles -Path C:\Windows -OlderThanDays 180 -Recurse
#Get-OldFiles -Path C:\Windows -OlderThanDays 180 | Out-GridView
#Get-OldFiles -OlderThanDays 180 -Path C:\Windows -Recurse
#Get-help Get-OldFiles -ShowWindow
#Get-OldFiles -Path C:\Windows -OlderThanDays 180 -OlderThan (Get-Date).AddMonths(-50)
#Get-OldFiles -Path C:\Windows -OlderThanDays 180
#Get-OldFiles -Path C:\Windows -OlderThan (Get-Date)
#Get-Help Where-Object -Online
#"c:\windows", "C:\windows\System32" | Get-OldFiles -OlderThanDays 180

#endregion

#region BEISPIEL Get-Product

# ! Siehe Modul AKPT

#endregion

#region BEISPIEL Get-BingPicture

# ! Siehe Modul AKPT

#endregion

# TODO Siehe auch weitere Beispiel in meinem Modul AKPT
Install-Module -Name AKPT

#endregion

#region Übungen

<# TODO ÜBUNG A
    ! VERTIEFUNG: Cmdlet lesen/verstehen
    ? Lesen Sie das Cmdlet 'Get-Product' (s. Modules\AKPT) und erklären Sie es!
#>


<# TODO ÜBUNG B
    ! VERTIEFUNG: Cmdlet lesen/verstehen
    ? Lesen Sie das Cmdlet 'Get-EuroExchange' (s. Modules\AKPT) und erklären Sie es!
#>


<# TODO Übung C
   ? Erstellen Sie ein Cmdlet 'Get-Hello'.
   ? A) Folgende Ausführungen sollen positive Ergebnisse auslösen:
         * Get-Hello -Name "Peter" -Culture "DE" # => Hallo Peter!
         * Get-Hello -Name "Peter" -Culture "US" # => Hello Peter!
         * Get-Hello -Name "Peter" -Culture "SP" # => Ola Peter!
         * Get-Hello -Name "Peter" # Default: DE d.h. => Hallo Peter!
         * "Peter", "Inge" | Get-Hello -Culture "SP" # => Ola Peter! Ola Inge!
   ? B) Folgende Ausführungen sollen auf einen Fehler laufen:
         * Get-Hello -Culture "SP" # ! => Fehler!
         * Get-Hello # ! => Fehler!
         * Get-Hello -Name "Peter" -Culture "XX" # ! Fehler!
         * Get-Hello -Name "" # ! => Fehler
         * Get-Hello -Name "Peter", "Inge" # ! => Fehler
   ! TIPPS Siehe Statisch Lösung => Dynamische Lösung => Cmdlet (s.u.a. Grundgerüst Get-EuroExchange.ps1)
#>


<# TODO ÜBUNG D
    ! VERTIEFUNG: Cmdlet erstellen
 
    ? Erstellen Sie das Cmdlet 'Add-DateTime' dessen Aufgabe es ist
    ? von einem DateTime-Objekt (z.B. Get-Date) einen Anzahl an Zahlen
    ? zu addieren für Jahr, Monat und Tag.
    ? Folgende Syntax soll implementiert werden:
    ? Add-DateTime -BaseDateTime (Get-Date) -Years 1
    ? Add-DateTime -BaseDateTime (Get-Date) -Months 2
    ? Add-DateTime -BaseDateTime (Get-Date) -Days 3
    ? Add-DateTime -BaseDateTime (Get-Date) -Years 1 -Months 2 -Days 3
    ? Add-DateTime -BaseDateTime (Get-Date) -Years 1 -Days 3
    ? Get-Help -Name Add-DateTime -ShowWindow # ? Hilfe-Information i.O.
    ? Show-Command -Name Add-DateTime -ErrorPopup -NoCommonParameter # ? Bedienbar
     
    TIPPS: (Get-Date).AddYears(1); (Get-Date).Month(2); (Get-Date).Day(3)
#>


<# TODO ÜBUNG E
    ! VERTIEFUNG: Cmdlet erstellen
    ? Erstellen Sie das Cmdlet 'New-LocalUserFromCsv' dessen Aufgabe es ist
    ? neue lokale Windows-Benutzer über eine .CSV-Datei zu erstellen.
    ? Als Basis dient das Beispiel am Ende der 'B04_Pipelining.ps1'-Datei.
    ? Folgende Syntax soll implementiert werden:
      New-LocalUserFromCsv -CsvFile C:\temp\NewUsers.csv -WhatIf # Simuliert das erstellen => [switch]$WhatIf
      New-LocalUserFromCsv -CsvFile C:\temp\NewUsers.csv # Erstellt die Benutzer
      New-LocalUserFromCsv # ! FEHLER da der Parameter 'CsvDatei' obligatorisch sein muss
      Get-Help -Name New-LocalUserFromCsv -ShowWindow # ? Hilfe-Information i.O.
      Show-Command -Name New-LocalUserFromCsv -ErrorPopup -NoCommonParameter # ? Bedienbar
#>


<# TODO ÜBUNG F
    ! VERTIEFUNG: Cmdlet erstellen
    ? Erstellen Sie das Cmdlet 'Get-EmptyDirectory' dessen Aufgabe es ist
    ? leere Ordner in Form von DirectoryInfo-Objekte zurückzugeben.
    ? Folgende Syntax soll implementiert werden:
      Get-EmptyDirectory -Path c:\temp
      Get-EmptyDirectory # Aktueller Ordner
      Get-EmptyDirectory -Path c:\temp -Recurse
      Get-EmptyDirectory -Path Z:\DiesenOrdnerGibEsNicht # FEHLER ausgeben da der Ordner nicht vorhanden ist!
      Get-Help -Name Get-EmptyDirectory -ShowWindow # Hilfe-Information i.O.?
      Show-Command -Name Get-EmptyDirectory -ErrorPopup -NoCommonParameter # Bedienbar?
#>


<# TODO Übung G (about-Hilfe)
    ? Des öfteren vergessen Benutzer ihr Passwort. Daher muss der Administrator das Passwort zurücksetzen. Hierbei bekommt der Benutzer ein Initial-Passwort das er bei der nächsten Anmelden ändern muss. Um diesen Prozess zu vereinfachen erstellen SIE ein Cmdlet ** Reset-UserPassword** das wie folgt parametrisiert werden kann:
      Reset-UserPassword -Username pLustig
    ? WICHTIG: Der Output des Cmdlets enthält das zufällig generierte Passwort.
    ! TIPPS: Get-LocalUser ; Where-Object ; Set-LocalUser ; Get-Random ; New-LocalUser ; WIN+X => Computerverwaltung
#>


<# TODO ÜBUNG H
    ! VERTIEFUNG: Cmdlet erstellen
    ? Erstellen Sie das Cmdlet 'Reset-LocalUserPassword' dessen Aufgabe es ist
    ? das Passwort eines vorhanden Benutzers zur auf ein Initialpasswort zurückzusetzen
    ? was der Benutzer nur einmal verwenden kann um ein neues Passwort zu vergeben.
     
    ? Folgende Syntax soll implementiert werden:
    ? 1. Reset-LocalUserPassword -Name user1 -InitializePhrase P@ssw0rd
    ? 2. Reset-LocalUserPassword -Name user1 # ! Validieren, da der Parameter -InitializePhrase
    ? 3. Reset-LocalUserPassword -InitializePhrase P@ssw0rd # ! Validieren, da der Parameter -Name
    ? 4. Reset-LocalUserPassword -Name "" -InitializePhrase P@ssw0rd # ! Validieren, da min. 1 Zeichen nötig ist
    ? 5. Reset-LocalUserPassword -Name 123456789012345678901 -InitializePhrase P@ssw0rd # ! Validieren, da max. 20 Zeichen erlaubt sind
    ? 6. "user1", "user2" | Reset-LocalUserPassword -InitializePhrase P@ssw0rd
    ? 7. "user1,123", "user2,098" | ConvertFrom-Csv -Header Name, InitializePhrase | Reset-LocalUserPassword
    ? 8. Get-Help -Name Reset-LocalUserPassword -ShowWindow # ! Hilfe-Information i. O. ?
    ? 9. Show-Command -Name Reset-LocalUserPassword -ErrorPopup -NoCommonParameter # ! Bedienbar ?
     
    TIPPS:
           Get-Help -Name about_Comment_Based_Help -ShowWindow # * bzgl. Cmdlet eigen Hilfeinformationen
           Get-Help -Name about_Functions_Advanced_Parameters -ShowWindow # * bzgl. Mandatory, ValidateLength
           Get-Help -Name about_Functions_CmdletBindingAttribute -ShowWindow # * bzgl. ValueFromPipeline, ValueFromPipelineByPropertyName
           Get-Help -Name about_Functions_OutputTypeAttribute -ShowWindow # * bzgl. Output
           Get-Help -Name Set-LocalUser -Online # * bzgl. Passort zurücksetzen
#>


#region # ! MUSTERLÖSUNGEN

<# * zu Übung E
-----BEGIN CMS-----
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
K7QygbY0YNbMw7PP8deegNU+gebq4kM=
-----END CMS-----
#>


<# * zu Übung F
-----BEGIN CMS-----
MIIFzAYJKoZIhvcNAQcDoIIFvTCCBbkCAQAxggKAMIICfAIBADBkMFAxJTAjBgkqhkiG9w0BCQEW
FnMubWFuQGtyeXB0b24udW5pdmVyc2UxJzAlBgNVBAMMHl9BS1BUIFN1cGVybWFuIChEb2N0b3Ig
Uy4gTWFuKQIQV5/kBauf95JOy7DMCzlZwzANBgkqhkiG9w0BAQcwAASCAgBWCmROtvB1IK5esmsn
ZiWvhN9a3VH/k1Feeq2XEBCa7D6Z0Ji7osKE+0tTZ8Rn1UJciVahyvBHZ7rTEPy+qWsyHFUSjYfK
CvxFADCvcFSP3/lqXveIN3UGyrVxvwQXXoV+YzKF3Pr1ULH81opcQViw3qYkhKGFMgv9RJ15kIlx
LAR9wy7ybhvUy2+KBJKO/snxd7FBe6WGRAYSQGDncbHKA1VnJWhI9nE8/YVFNy/Pm40wkJftpDcB
CbFK1G5TV2zEULLMbNViqUzfIwrhCBa+jDVIPU84EWggaF0f6HAoBKpUe0MvRN+4uhAOifn2vX2p
HftFnrOsmT45unAg2Nxr1Pi737EQX8AxxlWfVUXRGM4UA5ZhwF9HyNfiKeazyceetJvQqzuFabp4
PmNNkUtSxM93rz+9nquu3amrSed4+mCd9RosieKmFGLnTnbwPP63yHMZMcg/rG0fYc/xy5Z9/9Az
8LLd5kTAH5nTAypFvBP9mgc3msJDLdZTcXdAGfmqd02uGEnMypoQfNu9Odb/On1ACnZF4EoHVWnh
L/jhlyXdRbiUt5OEnO6rqpo6QpRxv15bk2k0Ts8+mtwC6vUHTVQfR9QYAJ56Jjspanw+XkS0tkHb
6BpLa9m8zOUoy0pap6oySYBTyvNj3GGtvwAFXTy5W5+V1LU3V+TNSXk6+TCCAy4GCSqGSIb3DQEH
ATAdBglghkgBZQMEASoEEChxUtg4iy38GVzyCXGd51yAggMA4hrr/aZ0qChaiJEHx4GwQ8VlxeML
nvrgA9/UpGZKY0050pX0GhXHaX5cLIm+tmKnIPNltUkf3lyt4FIXlNjvxxqZVfgaoseRs9ZLjfdu
tzwg2KgFldviLbZl3iFaAZpK4BuL0NzU00PveusDjEctCQjllK0gUDXWVen+1Vhhk4J2vSn5VIYg
qRJlTNd+OZW+QEPPbKe4zq0qDuF4sAivvuK9RfyjWHD+RvCrKo0QPeh08uT8tae0Ka65FxgYaSyv
8WeF/3IbZK+uSjuMrglF/y/wBlzRN7bOFYDmvh6sDTE1++NHFxZsuglQqFX4EpFr5+CLvMHJ320o
43K+SiwgcAuu1ECa2sbWFN+F50dAWgTVUHCKa0kvWrTyN60TkihNVlLn+pW+rI8H5HgJZFir9eQd
GKPxAu2YhykeniuSFF6GOR1iMyG/q5hItoaZCqt072ePVhk7oSbdRYPFXJ+63dIHvQpxHzXFviQc
j5BVw7I9SpqQLa2zzPB7cBl3+xi2Y+awt7KQ13ZNWfXja1ZPOM1CeizHyGs9hA/RLsPbOCP/AaWB
Hu8oC1h1B+J8P3G55IqA9rA0+w0DjP1uHWVuMY8qXo4P4H/BbbU5VyVhI9tA3PThLVfXdgLIHsZz
WUbcSrZkX9aByT+yk3CQvmPOeFj9KjqPplqJb5cQFlZrKimFg3mNsv5lNsOXEt8fHQ8X6hn1aKOQ
ZFrkfRt3MxPq+ShVCRhpUM0RWnvTviGOTIovgxKq4a9RIonO8tGSyLFTW/Dy/+lfTou1F23ZgVHt
sX6p0W3jNi07HSRwVUEWD+1qfAbYnNoeMFAZtkFO1ccQpSN9ggvziiZMsYk91RDi9lFuuAxiMcZ3
3VIiX9A2GiCIPCw7hpLM04hPulwY+dj1QAXbmu0I3uL/xsNGCMn2W/wQGFz4+gPcu9q590DjsDOd
eoxYI5a/Y3DbQqxplt8vSxe8/zTcvu63PQ+ARlfirdxP6/yyzeoUwDxADpRW//W/iWlbp11bcHJm
/p7i+Lnu
-----END CMS-----
#>


<# * zu Übung G
-----BEGIN CMS-----
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
-----END CMS-----
#>


<# * zu Übung H
-----BEGIN CMS-----
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
-----END CMS-----
#>


#endregion

#endregion