Private/Wissen/C_Advance/C10_UserModules.ps1

<#
 
# User Module
 
Eigene Module erstellen
 
- **Hashtags** Module Repository Deployment PowerShellGallery
- **Version** 2020.10.15
 
#>


#region Vorbereitung

# 1. Installationsort des Moduls festlegen (möglich sind: $env:PSModulePath -split ';')
$destinationModulePath = Join-Path -Path $env:USERPROFILE -ChildPath '\OneDrive\Documents\WindowsPowerShell\Modules'

# 2. Test-Cmdlet erzeugen
@'
function Get-Hello {
    param ([string]$Name = 'Du')
    "Hello $Name!" | Write-Output
}
'@
 | Set-Content -Path 'C:\Temp\Get-Hello.ps1'

# 3. OPTIONAL Ein Zertifikat zum signieren auswählen
$cert = Get-ChildItem -Path 'C:\Temp\MyCodeSigningCert.pfx' | Get-PfxCertificate
Set-AuthenticodeSignature -FilePath 'C:\Temp\Get-Hello.ps1' -Certificate $cert

#endregion

#region Module erstellen

# ! 1. Jedes Module benötigt einen "eindeutigen" Namen:
$moduleName = 'ABC'

# ! 2. Für das neue Module den Modul-Ordner erstellen
$destinationModulePath = Join-Path -Path $destinationModulePath -ChildPath $moduleName
New-Item -Path $destinationModulePath -ItemType 'Directory' -Force
Set-Location -Path $destinationModulePath

New-Item -Path $destinationModulePath -Name 'Public'  -ItemType Directory # ! Öffentlicher Teil des Module
New-Item -Path $destinationModulePath -Name 'Private' -ItemType Directory # ! Interner Teil des Module, z.B. Hilfs-Funktion
New-Item -Path $destinationModulePath -Name 'de-DE'   -ItemType Directory # ! Sprach-Land-Bezogene Inhalte (weitere z.B. en-US, jp-JP, usw.)

# ! 3. Eigene Cmdlets in den Modulordner kopieren
Copy-Item -Path 'C:\Temp\Get-Hello.ps1' -Destination '.\Public\' -Force -Verbose

# ! 4. .PSM1-Datei erstellen
# ! Der darin enthaltene Code wird ausgeführt, wenn das Modul importiert wird.
New-Item -Path .\$moduleName.psm1 -ItemType File -Force
@'
Get-ChildItem "$PSScriptRoot\Public\*.ps1" | ForEach-Object {
    . $_.FullName
}
'@
 | Add-Content .\$moduleName.psm1

# ! OPTIONAL: Fügen Sie folgenden Code der .PSM1-Datei hinzu, um einen Ereignis-Handler für das
# ! Entladen des Moduls hinzuzufügen.
@'
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    'Module wurde entladen.' | Write-Warning # TODO: Code z.B. zum Aufräumen bei entladen dieses Modules.
}
'@
 | Add-Content -Path .\$moduleName.psm1

# ! OPTIONAL: PSM1-Datei signieren
Set-AuthenticodeSignature -FilePath .\$moduleName.psm1 -Certificate $cert

# ? Inhalt der .PSM1-Datei anzeigen
Start-Process -FilePath .\$moduleName.psm1 # TODO Inhalt korrekt bzw. ergänzen

# ! 5. .PSD1-Datei erstellen (Manifest-Informationen über das neue Modul)
$params = @{
    Path            = ".\$moduleName.psd1"
    RootModule      = $moduleName
    Guid            = '38ed2cd7-d6ae-44fe-bb28-7a8a21247dfe' # Eine eindeutig GUID die das Modul ein lebenlang behält # TODO eine neue GUID erzeugen über: New-Guid
    Author          = 'VORNAME NACHNAME'
    CompanyName     = 'DIE FIRMA'
    Copyright       = '(c) 2019 by DIE FIRMA'
    Description     = 'Test-Module aus der PowerShell-Schulung - Aufbaukurs'
    ModuleVersion   = [Version]::new(2020, 12, 18, 511040) # ! major.minor.build.revision => major.minor (Neue Feature), build (Bugfixing), revision (Ausroll-Varianten)
    CmdletsToExport = Get-ChildItem -Path '.\Public\*-*.ps1' | Select-Object -ExpandProperty 'BaseName'
}
New-ModuleManifest @params
# TODO Die .psd1-Datei kann auch komfortabel per TXT-Editor bearbeitet werden:
Start-Process -FilePath .\$moduleName.psd1 -Wait # TODO Inhalt korrekt bzw. ergänzen

#endregion

#region Erstelltes Module testen

# ? Wird das Module in der Liste der installierten Module angezeigt:
Get-Module -Name $moduleName -ListAvailable

# ? Lässt sich das Modul manuell importiert?
Remove-Module -Name $moduleName -Force -ErrorAction Ignore
Import-Module -Name $moduleName -Verbose -Force
Get-Module -Name $moduleName

# ? Wird das Modul entladen?
Remove-Module -Name $moduleName
Get-Module -Name $moduleName

# ? Werden alle Cmdlets, Aliase, Funktionen etc. aufgelistet?
Get-Command -Module $moduleName -All

# ? Können die Modul-Features genutzt werden?
Get-Hello -Name 'Attila'

# ? ABC-Module in Show-Command nutzbar?
Show-Command -NoCommonParameter -ErrorPopup

#region Pester-UTest zum Module ABC

# Pester 5.1.0
# Abc.Tests.ps1

$AbcModulePath = Join-Path -Path $env:USERPROFILE -ChildPath '\OneDrive\Documents\WindowsPowerShell\Modules\ABC'
Describe 'Modul ABC Test' {

    Context 'Ordner- und Datei-Struktur testen' {
        It 'Modul-Ordner heißt ABC und ist vorhanden' { Test-Path -Path $AbcModulePath                | Should -Be $true }
        It '.PSD1-Dateiname heißt ABC.psd1'           { Test-Path -Path "$AbcModulePath\ABC.psd1"     | Should -Be $true }
        It '.PSM1-Dateiname heißt ABC.psd1'           { Test-Path -Path "$AbcModulePath\ABC.psm1"     | Should -Be $true }
    }

    Context 'Enthaltene Cmdlets testen' {
        It 'Das Module ABC enthält das Skript Get-Hello.ps1'    { Test-Path -Path "$AbcModulePath\Public\Get-Hello.ps1" | Should -Be $true   }
    }

    Context 'Modul-Manifest-Datei validieren' {
        It 'Manifest frei von Exceptions' {
            { $Script:Manifest = Test-ModuleManifest -Path "$AbcModulePath\ABC.psd1" -ErrorAction 'Stop' -WarningAction 'SilentlyContinue' } | Should -Not -Throw
        }
        It 'Manifest Name ist ABC'                  { $Script:Manifest.Name                  | Should -Be 'ABC'                                   }
        It 'Manifest Version ist vom Typ [Version]' { $Script:Manifest.Version -as [Version] | Should -Not -BeNullOrEmpty                         }
        It 'Manifest Beschreibung gesetzt'          { $Script:Manifest.Description           | Should -Not -BeNullOrEmpty                         }
        It 'Manifest Root-Name ist ABC'             { $Script:Manifest.RootModule            | Should -Be 'ABC'                                   }
        It 'Manifest GUID korrekt'                  { $Script:Manifest.Guid                  | Should -Be '38ed2cd7-d6ae-44fe-bb28-7a8a21247dfe'  }
        It 'No Format File'                         { $Script:Manifest.ExportedFormatFiles   | Should -BeNullOrEmpty                              }
    }

    Context 'Funktionstest' {
        It 'Modul in der Liste der installierten Module enthalten' { Get-Module -Name ABC -ListAvailable | Should -Not -BeNullOrEmpty}
    }
}

#endregion

#endregion

#region Ein privates Repository einrichten

# ! Denkbar über bereits vorhandene Repositories wie z.B. Artefactory, Nexus, etc.)
# ! Oder über eine Dateifreigabe (Modul-Autoren R/W-, Nutzer R-Rechte):

# ? Repository erstellen
New-Item -Path 'C:\Temp' -Name 'AbcGallery' -ItemType 'Directory'
New-SmbShare -Name 'AbcGallery' -Path 'c:\temp\AbcGallery' -ReadAccess 'Benutzer' -FullAccess 'Administratoren'
Register-PSRepository -Name 'AbcGallery' -SourceLocation '\\localhost\AbcGallery' -InstallationPolicy 'Trusted' -Verbose # TODO per GPO verteilen
Get-PSRepository

# ? Modul veröffentlichen
Publish-Module -Name '..\ABC' -Repository 'AbcGallery' -Verbose

# ? Modul installieren
Find-Module -Name 'ABC' -Repository 'AbcGallery' -AllVersions -AllowPrerelease
Install-Module -Name 'ABC' -Repository 'AbcGallery' -Scope CurrentUser -AllowClobber

# ? Repository wieder löschen
Uninstall-Module -Name 'ABC' -Force
Unregister-PSRepository -Name 'AbcGallery'
Remove-SmbShare -Name 'AbcGallery' -Force
Remove-Item -Path 'c:\temp\AbcGallery' -Recurse -Force

#endregion

#region Ein eigenes Module in die https://www.powershellgallery.com veröffentlichen

# ! 1. Folgende Pakete, Module und Dateien aktualisieren (ADMIN-Rechte):
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force
Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile '$env:LocalAppData\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe'
Unblock-File -Path '$env:LocalAppData\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe'
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module –Name PowerShellGet –Force
Update-Module –Name PowerShellGet
Remove-Module -Name * -Force

# ! 2. Den Deploy-Vorgang zuerst in einem privaten Repository testen
New-Item -Path 'C:\Temp' -Name 'AbcRepository' -ItemType 'Directory'
New-SmbShare -Name 'AbcRepository' -Path 'c:\temp\AbcRepository' -ReadAccess 'Benutzer' -FullAccess 'Administratoren'
Register-PSRepository -Name 'AbcRepository' -SourceLocation '\\localhost\AbcRepository' -InstallationPolicy 'Trusted' -Verbose
Publish-Module -Name '.\AKPT' -Repository 'AbcRepository' -Verbose
Find-Module -Name 'AKPT' -Repository 'AbcRepository' | Select-Object -Property Name, Version, Description, Author, CompanyName, Copyright, ProjectUri, Tags, Dependencies, ReleaseNotes
Install-Module -Name 'ABC' -Repository 'AbcRepository' -Scope CurrentUser
Uninstall-Module -Name 'ABC' -Force
Remove-SmbShare -Name 'AbcRepository' -Force
Remove-Item -Path 'c:\temp\AbcRepository' -Recurse -Force

# ! 3. Module in der PowerShellGallery veröffentlichen
Publish-Module -Name '.\..\AKPT' -NuGetApiKey '??????????????????????????????' -Verbose

#endregion

#region Übungen

<# TODO Übung 1 (User Module)
    1. Erweitern Sie das Modul ABC um das folgende Cmdlet:
    function Get-About() {
        Get-Help -Name 'about_*' | Out-GridView -OutputMode Multiple -Title 'About-Seite(n) auswählen' | Get-Help -ShowWindow
    }
 
    2. Testen Sie ihre ABC-Modul-Erweiterung.
 
    3. OPTIONAL, um die Schritte 1. und 2. nachvollziehen zu können, implementieren Sie Ihre Lösung per PowerShell-Code.
#>


#endregion