Theorie Powershell

Inhalt

1. Teil
1.1 Vorbereitungen
1.2 Ausführungsrichtlinien und Codierungsregeln
1.3 Hilfe erhalten
1.4 Absoluter und relativer Pfad
1.5 Script-Programme starten
1.6 Powershell-Script-Template

2. Teil
2.1 Datentypen
2.2 Input/Output
2.3 Vergleichsoperatoren und Logische Operatoren
2.4 Die Selektion
2.5 Die Iteration
2.6 Try and catch
2.7 Abstraktion / Funktion / Methode / Filter
2.8 Piping
2.9 Redirect
2.10 Alias

3. Teil
3.1 Das objektorientierte Programmierparadigma
3.2 Zugriff auf Eigenschaften und Methoden von Objekten
3.3 Statische Eigenschaften und Methoden von .Net-Klassen verwenden
3.4 Klassen instanzieren

4. Teil
4.1 Standardobjekte: Zählfunktionen
4.2 Standardobjekte: String-Methoden und Funktionen
4.3 Standardobjekte: DateTime-Methoden und Funktionen

5. Teil
5.1 Ausgabe in eine HTML-Datei
5.2 Mit Dateien arbeiten
5.3 Lokale Benutzer und Gruppen verwalten
5.4 Arbeiten in der Registry
5.5 Arbeiten mit Eventlogs
5.6 Arbeiten mit COM-Objekten
5.7 Arbeiten mit Webformularen
5.8 Arbeiten mit .net-Objekten - Erstellen einer grafischen Benutzereingabe

6. Teil
6.1 Einführung Testen von Software
6.2 Analytische Qualitätssicherung zur Vermeidung von Fehlern
6.3 Testplan, Testdefinition

1. Teil

1.1 Vorbereitungen

Windows PowerShell bietet umfassende Möglichkeiten der Systemverwaltung und -automatisierung auf der Windows-Plattform. PowerShell ist eine Kommandozeilen- und Skriptumgebung, welche auf dem .NET Framework basiert. Sie erlaubt die Steuerung und Automatisierung von Windows Server aber auch von Anwendungen bzw. Services wie Active Directory, Hyper-V, Exchange Server, System Center, Skype for Business, Citrix und VMware ESX Server usw. PowerShell-Kenntnisse sind heute unerlässlich für Windows-Systemadministratorinnen und -administratoren – sei es für Installationen vor Ort oder in der Cloud. Seit der Version 5.1 steht mit PowerShell Core parallel dazu eine Cross-Plattform-Variante zur Verfügung, die auch für Linux und macOS eingesetzt werden kann.

Zur mächtigen Skriptsprache wird Powershell dank der .Net-Bibliothek, die mittlerweile eine ansehnliche Grösse erreicht hat und ständig am weiterwachsen ist. Aus diesem Grund lädt Powershell nur die am häufigsten benötigten Teile von .Net in den Arbeitsspeicher. Benötigt man allerdings eine .Net-Komponente, die standardmässig nicht geladen ist, muss man diese explizit nachladen (Siehe 1.1.2 Nachladen von .Net-Komponenten). Um beispielsweise eine komfortable GUI-Schnittstellen zu realisieren, muss man die Windows.Forms-Assembly laden, bevor man dann ein z.B. Formular-Objekt erstellen und mit Schaltflächen, Textboxen und weiteren Dingen bestücken kann.

1.1.1 PowerShell-Version anzeigen

$PSVersionTable

1.1.2 Nachladen von .Net-Komponenten (Verschiedene Varianten)

#Load-Methode, falls man den "Full Name" der Komponete kennt:
[system.reflection.assembly]::Load("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

#Die Alternative ohne "system":
[reflection.assembly]::Load("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

#LoadForm-Methode, falls man den absoluten Pfad zur Komponente kennt:
[reflection.assembly]::LoadFrom("C:/Windows/Microsoft.Net/.../System.Windows.Forms/v4.0_4.0.0.0__b77a5c561934e089/System.Windows.Forms.dll")

#LoadWithPartialName-Methode, falls man nur Teile des "Full Name" der Komponente kennt:
[reflection.assembly]::LoadWithPartialName("Windows.Forms")

#Mit CmdLet Add-Type: (Funktioniert nicht immer. Benötigt z.T. "Full Name")
Add-Type –AssemblyName Windows.Forms

1.2 Ausführungsrichtlinien und Codierungsregeln

# ********** AUSFÜHRUNGSRICHTLINIEN IN DER POWERSHELL-KONSOLE ANPASSEN **********
# Um Scripte im Entwicklungsstadium vorbehaltlos ausführen zu können, empfiehlt es sich,
# im Powershell-Terminal die Powershell-Ausführungsrichtlinien anzupassen.
# GELOCKERTE WPS-SICHERHEITS-BARRIEREN = GELADENE PISTOLE!   ......denn sie wissen (nicht), was sie tun...
# 
Set-ExecutionPolicy AllSigned       # Nur signierte Scripts werden ausgeführt
Set-ExecutionPolicy RemoteSigned    # Aus dem Internet heruntergeladene Scripts müssen signiert sein
Set-ExecutionPolicy Unrestricted    # Bevorzugt! Alle Scripts werden ausgeführt. Unsignierten Scripts aus dem Internet müssen bestätigt werden
Set-ExecutionPolicy Bypass          # Keinerlei Einschränkungen, Warnungen oder Prompts
Set-ExecutionPolicy Undefined       # Entfernt eine zugewiesene Richtlinie

# ********** CODIERUNGSREGELN IN POWERSHELL-SKRIPT ERZWINGEN **********
# Eigentlich käme man auch ohne aus. Allerdings zwingt dies zum disziplinierten Coden und wirkt prophylaktisch gegen Programmierfehler.
#
# set-psdebug aktiviert/deaktiviert Debuggingfunktionen, Ablaufverfolgungsebene wird festgelegt bzw. Strict-Modus umgeschaltet
set-psdebug -off                  # Deaktiviert alle Skript-Debuggingfunktionen
set-psdebug -strict               # TBZ! Interpreter meldet, wenn auf eine Variable verwiesen wird, bevor ihr ein Wert zugewiesen wurde

# set-strictmode aktiviert bedeutet Abbruch, falls Codierungsregeln gebrochen werden
set-strictmode -off               # Deaktiviert Strict-Modus und set-psdebug -strict
set-strictmode -version latest    # Bevorzugt! Verhindert u.a. Verweise auf nicht initialisierte Variablen

# HINWEIS: Set-StrictMode ähnelt dem Strict-Parameter von Set-PSDebug.
# "Set-Strictmode -version 1" entspricht "Set-PSDebug -strict" mit der Ausnahme,
# dass sich Set-PSDebug auf alle Bereiche auswirkt.
# Set-StrictMode wirkt sich nur auf den festgelegten Bereich sowie auf die untergeordneten Bereiche aus.

1.3 Hilfe erhalten

# Bevor sie in der Powershell ISE stundenlang an einem Fehler herumsuchen, der unter Umständen gar keiner ist,
# beenden sie die Powershell ISE und starten sie diese neu.
# Falls sie ihre Powershell-Skripte aus der Powershell-ISE heraus ausführen, sollten sie beachten, dass ihre Variablen
# nach beendeter Skriptausführumng in der Powershell ISE immer noch bestehen.
#
# Unterschied CMD-TOOL ↔ CMDLET
# cmd-Tools → Tools aus der alten Betriebssystem-Shell cmd bzw. Windows-Eingabeaufforderung)
#                  Funktionieren auch in der Powershell.
#                  Bsp.: cd, dir, xcopy etc.

# CmdLet's  → Powershell-Tools
#                  Funktionieren nicht in der alten Betriebssystem-Shell cmd
#                  Bsp: get-ChildItem, get-Process etc.

# Namensaufbau von CmdLet's: [VERB]-[NAMEN]. Beispiele:
get-process
get-help
start-Process
write-Output

# Alle möglichen Verben anzeigen lassen
get-verb                          # Zeigt alle möglichen Verben wie z.B. "get" an
#-----------------------------------------------------------------------------------------------------------------------
# So erhält man Hilfe bzw. Klarheit, wenn man einmal nicht mehr weiter weiss...
update-help                       # Lädt die aktuellsten Helpfiles herunter
get-help                          # Hilfe zum Hilfesystem anzeigen
get-help about*                   # Zeigt alle Helpfile-Einträge an
get-help about_for                # Zeigt Hilfetext zur for-Schleife an
get-help about_Functions          # Zeigt Hilfetext zu Funktionen an
get-help about_Parameters         # Zeigt Hilfetext zu Parametern an
get-help Write-Eventlog -full     # Zeigt kompletten Hilfetext zu Write-Eventlog an
get-help Write-Eventlog -examples # Zeigt Beispiel-Skript zu Write-Eventlog an
#-----------------------------------------------------------------------------------------------------------------------
# Alle vorhandenen cmd-Let's anzeigen lassen: 
get-command                       # Zeigt alle Alias/CmdLet/Function an
get-command get*                  # Zeigt alle Alias/CmdLet/Function mit dem Verb "get" an (* → Wildcard)
get-command *alias*               # Zeigt alles an, das die Bezeichnung "alias" im Namen enthält
#-----------------------------------------------------------------------------------------------------------------------
# Properties, Methods und Datentyp von einem Objekt anzeigen:
[string]$myStr = "Hello"          # Deklaration und Initialisierung der ersten Beispielvariablen
[int]myInt = 133                  # Deklaration und Initialisierung der zweiten Beispielvariablen
$myStr | get-member               # Zeigt alle Eigenschaften und Methoden eines String-Objektes an
$myInt | get-member               # Zeigt alle Eigenschaften und Methoden eines Integer-Objektes an
$myStr.gettype().fullname         # Zeigt den genauen Datentyp an → System.String
$myInt.gettype().fullname         # Zeigt den genauen Datentyp an → System.Int32
"Hallo" | Get-Member              # | → Pipeline/Objekt-Weitergabe: Gibt Ergebnis an einen nächsten Befehl weiter
"Hallo".gettype().fullname        # Zeigt den Datentyp an.
#-----------------------------------------------------------------------------------------------------------------------
# Fallbeispiel → Hilfe für Get-Process erhalten:
get-help Get-Process              # NAME: Get-Process
                                  # ÜBERSICHT: Gets the processes that are running ...
                                  # SYNTAX: Get-Process [[-Name] ] [-ComputerName ] ...
                                  # BESCHREIBUNG: The Get-Process cmdlet gets the processes on a local or ...
                                  # VEWANDTE LINKS: Debug-Process...
                                  # HINWEISE: Zum Aufrufen der Beispiele...
								  # Beispiele:
# Die neuen Erkenntnisse anwenden:
get-process notepad               # Falls der Prozessname an erster Stelle in der Parameterliste erscheint
get-process -name notepad         # Sonst mit Parameterbezeichnung -name
get-process -id 3880              # Oder mit der Prozess-ID (falls bekannt bzw. in diesem Beispiel die 3880)

1.4 Absoluter und relativer Pfad

# Absoluter Pfad
C:\Benutzer\FelixMuster\Dokumente\MeinText.txt

# Relativer Pfad 
# (Abhängig vom CurrentWorkingDirectory CWD. Der Punkt . steht für das CWD)
.\MeinText.txt            # Falls CWD = C:\Benutzer\FelixMuster\Dokumente
.\Dokumente\MeinText.txt  # Falls CWD = C:\Benutzer\FelixMuster

# Relative Pfade verwenden
# (Um bei Funktionen, Dateien etc. mit relativen Pfaden zu arbeiten, empfiehlt es sich, 
# den aktuellen Pfad bewusst als Current Working Directory CWD z.B. auf den Skriptordner zu setzen)
[String] $scriptPath = Split-Path $MyInvocation.MyCommand.Path
Set-Location $scriptPath

1.5 Script-Programme starten

1.5.1 Script aus der Powershell-ISE oder aus einem anderen Skript heraus starten:

# Im aktuellen Variablen-Gültigkeitsbereich («.» beachten!)
. "C:\Benutzer\FelixMuster\Dokumente\myScript.ps1"
. .\myScript.ps1

# Im eigenen Variablen-Gültigkeitsbereich («&» beachten!)
& "~\Dokumente\myScript.ps1" # ~ bedeutet Home-Verzeichnis
& .\myScript.ps1

1.5.2 Powershellscripts und CmdLet's aus der Konsole CMD aufrufen:

Konsole «cmd.exe» aufrufen und eine der folgenden Befehlszeilen ausführen:

Powershell.exe -command Get-Process                        # WPS-CmdLet «Get-Process» ausführen
Powershell.exe -command Set-ExecutionPolicy unrestricted   # Policy ändern, wie in der WPS-ISA auch, um eigener Skript ausführen zu dürfen
Powershell.exe -command .\myScript.ps1                     # Eigener Skript über relativen Pfad aufrufen

1.6 Powershell-Script-Template

Nachdem die PowerShell-ISE als Administrator gestartet wurde, soll der folgende Befehl in der Powershell-Konsole ausgeführt werden:
(Siehe auch 1.2 Ausführungsrichtlinien und Codierungsregeln)

Set-ExecutionPolicy unrestricted
Danach ein neues Powershell-Dokument erstellen und mit folgenden Zeilen füllen bzw. nach Bedarf anpassen:

#******************* «PROGRAMM-TITEL eintragen» *******************************
# VERSION: «Aktuelle Versionsnummer»
# AUTOR:   «Ihr Name»
# DATUM:   «Aktuelles Datum»
# ZWECK:   «Zweckangabe bzw. kurze Programmbeschreibung»
#******************************************************************************

set-strictmode -version latest       # Codierungsregeln verschärfen

#******************* Variablen-Deklaration & Initialisierung ******************
[int]$myInt = 0                      # Für eine Ganzzahl ...-2,-1,0,1,2...
[double]$myDouble = 0.0              # Für einer Dezimalzahl z.B. 6.25
[string]$myString = "Hello World!"   # Für eine Textzeile
[bool]$myBool = $TRUE                # Logische Variable $True oder $False

#******************* HIER BEGINNT MEIN PROGRAMM *******************************
cls                                  # Bildschirm (Konsole) löschen
 
#********** EINGABE **************
$myString = Read-Host "Bitte ihren Namen eingeben"
$myDouble = Read-Host "Bitte ihre letzte Zeugnisnote eingeben"

#********** VERARBEITUNG *********

if($myBool -eq $True)                # Beispiel für eine Selektion
{
  # Tu etwas
}
else
{
  # Tu etwas anderes
} 

while($myInt -lt 10)                 # Beispiel für eine Iteration
{
  $myInt = $myInt + 1
}
 
#********** AUSGABE *************
write-Host "Lieber $myString, deine letzte Zeugnisnote war eine $myDouble. Gratuliere!"

#******************* HIER ENDET MEIN PROGRAMM **********************************

2. Teil

2.1 Datentypen

(Aufzählung nicht abschliessend)

[int] $intVar = 0 #32-Bit-Ganzzahl
[double] $dblVar = 0 #Fliesskommazahl
[char] $chrVar = ' ' #Ein Unicode-16-Bit-Character
[string] $strVar = "" #Textzeile
[bool] $boolVar = $true #Boolean oder logische Variable ($true oder $false)
[DateTime] $datVar = "01.31.1999"
[DateTime] $datVar = Get-Date #Variable mit aktuellem Datum füllen
[Object] $objVar = 0 #Der Stamm einer Objekthierarchie
[int[]] $intArr = 66,77,88,99 #Feldvariable / Array
$intArr[0] =22 #Überschreiben des "ersten" Elements 0
$intArr[1] = 33 #Überschreiben des "zweiten" Elements 1
Set-Variable constWert -Value 100 -option ReadOnly #Unveränderbare Konstante mit Inhalt 100

Ergänzung zum Datentyp Array/Feldvariable

Beispiel: Ziehung bzw. Speicherung der Lottozahlen: 6 Zahlen und Zusatzzahl

2.2 Input/Output

  • Wert mit Read-Host einlesen:
    $intVar = Read-Host "Bitte eine Zahl eingeben:"
  • Ausgabe mit Write-Host:
    Write-Host "Das Resultat lautet:" $intVar
  • Ausgabe mit Out-GridView:
    Get-Services | Out-GridView

2.3 Vergleichsoperatoren und Logische Operatoren

Vergleichsoperatoren

-eq       # Equal oder Gleich (==)
-ne       # Not-Equal oder Ungleich (!=)
-lt       # Lower-than oder Kleiner-als (<)
-le       # Lower-Equal oder Kleiner-Gleich (<=)
-gt       # Greater-than oder Grösser-als (>)
-ge       # Greater-Equal oder Grösser-Gleich (>=)

-like     # Zeichenkette mit Wildcards finden. Bsp.: "Arnold" -like "Arn*"
-match    #Substring finden. Bsp.: "Arnold" -match "nol"
-contains # Suche in Collections. Bsp.: $w = "Do","Re","Mi" → $w -contains "Mi"

Beispiel zu Vergleichsoperatoren

if ( $x -gt 10) {..} else {..}

Logische Operatoren

-not  # Invertierung / NOT / NICHT / Aussageumkehrung
-and  # AND oder UND Verknüpfung
-or   # OR oder ODER Verknüpfung
-xor  # Exklusiv-ODER / XOR Verknüpfung

Beispiel zu Logische Operatoren

while ( $ResultatNOK -or $Abbrechen) {..}

2.4 Die Selektion

Selektion → Verzweigung

  • Zweiseitige Selektion:
    if ( $userInput -ne $null )
    {
      echo "Input was [$userInput]"
    }
    else
    {
      echo "User cancelled the form!"
    }
    
  • Entscheidungen können auch mit Pipelines formuliert werden. Die Bedingung ist wahr ($true), wenn die Pipe mindestens ein Objekt zurückliefert:
    if (dir *.txt | Select-String "Steuererklaerung")
    {
      write-host "Es existiert mindestens eine Datei Steuererklaerung"
    }
  • Mehrfachselektion:
    switch($A)
    {
      1       { Write-Host "Eins" }
      2       { Write-Host "Zwei" }
      3       { Write-Host "Drei" }
      default { Write-Host "Leer" }
    }
    

2.5 Die Iteration

Iteration → Schleife

  • Kopfgesteuerte Iteration:
    [int] $myVar = 0
    while ($myVar -lt 10)
    {
      Write-Host Hello
      $myVar++
    }
  • Fussgesteuerte Iteration:
    [int] $myVar = 0
    do
    {
      Write-Host "Hello"
      $myVar++
    } while ($myVar -lt 10)
    
  • Kopfgesteuerte Iteration - Spezialfall for-Schleife:
    [string] $sterne = ""
    [int] $maxPosition=8
    for ( $currPosition = 1 ; $currPosition -le $maxPosition ; $currPosition++ )
    {
      $sterne = $sterne + "*"
    }
    write-host "Anzahl" $sterne
    
  • Diese Iteration arbeitet eine Sammlung von Objekten ab.
    Die sogenannte Element-Variable $Variable speichert bei jedem Durchgang jeweils ein Objekt der Objekt-Gruppe:
    Foreach ($Variable in get-childitem C:\windows)
    {
      ...
      $Variable.Name
      $Variable.CreationTime
      ...
    }
    
  • Vereinfachte Variante mit dem CmdLet «Foreach_Object»:
    Man kann mit der Standardvariable $_ auf die einzelnen Objekte in der Pipeline zugreifen:
    (Vorsicht: Das vorangegangenen Beispiel mit der foreach-Funktion unterscheidet sich zu diesem Beispiel darin, dass hier ein foreach-CmdLet verwendet wird. Im Gegensatz zur Funktion muss beim CmdLet die geschweifte Klammer «{» auf derselben Zeile folgen.)
    get-childitem C:\windows | Foreach-Object {
      ...
      $_.Name
      $_.CreationTime
      ...
    }
    

2.6 Try and catch

Da gewisse Anweisungen einen Laufzeitfehler auslösen können, bedient man sich um diese abzufangen einer Try-and-Catch-Struktur: Wird innerhalb des Try-Blocks ein Laufzeitfehler ausgelöst, wird der Catch-Block ausgeführt. Damit kann man eine robuste Dateneingabe erreichen, den Aufruf eines nicht vorhandenen Dienstes abblocken oder Zahlen überprüfen.

  • Try-Catch in einer Iteration:
    do
    {
      try
      {
        $numOk = $true
    	[int]$myNumber = Read-host "Bitte eine Zahl zwischen 0 und 100 eingeben"
      } # end try-Part
      catch
      {
        $numOK = $false
      } # end catch-Part
    } until (($myNumber -ge 1 -and $myNumber -lt 100) -and $numOK)
    
  • Dienst auf sein Vorhandensein überprüfen:
    $servicename = Read-host "Bitte geben Sie ein Servicename ein"
    try
    { # der Fehler beim ausgeführten Cmdlet wird abgefangen!
      Get-Service $servicename -ErrorAction Stop
    } # end try-Part
    catch
    {
      Write-Warning "Diesen Service ($servicename) gibt es nicht!"
    } # end catch-Part
    
  • Robuste Zahleneingabe (Funktion):
    function isNumeric ($x)
    {
      try
      {
        0 + $x | Out-Null # Versucht mit der Variable zu rechnen
        return $true # Wenn möglich $true zurückgeben
      }
      catch
      {
        return $false #Wenn Fehler auftritt, handelt es sich nicht um eine Zahl, somit $false
      }
    }
    

2.7 Abstraktion / Funktion / Methode / Filter

Wie die meisten Programmier­sprachen kann auch PowerShell mehrere Statements zu einer Funktion zusammenfassen. Sie vereinfacht die Wiederverwendung von Code und hilft bei der Strukturierung von Scripts. Auch wenn Funktionen in PowerShell für die Benutzer von VBScript oder PHP auf den ersten Blick vertraut aussehen, so gibt es doch einige gravierende Unterschiede.

Sowohl Cmdlets als auch in Bibliotheken bereitgestellte Funktionen (Methoden bei Objekten) stellen sogenannte Abstraktionen dar. Oft liefern diese einen Rückgabewert (ReturnValue) an das aufrufende Programm zurück. Es lassen sich aber auch eigene Funktionen erstellen. Im Quellcode müssen diese vor ihrem Aufruf definiert werden.

  • Variante 1: Die param-Angabe bestimmt dabei erforderliche Übergabeparameter und definiert deren Namen. Über diese Namen kann ein Parameter beim Aufruf auch explizit gesetzt werden.
    function myFunction
    {
      param([int]$parameter1, [string]$parameter2, ...) #Parameter definieren
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> $resultat = myFunction -parameter2 "Text" -parameter1 12 ...
    
  • Variante 2: Kurzschreibweise ohne "param" Bei der Übergabe muss die hier definierte Reihenfolge eingehalten werden.
    function myFunction ([int]$parameter1, [string]$parameter2, ...)
    {
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> write-host myFunction 12 "Text" ...
    
  • Variante 3: Übergabe von Werten via Argumentübergabe mittels der vordefinierten Variable $args (=Array) und $arg.count (=Anzahl übergebener Parameter).
    function myFunction
    {
      ... # Hier steht der Inhalt der Funktion
      $args[...] # Der erste Wert hat den Index 0
      ... # Hier steht der Inhalt der Funktion
      return ... # Damit kann ein allfälliger Rückgabewert an den Aufrufer zurückgegeben werden
    }
    
    Aufruf der Funktion:
    c:\user\...> myFunction 12 "c:\" … | out-file resultat.txt
    

2.7.1 Beispiele zu Funktionen

(Hinweis: Beim Aufruf mit mehreren Parametern müssen die Parameter mit Leerschlag und nicht mit Komma getrennt werden! Kommata definieren ein Array und werden als solche dann in einem Parameter abgelegt. Um zusammenhängenden Text in einen Parameter zu packen müssen Sie ihn mit " " einfassen.)

Beispiel-1:

function myFunction             # Funktion
{
  [int]$a    = 5
  [string]$b = "Hello World"
  $a
 #$b                            # Hier (ohne # Kommentarzeichen) $b oder return $b haben denselben Effekt. 
  return $b                     # Rückgabewert (siehe Kommentar eine Zeile höher)
}                               # Würde man sowohl $b und auch return $b schreiben, hätte man mit $a drei Werte 

$r = myFunction                 # Funktionsaufruf
write-host $r                   # Ist eigentlich ein Array und enthält Werte von $a und $b
write-host $r[0]                # Der Beweis: $r ist ein Array. In Element 0 steht Wert von $a
write-host $r[1]                # In Element 1 steht Wert von $b
#write-host $r[2]               # Würde man in der obigen Funktion sowohl $b (aktuell auskommentiert) und auch return $b schreiben,
                                # hätte man hier mit sogar drei Elemente in $r ($a, $b, $b). Dies macht allerdings keinen Sinn!

Beispiel-2:

function MyPing                 # Funktion
{
  $CompName = $args[0]          # Parameterübergabe Erstes Argument
  $IP = $args[1]                # Parameterübergabe Zweites Argument
  Test-Connection $CompName
  Test-Connection $IP
}

MyPing myhostname 192.168.1.10  # Funktionsaufruf myhostname = 1. Argument, 192.168.1.10 = 2. Argument

Beispiel-3:

function Start-App([String] $AppName)
{
  foreach($App in $input) {
    if($App.Name -like $AppName)
    {
      Start-Process "explorer.exe" -ArgumentList ("shell:AppsFolder\" + $App.AppID)
    }
  }
}

Get-StartApps | Start-App "Paint*"     # Daten über Pipe an Funktion übergeben

Beispiel-4:

function myMessage ($string, $title='PowerShell Message')
{
  [windows.forms.messagebox]::Show($string, $title)
}

myMessage "Diesen Text anzeigen!"; #Funktionsaufruf

Beispiel-5:

$nl = [System.Environment]::NewLine # oder `
function myHallo
{
  param $name
  $date = Get-Date
  If ($date.Hour -lt 12 -and $date.Hour -gt 6)
  {
    "Guten Morgen $name,$nl Es ist $($date.DateTime)"
  }
  elseif ($date.Hour -gt 12 -and $date.Hour -lt 19)
  {
    "Guten Tag $name,$nl Es ist $($date.DateTime)"
  }
  elseif ($date.Hour -gt 19 -and $date.Hour -lt 24)
  {
    "Guten Abend $name,$nlEs ist $($date.DateTime)"
  }
}  
  
myHallo -name Stefan Rehwald           # Funktionsaufruf
myHallo -name "Konrad Ernst Otto Zuse" # Funktionsaufruf
myHallo "Rudolf Diesel"                # Funktionsaufruf
$User = "Nikolaus Kopernikus"
myHallo $User                          #Funktionsaufruf

2.7.2 Definition eines eigenen Filters für die Pipe:

Filter werden identisch zu den Funktionen definiert. Eine Funktion wird einmal für alle Objekte in der Pipeline gemeinsam aufgerufen, ein Filter jeweils einmal für jedes Objekt. Um also beispielsweise einen Befehl zu entwickeln, der die Summe einer bestimmten Eigenschaft berechnet, wird eine Funktion eingesetzt, was jedoch auch den Nachteil hat, dass die Pipeline-Verarbeitung solange angehalten wird, bis sämtliche Objekte zur Verfügung stehen. Bei einem Filter kann die Pipeline weiterarbeiten, obwohl das vorhergehende Cmdlet noch weitere Objekte liefert.

  • Variante 1
    filter name { param($parameter1, $parameter2, ...) ... }
  • Variante 2
    filter name ($parameter1, $parameter2, ...) {...}
  • Variante 3
    filter name { ... $args[...] ... }

2.8 Piping

Beispiel: Get-Process explorer | Get-Member (Get-Process und Get-Member sind CmdLets. Den Pipe «|» erhält man mit ALTGR + 7)

2.9 Redirect

Redirect nennt man die Umleitung des Outputs vom Standard-Ausgabedevice (WPS-ISE-Konsole) in eine Textdatei:

# Piping/Pipeline
get-process | out-file "myProc.txt"   # Interprozesskommunikation per Pipe «|» (WIN: ALTGR + 7)

# Redirect
get-process > "myProc.txt"            # Wie bei UNIX/LINUX üblich: Es wird die Datei myProc.txt erstellt 
                                      # oder wenn bereits bestehend, ohne Rückfrage überschrieben.

# Append
get-process >> "myProc.txt"           # Append = Wenn die Datei bereits existiert, wird der Inhalt an das Dateiende angehängt,
                                      # sonst wird die Datei neu erstellt.

2.10 Alias

Das Wort Alias bedeutet «Sonst genannt / also known as» bzw. ein Alias ist ein Pseudonym für einen anderen Befehl.
Achtung: Eigene Aliase sind nur temporär vorhanden und verschwinden nach einem Neustart der WPS-ISE!

Set-Alias -Name list -Value get-childitem #get-childitem ist ein CmdLet
Set-Alias list get-childitem           # Kurzform
Set-Alias np c:\windows\notepad.exe    # Alias erzeugen
Set-Alias no1 .\myNumberOneScript.ps1  # Alias erzeugen
Get-Alias                              # Aliase anzeigen

3. Teil

3.1 Das objektorientierte Programmierparadigma

Die objektorientierte Programmierung (OOP) ist ein auf dem Konzept der Objektorientierung basierendes Programmierparadigma. Die Grundidee besteht darin, die Architektur einer Software an den Grundstrukturen desjenigen Bereichs der Wirklichkeit auszurichten, der die gegebene Anwendung betrifft.

  • Alles ist ein Objekt
  • Objekte kommunizieren durch das Senden und Empfangen von Nachrichten
  • Objekte haben ihren eigenen Speicher
  • Jedes Objekt ist die Instanz einer Klasse
  • Die Klasse beinhaltet das Verhalten aller ihrer Instanzen

Der hauptsächliche Unterschied zwischen prozeduraler und objektorientierter Programmierung ist die Beziehung zwischen Daten und Funktionen. Während bei der objektorientierten Programmierung Daten und Funktionen, die auf diese Daten angewandt werden können, in Objekten zusammengefasst werden, haben bei der prozeduralen Programmierung Daten und Funktionen keinen Zusammenhalt.

Beispiel:

Aufgrund eines Bauplans (Class) werden Exemplare (Objects) davon erzeugt (instanziert). Nämlich in diesem Fall ein Objekt mit dem Namen «Lightning», ein anderes mit dem Namen «Blizzard» und ein drittes mit dem Namen «Thunder». Diese Objekte können nun über ihre Objektnamen angesprochen werden.

Die Objekte besitzen:

  • Eigenschaften oder Attribute (Properties), wie z.B. Farbe, Grösse und weitere Merklmale
  • Methoden, Funktionen (Methods). Methoden können Parameter erhalten, die beim Aufruf übergeben werden müssen, und einen Rückgabewert besitzen, den sie am Ende dem Aufrufer zurückgeben. Beispielsweise hat die Methode «addiere» die Parameter Zahl 1 und Zahl 2 und gibt als Rückgabewert die Summe der Zahlen zurück.

Verändern von Eigenschaften (Zugriff auf Properties):

Ausführen von Funktionen (Methods):

Spezielle Methoden zur Erzeugung und Zerstörung von Objekten heissen Konstruktoren Create() beziehungsweise Destruktoren Kill().

3.2 Zugriff auf Eigenschaften und Methoden von Objekten

Erklärt an einem Beispiel das voraussetzt, dass der Prozess Notepad auf dem System gestartet ist:

# Alle Eigenschaften und Methoden des Prozess-Objekts «Notepad» anzeigen:
Get-Process Notepad | Get-Member   # Get-Process und Get-Member sind CmdLet's

# 1. Variante → Methode ausführen:
# (Mit dem CmdLet «Get-Process» das Prozess-Objekt «Notepad» ansprechen und die Methode «GetType()» aufrufen)
(get-process notepad).GetType()

# 2. Variante → Methode ausführen:
# (Mit dem CmdLet «Get-Process» das Prozess-Objekt «Notepad» in der Object-Variablen «myObj» speichern
# und anschliessend über die Object-Variable die Methode «GetType()» ausführen)
[Object] $myObj                    # Object-Variable deklarieren
$myObj = get-process Notepad       # Object-Variable zuweisen
$myObj.GetType()                   # Über Object-Variable eine seiner Methoden ausführen

# Eigenschaft Prozess-ID anzeigen:
(Get-Process Notepad).Id
$myObj.Id

Wenn man mit einer PowerShell-Pipeline arbeitet und sich auf das Objekt beziehen möchten, das sich gerade in der Pipeline befindet, kann man eine der beiden automatisch generierten und völlig gleichwertigen Variablen $PSItem oder $_ nutzen. Dazu ein Beispiel:

Get-Process | Foreach-Object { write-host $PSItem.ProcessName $PSItem.CPU}
Get-Process | Foreach-Object { write-host $_.ProcessName $_.CPU}
Get-Process | Foreach-Object { write-host $_.ProcessName }
Get-Process | Foreach-Object { $_.ProcessName }

3.3 Statische Eigenschaften und Methoden von .Net-Klassen verwenden

[<Klassenname>]::Eigenschaft
[<Klassenname>]::Methode(<Parameter>)

Beispiele mit der .Net-Klasse «Math»

[Math]::Pi #Konstatnte Pi
$x = 0.5; [Math]::Sin($x) #Sinuswert ermitteln
$y = 2; [Math]::Sqrt($y) #Quadratwurzel ermitteln

Beispiele mit der .Net-Klasse «Convert»

$Bin2Dez = [Convert]::ToInt32("101101010101",2)   #Resultat: 290110
$Oct2Dez = [Convert]::ToInt32("1234567",8)        #Resultat: 34239110
$Dez2Hex = [Convert]::ToString("65098",16)        #Resultat: "FE4A"
$Dez2Bin = [Convert]::ToString("12345",2)         #Resultat: "11000000111001"

3.4 Klassen instanzieren

Bei Powershell ist alles ein Objekt mit Eigenschaften und Methoden, sogar der Integer und der String.
Beispiel:

[string] $myStr = "Hello"
$myStr | Get-Member
$myStr.Length              # Ergibt den Wert 5
$myStr.ToUpper()           # Ergibt HELLO

Beim folgenden Beispiel wird nicht ein neues Objekt erzeugt, sondern eine Objektvariable erstellt und dieser die Referenz auf das Prozessobjekt "notepad" zugewiesen. Das Prozessobjekt "notepad" wurde von Betriebssystem zu dem Zeitpunkt erstellt, als das Programm notepad.exe gestartet wurde:

[object]$myObject
$myObject = Get-Process notepad

Es soll mit New-Object ein Objekt von einer .Net-Klasse erstellt werden. Dies soll anhand eines Beispiels mit der Klasse Net.WebClient gezeigt werden. Diese enthält die Methode DownloadFile, mit der eine Datei aus dem Internet geladen und lokal gespeichert werden kann:

$web = New-Object -typename Net.WebClient
$web.DownloadFile("http://edu.juergarnold.ch/fach_it/wpssummary/titel.jpg", "C:\Users\FelixMuster\Pictures\titel.jpg")

Falls an ihrem Standort ein Proxiserver mit z.B. IP=192.0.200.200 an Port=8080 existiert, muss folgendes ergänzt werden:

$web = New-Object -typename Net.WebClient
$proxy = New-Object System.Net.WebProxy("http://192.0.200.200:8080")
$web.proxy = $proxy #WebClient über diesen Proxy leiten
$proxy.UseDefaultCredentials = $true #Benutzt Benutzername & PW des aktuellen Standorts
$web.DownloadFile("http://edu.juergarnold.ch/fach_it/wpssummary/titel.jpg", "C:\Users\FelixMuster\Pictures\titel.jpg")

4. Teil

4.1 Standardobjekte: Zählfunktionen

Um Dinge zu zählen gibt es z.B. das Cmdlet Measure-Object.

Get-Process * | Measure-Object
Get-ChildItem -Filter *.txt | Measure-Object -Property length -Maximum -Minimum -Average –Sum
Get-Content MeinText.txt | Measure-Object -line -char –word -IgnoreWhiteSPace
(Get-ChildItem C:\Users\MeinNamen -Recurse –force).count

4.2 Standardobjekte: String-Methoden und Funktionen

Bei Zeichenketten (Strings) handelt es sich immer um Objekte.

"Hallo Welt" | Get-Member                  # Zeigt alle Methoden und Eigenschaften eines Stringobjektes an

$h = "Hallo"                               # Zeichenkette zusammenfügen
$w = "Welt"
$h + " " + $w                              # Eine Alternative wäre: "$h $w"

$st = @("Hallo", "Welt")                   # Elemente eines String-Arrays zusammenfügen
"$st"                                      # Dazwischen wird automatisch ein Leerzeichen eingefügt

[string]$s1 = "Hallo"
[string]$s2 = "Welt"
-join($s1,$s2)                             # Verknüpfung mit join-Operator. Kein Leerzeichen dazwischen
$s1,$s2 -join " "                          # Dazwischen wird nun ein Leerzeichen eingefügt

("Hallo Welt").split(" ")                  # Zeichenkette am Leerzeichen zerlegen. Das Leerzeichen entfällt
("Hallo Welt").Substring(2,5)              # Teil der Zeichenkette herauslösen
("Hallo Welt").Remove(2,3)                 # Teil der Zeichenkette löschen
("Hallo Welt").Replace("Hallo","Neue")     # Teil der Zeichenkette ersetzen
("Hallo Welt").Contains("ll")              # Teil der Zeichenkette suchen. Liefert $True oder $False zurück
("Hallo Welt").IndexOf("ll")               # Position eines Zeichens oder Substrings ermitteln

[string]$s1 = "Eisen"
[string]$s2 = "Bahn"
("EisenBahn").CompareTo($s1 + $s2)         # Liefert den Wert 0 → Reihenfolge identisch
("BahnEisen").CompareTo($s1 + $s2)         # Liefert den Wert -1 → Reihenfolge umgekehrt
("Eisen Bahn").CompareTo($s1 + " " + $s2)  # Liefert den Wert 0 → Reihenfolge identisch
("EisenBahn").CompareTo($s1 + " " + $s2)   # Liefert den Wert 1

("Eisen Bahn").Equals($s1 + " " + $s2)     # Liefert den Wert $True → Zeichenketten sind identisch

"eisenbahn".ToUpper()                      # Wandelt die komplette Textzeile in Grossbuchstaben um

$nl = [System.Environment]::NewLine        # Erzeugt eine Neue Zeile bei der Ausgabe
write-host "Eisen $nl Bahn"

4.3 Standardobjekte: DateTime-Methoden und Funktionen

In einer DateTime-Variable lassen sich Zeit- und/oder Datumswerte abspeichern und verarbeiten.
Da ein Datum immer ortsabhängig (Culture) angegeben werden muss, müssen wir bei der Eingabe immer über das ortssensitive Cmdlet Get-Date operieren.

Get-Date | get-member -MemberType Property                  # Eigenschaften anzeigen
Get-Date | get-member -MemberType Method                    # Methoden anzeigen

$info = new-object system.globalization.datetimeformatinfo
$info.DayNames
$info.MonthNames

[DateTime]$Zeitpunkt = Get-Date "12.3.2015 15:33"           # Konkretes Datum/Uhrzeit als String eingeben
Write-Host $Zeitpunkt

[DateTime]$Zeit = Get-Date "15:33"                          # Konkrete Zeit als String eingeben
Write-Host $Zeit

[DateTime]$Datum = Get-Date "12.3.2015"                     # Konkretes Datum eingeben (Uhrzeit ist dann 00:00:00)
Write-Host $Datum

"Day: " + $Datum.Day
"Month: " + $Datum.Month
"Year: " + $Datum.Year
"Hour: " + $Zeit.Hour
"Second: " + $Zeit.Second
$Zeitpunkt.DayOfYear                                        # Der wievielte Tag im Jahr?
$Zeitpunkt.TimeOfDay                                        # Tabelle mit allen Werten

$Zeitpunkt.AddDay(2)                                        # Anzahl Tage dazuzählen
$Zeitpunkt.AddDay(-2)                                       # Anzahl Tage abzählen
([datetime] "1.2.2006" - [datetime]"1.1.2006").TotalDays    # Datumsdifferenz

function tillXmas ()                                        # Eigene Funktion erstellen
{
  $now = [DateTime]::Now
  [Datetime]("25.12." + [string]$now.Year) - $Now
}

5. Teil

5.1 Ausgabe in eine HTML-Datei

# Hinweis Zeilenumbruch: Für eine bessere Lesbarkeit, wurde die lange Code-Zeilen "ConvertTo-Html" auf mehrere Zeilen umgebrochen.
#                        Damit dies zu keinem Syntaxfehler führt, muss jede Zeile mit "`" ergänzt werden.
# Hinweis foreach :      foreach verlangt das Begin "{" auf der selben Zeile.

get-service | ConvertTo-Html `
              -title "MELDUNG SYSADMIN" `
              -body (get-date) `
              -pre "<p>Erstellt von Felix Muster</p>" `
              -post "<p>Weitere Infos siehe <a href=http://felixmuster> http://felixmuster</a></p>" `
              -Property Name, Status | foreach {

  if($_ -like "*<td>Running</td>*")
  {
    $_ -replace "<tr>", "<tr bgcolor=green>"
  }
  elseif($_ -like "*<td>Stopped</td>*")
  {
    $_ -replace "<tr>", "<tr bgcolor=red>"
  }
  else
  {
    $_
  }
} > .\ServiceStatus.html

5.2 Mit Dateien arbeiten

Set-Location Pfadname                             # Pfad setzen
Get-Location                                      # Pfad auslesen
Test-Path -path Pfadname                          # Existenz eines Pfades überprüfen ($True oder $False)
Get-Item Verzeichnisname                          # Verzeichnis lesen
Get-Item Dateiname                                # Datei lesen
Get-ChildItem Verzeichnisname -include Pattern    # Filterfunktion-Einschliesslich; Pattern → *.jpg 
Get-ChildItem Verzeichnisname -exclude Pattern    # Filterfunktion-Ausschliesslich; Pattern → *.gif
Get-ChildItem Verzeichnisname -recurse            # Rekursion → Zeigt auch alle Unterverzeichnisse an
(Get-Item .\meinUnterverzeichnis).delete()        # Leeres Unterverzeichnis löschen
#-------------------------------------------------------------------------------------------------------
$myFile = Get-Item *.txt                          # Ergibt Array von gefundenen Dateiobjekten
$myFile | Get-Member                              # Dateiobjekt-Array wird an Get-Member weitergereicht
#-------------------------------------------------------------------------------------------------------
$myFile = Get-Item .\meinText.txt                 # Auf Dateieigenschaften von "meinText.txt" zugreifen
Write-Host $myFile.Name                           # Dateiname anzeigen
Write-Host $myFile.Length                         # Dateigrösse in Byte anzeigen
#-------------------------------------------------------------------------------------------------------
$myFileList = get-ChildItem Pfadname -recurse     # Zeigt alle Verzeichnisse ab Pfadname an
foreach($myFile in $myFileList) {
  if($myFile.PSISContainer -eq $True)
  {
    Write-Host $myFile.Name
  }
}
#-------------------------------------------------------------------------------------------------------
dir Pfadname -r | foreach {                       # Zeigt ebenfalls alle Verzeichnisse ab Pfadname an
  if ($_.mode –match "d")
  {
    Write $_.Name
  }
}
#-------------------------------------------------------------------------------------------------------
if ((get-ChildItem Pfadname).exists -eq $True)    # Existenzprüfung:
{                                                 # Falls WPS die Datei nicht findet,
  write-host OK                                   # auf die eine Operation ausgeführt werden soll,
}                                                 # kommt es zu einer Fehlermeldung.
else                                              # Darum muss zuerst deren Existenz überprüft werden.
{
  write-host NOK
}

Dateien kopieren, verschieben, löschen oder umbenennen

# Wichtig für alle folgenden Beispiele:
# Falls der Pfadname relativ angegeben wird, empfiehlt es sich, an erster Stelle den aktuellen Pfad wie folgt zu setzen:
[environment]::CurrentDirectory = Get-Location    # Aktueller Pfad setzen, falls relative Pfadangabe folgt

# Eine Datei kopieren
$myFile = Get-Item Dateiname                      # Z.B. Get-Item .\meinText.txt
$myFile.CopyTo("Pfadname\Dateiname", -1)          # -1 → Überschreiben

# Mehrere Dateien kopieren - 1. Variante
$myFileList = get-ChildItem Pfadname\Dateiname
$destDir = "MeinZielPfadname"
foreach($myFile in $myFileList) {                 # Out-Null soll die Ausgabe unterdrücken
  $myFile.CopyTo($destDir +($myFile.Name), -1) | Out-Null
}

# Mehrere Dateien kopieren - 2. Variante
$myFileList = get-ChildItem Pfadname\Dateiname
$destDir = "MeinZielPfadname"
Copy-Item $myFileList $destinationDir -force      #force erzwingt Überschreiben

# Eine Datei verschieben
$myFile = Get-Item Dateiname
$myFile.MoveTo("Pfadname\Dateiname")

# Mehrere Dateien verschieben
$myFileList = get-ChildItem Pfadname\*.*         # z.B.: $myFileList = get-ChildItem C:\Dokumente\*.txt
$destDir = "MeinZielPfadname"
Move-Item $myFileList $destDir

# Eine Datei löschen
$myFile = Get-Item Dateiname
$myFile.Delete()

# Mehrere Dateien löschen
$myFileList = get-ChildItem Pfadname\*.*         # z.B.: $myFileList = get-ChildItem C:\Dokumente\*.txt
Remove-Item $myFileList

# Dateien umbenennen (Eine eigentliche Methode gibt es nicht. Allenfalls kann mit MoveTo() 
                      eine Umbenennung konstruiert werden.
                      Ein CmdLet existiert allerdings und nennt sich Rename-Item)
$myNewName = Read-Host "Neuer Dateinamen"
$myZaehler = 0
$myFiles = Get-ChildItem *.txt
if($myFiles -ne $NULL)
{
   foreach($File in $myFiles) {
     $myFullNewName = $myNewName + $myZaehler + ($File.extension)
     Rename-Item $File $myFullNewName
     $myZaehler++
   }
   write-host ($myZaehler)" Dateien umbenannt"
}

Mit Dateiattributen arbeiten:

# Es existieren unter MS-Windows folgende Dateiattribute:
# +R bzw -R → Schreibgeschützt setzen (+) bzw. löschen (-)
# +A bzw -A → Archiv setzen (+) bzw. löschen (-)
# +S bzw -S → Systemdatei setzen (+) bzw. löschen (-)
# +H bzw -H → Versteckte Datei setzen (+) bzw. löschen (-)
# (Im folgenden beschäftigen wir uns nur noch mit dem Archive-Attribut)
# In den Dateieigenschaften findet man unter Allgemein/Erweitert das Archive-Attribut:
# Erweiterte Attribute: ⊗ Datei kann archiviert werden
# Das Archiv-Bit kann z.B. von einer Backup-SW ausgewertet werden.
# Archiv-Bit = "1": Die Datei wurde verändert und soll bei einem Backup gespeichert werden.
# Archiv-Bit = "0": Die Datei wurde (z.B. seit dem letzten Backup) nicht verändert und muss nicht neu archiviert werden.
# Ist das Archiv-Bit auf 0, wird es automatisch vom Betriebssystem auf 1 gesetzt, wenn auf das File schreibend zugegriffen wird.
# Dieses Attribut lässt sich mit dem folgenden Win-Programm ändern:
# attrib.exe meinFile -A (Löscht das Archiv-Bit =0)
# attrib.exe meinFile +A (Setzt das Archiv-Bit =1)
# In Powershell:
set-itemproperty meinFile -Name Attributes -Value "Normal"  # → Löscht alle Attribute, auch das Archive-Attribut
set-itemproperty meinFile -Name Attributes -Value "Archive" # → Setzt das Archive-Attribut

[Object]$meineDatei = get-childitem meinText.txt            # Objektvariable initialisieren und deklarieren
if($meineDatei.mode -eq "-a----")                           # → Das Datei-Attribut in der Objekt-Eigenschaft "Mode" ausgewertet
{                                                           #   (Mode ist übrigens schreibgeschützt)
  # Tu was Gutes
}

Webseite in eine Datei kopieren:

$wc = new-object System.Net.WebClient
$wc.DownloadString('http://edu.juergarnold.ch/index.html') | out-file edu.txt
get-content edu.txt

Eingabe der Pfadnamen für Quellverzeichnis und Zielverzeichnis inklusive Überprüfung.

function validatePath                             # Pfadsyntax Ueberpruefen
{
  $trim = $args[0] -replace " ", ""               # Alle Leerschlaege loeschen
  if ($trim.Length -eq 0 -or (Test-Path -isValid $args[0]) -eq $False)
  {
    return $False;                                # Rueckgabewert ins initDirectory()
  }
  return $True;                                   # Rueckgabewert ins initDirectory()
}

function initDirectory                            # Syntax-Check
{
  if ((validatePath($args[0])) -eq $False)        # Funktionsaufruf validatePath()
  {
    return $False                                 # Rueckgabewert ins Hauptprogramm
  } 
  if ((Test-Path -path $args[0]) -eq $False)      # Existiert Verzeichnis bereits?
  { 
    new-item -path $args[0] -type directory       # Verzeichnis erstellen, falls noch nicht vorhanden
    write-host " Verzeichnis $args neu erstellt!"
  }
  else
  {
    write-host " Verzeichnis $args existiert bereits!"
  }
  return $args[0]                                 # Rueckgabewert ins Hauptprogramm
}

# ------------------ Hauptprogramm --------------------------------------------------
do                                                # Abfrage nach dem Quellverzeichnis
{
  $mySRC = Read-Host "Quellverzeichnis"
  $src = $mySRC
  $src = initDirectory($src)
} While($src -eq $False)

do                                                # Abfrage nach dem Zielverzeichnis
{
  $myDST = Read-Host "Zielverzeichnis"
  $dst = $myDST
  $dst = initDirectory($dst)
} While($dst -eq $False)

# Kontrollausgabe
write-host "Quellverzeichnis:" $mySRC " Zielverzeichnis:" $myDST

5.3 Lokale Benutzer und Gruppen verwalten

Die folgenden CmdLets helfen bei der Automatisierung des lokalen User-Managements. Dies ist insbesondere dann interessant, wenn man die Vorgaben wie Benutzername, Passworte usw. aus einer EXCEL-Datei bezieht. Wie man mit EXCEL-Dateien arbeitet, ist im nachfolgenden Kapitel "Arbeiten mit COM-Objekten" beschrieben.
(Hinweis: Für das Anlegen eines Active-Directory-Users siehe New-ADUser und Set-ADUser. Wird hier aber nicht weiter behandelt!)

Get-Command -Module Microsoft.PowerShell.LocalAccounts   # Sämtliche CmdLets für LocalAccounts
Get-LocalUser A* | select *                              # Zeigt alle User mit dem Startbuchstaben "A" an
Get-LocalGroup                                           # Zeigt alle Gruppen an

Neues Benutzerkonto erstellen, ändern und löschen
Angaben, mit denen sich die Eigenschaften eines neu zu erstellenden Kontos festlegen lassen sind:

  • AccountExpires → DateTime
  • AccountNeverExpires
  • Description → String
  • Disabled
  • FullName → String
  • Name → String (Pflichtangabe)
  • Password → SecureString (Pflichtangabe)
  • NoPassword (Alternative Pflichtangabe zum obigen Password, wenn auf ein Passwort verzichtet wird)
  • PasswordNeverExpires
  • UserMayNotChangePassword
# Interaktive-Variante
[System.Security.SecureString]$meinPasswort = Read-Host -AsSecureString
New-LocalUser -Name "FelixMuster" -Password $meinPasswort -FullName "Felix Muster" -Description "Musterknabe"

# Batchverarbeitungs-Variante
[System.Security.SecureString]$meinPasswort = ConvertTo-SecureString -String "Pa$$w0rd" -AsPlainText -Force
New-LocalUser -Name FelixMuster  -Password $meinPasswort -FullName "Felix Muster" -Description "Musterknabe"

Set-LocalUser -Name FelixMuster -Description "Musterknabe" -AccountExpires "31.12.2030" # Nachträgliche Angaben
Rename-LocalUser -Name FelixMuster -NewName FelixKuster                                 # Benutzername umbenennen
Remove-LocalUser -Name FelixMuster                                                      # Eintrag wieder löschen

Neue Gruppe erstellen, ergänzen und löschen

Get-LocalGroup                                                 # Zeigt alle Gruppen an
Get-LocalGroupMember -Name Administratoren                     # Alle in Administratoren enthaltene Benutzer/Gruppen
New-LocalGroup -Name Verkauf -Description "Abteilung Verkauf"  # Erstellt neue Gruppe für die Abteilung Verkauf
Add-LocalGroupMember -Name Verkauf -Member FelixMuster         # Fügt Felix Muster der Gruppe Verkauf hinzu
Remove-LocalGroupMember -Name Verkauf -Member FelixMuster      # Entfernt Felix Muster aus der Gruppe Verkauf
Remove-LocalGroup -Name Verkauf                                # Löscht die Gruppe Verkauf

# Anstelle von Benutzernamen kann man bei Add-LocalGroupMember und Remove-LocalGroupMember
# für Member auch eine Gruppe angeben.

5.4 Arbeiten in der Registry

Die Windows-Registrierungsdatenbank (Registry) ist die zentrale hierarchische Konfigurationsdatenbank des Betriebssystems Windows. Hier werden sowohl Informationen zu Windows selbst als auch Informationen zu Programmen gespeichert. Die Registry steht Powershell wie ein normales Laufwerk zur Verfügung. Darum kommen ähnliche CmdLets und Funktionen zur Anwedung wie beim Arbeiten mit Dateien. Registry-Schlüssel (HKEY_ = Handle to a Key_) lassen sich mit Get-ChildItem anzeigen, deren Inhalte mit Get-ChildItemProperty.

# Registry-Schlüssel und Properties in "HKEY_LOCAL_MACHINE\Software" anzeigen:
Get-ChildItem HKLM:\Software

# Properties anzeigen: (Hier der Autostart-Schlüssel)
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Run

# Neuer Registry-Schlüssel in "HKEY_LOCAL_MACHINE\Software" anlegen:
New-Item -path "HKLM:\Software" -Name "Testkey"

# Neuer Eintrag erstellen:
New-ItemProperty -literalpath "HKLM:\Software\Testkey" -Name "Testkey" -Value "Test CRM" -type String

# Eintrag entfernen:
Remove-ItemProperty -literalpath "HKLM:\Software\Testkey" -Name "Testkey"

# Registry-Schlüssel entfernen:
Remove-Item -path "HKLM:\Software\Testkey"

5.5 Arbeiten mit Eventlogs

# Die drei letzten Einträge im System-Eventlog formatiert auslesen:
get-eventlog system -newest 3 | format-list
get-winevent -logname system -computername 127.0.0.1 -maxevents 3 | format-list

# Ein Event in den Application-Eventlog eintragen: 
# (Falls noch nicht geschehen, die neue Event-Quelle einmalig registrieren)
New-EventLog -LogName Application -Source myApp            # Neue Event-Quelle registrieren
Write-EventLog -logname "Application" `
               -computername $env:COMPUTERNAME `
               -source "myApp" `
               -eventID 9999 `
               -entrytype Information `
               -message "Hello World"

# Hinweis:
# -computername [computername] ermöglicht auch die Abfrage über das Netzwerk
# $env:COMPUTERNAME ist der eigene Computername 127.0.0.1.
# In diesem Fall könnte -computername auch ganz weggelassen werden

5.6 Arbeiten mit COM-Objekten

Das Component Object Model (COM) ist eine von Microsoft entwickelte Technik zur Interprozesskommunikation unter Windows. COM-Komponenten können sowohl in Form von Laufzeitmodulen (DLLs) als auch als ausführbare Programme umgesetzt sein. COM soll eine leichte Wiederverwendung von bereits geschriebenem Programmcode ermöglichen, zum Teil auch über Betriebssystemgrenzen hinweg.  Als Beispiel sollen Automatisierungsmöglichkeiten von Office, speziell Excel und Webseitenabfragen mit Internetexplorer aufgezeigt werden.

Excel:

# Bestehendes Workbook öffnen und lesen(Schritt für Schritt):
$myExcel = New-Object -comobject Excel.Application                 # Excel-Objekt erzeugen
$myWorkBook = $myExcel.Workbooks.Open("C:\...\meineDatei.xlsx")    # Workbook öffnen
$myWorkSheet = $myWorkBook.sheets.item("Tabelle1")                 # Worksheet/Tabelle öffnen
$myExcel.Visible = $True                                           # EXCEL-Tabellen anzeigen, falls gewünscht
[string]$meinText1 = $myWorkSheet.Cells.Item(1,1).Text             # Text in Zelle A1 lesen
[string]$meinText2 = $myWorkSheet.Cells.Item(2,1).Text             # Text in Zelle A2 lesen
$myExcel.Quit()                                                    # Excel schliessen
Remove-Variable myWorkSheet, myWorkBook, myExcel                   # Variablen löschen

# Neues Workbook erstellen und befüllen (Schritt für Schritt):
$myExcel = New-Object -comobject Excel.Application                 # Excel-Objekt erzeugen
$myWorkBook = $myExcel.Workbooks.Add()                             # Neues Workbook erzeugen
$myWorkSheet = $myWorkBook.Worksheets.Item(1)                      # Neues Worksheet/Tabelle erzeugen
$myWorkSheet.Cells.Item(1,1) = "Hello World"                       # Zellen füllen
$myWorkSheet.Cells.Item(2,1) = "2+4"                               # Bsp.: Textzeile
$myWorkSheet.Cells.Item(3,1) = 2+4                                 # Bsp.: Integer
$myExcel.Visible = $True                                           # EXCEL-Tabellen anzeigen, falls gewünscht
$myWorkBook.SaveAs(".\test.xlsx")                                  # Excelsheet abspeichern... 
                                                                    (...bei relativem Pfad in Dokumentenordner)
$myExcel.Quit()                                                    # Excel schliessen
Remove-Variable myWorkSheet, myWorkbook, myExcel                   # Variablen löschen

# Hilfe erhalten
$myExcel | Get-member                                              # Eigenschaften / Methoden des Excel-Objekts
$myExcel.Workbooks | Get-Member                                    # Eigenschaften / Methoden von Workbooks
$myExcel.Workbooks | Select-Object -Property name, path, author    # Überprüfen, ob Workbooks geöffnet ist

Internet-Explorer:

# InternetExplorer-Objekt erzeugen:
$myIE = New-Object -comobject InternetExplorer.Application

# Eigenschaften und Methoden des InternetExplorer-Objekts anzeigen:
$myIE | Get-member

# InternetExplorer positionieren:
$myIE.top = 20
$myIE.width = 1800
$myIE.height = 1600
$myIE.Left = 20;

# InternetExplorer anzeigen:
$myIE.Visible = $True

# Webseite aufrufen:
$myIE.Navigate("edu.juergarnold.ch")

5.7 Arbeiten mit Webformularen

# Mit Invoke-WebRequest ein Formular aufrufen und Formular mit SessionKey merken:
$myWebForm=Invoke-WebRequest http://edu.juergarnold.ch/fach_it/wpssummary/testForm.html -SessionVariable mySession

# Im Formular "berechnung" das Feld "resultat" beschreiben:
$myWebForm.Forms["berechnung"].Fields["resultat"] = "2"

# Formularinhalt übermitteln und Formularaktion auslösen:
Invoke-WebRequest -Method POST  `
                  -URI ("http://edu.juergarnold.ch/fach_it/wpssummary/" + $myWebForm.Forms["berechnung"].action)  `
                  -Body $myWebForm.Forms["berechnung"].Fields -WebSession $mySession

5.8 Arbeiten mit .net-Objekten - Erstellen einer grafischen Benutzereingabe

# Benötigte .NET-Frameworkklassen laden:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Instanz einer Form-Klasse erstellen:
$form = New-Object System.Windows.Forms.Form

# Form-Objekt anpassen:
$form.Text = 'FORMULAR'
$form.Size = New-Object System.Drawing.Size(400,300)
$form.StartPosition = 'CenterScreen'

# OK-Button kreiern, anpassen und in das Formular einfügen:
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = 'OK'
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK $form.AcceptButton = $OKButton
$form.Controls.Add($OKButton) # Cancel-Button kreiern, anpassen und in das Formular einfügen: $CancelButton = New-Object System.Windows.Forms.Button $CancelButton.Location = New-Object System.Drawing.Point(150,120) $CancelButton.Size = New-Object System.Drawing.Size(75,23) $CancelButton.Text = 'Cancel' $CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $form.CancelButton = $CancelButton $form.Controls.Add($CancelButton) # Beschriftung kreiern, anpassen und in das Formular einfügen: $label = New-Object System.Windows.Forms.Label $label.Location = New-Object System.Drawing.Point(10,20) $label.Size = New-Object System.Drawing.Size(280,20) $label.Text = 'Bitte Text eingeben:'
$form.Controls.Add($label) # Texteingabezeile kreiern, anpassen und in das Formular einfügen: $textBox = New-Object System.Windows.Forms.TextBox $textBox.Location = New-Object System.Drawing.Point(10,40) $textBox.Size = New-Object System.Drawing.Size(260,20) $form.Controls.Add($textBox) # Topmost-Eigenschaft auf $True setzen, um das Öffnen des Fensters über anderen geöffneten Fenstern und Dialogfeldern zu erzwingen: $form.Topmost = $true # Formular aktivieren und Fokus auf das Textfeld setzen: $form.Add_Shown({$textBox.Select()}) # Formular anzeigen lassen: $result = $form.ShowDialog() # Der Code im If-Block weist Windows an, wie mit dem Formular verfahren werden soll, # nachdem der Benutzer einen Text eingegeben und die Schaltfläche OK oder Eingabetaste gedrückt hat: if ($result -eq [System.Windows.Forms.DialogResult]::OK) { $x = $textBox.Text }

6. Teil

6.1 Einführung Testen von Software

Das Wort Qualität steht für viele verschiedene Aspekte eines Produktes oder einer Dienstleistung und bedeutet umgangssprachlich:

  • Etwas das gut bis sehr gut ist und/oder lange hält
  • Gute Preis/Leistung
  • Funktionsvielfalt, Verarbeitung
  • Markenname der für gute Qualität bürgt
  • Garantiedauer
  • Verständliche und vollständige Bedienungsanleitung
  • Nachhaltigkeit, Ökologische Herstellung und Entsorgung
  • Haltbarkeit
  • Sicherheit
  • Design
  • weiteres...

In verschiedenen Normen wird die Qualität beschrieben oder definiert. Als Beispiel die Begriffsdefinition aus DIN 55 350:
Qualität = Die Gesamtheit von Eigenschaften eines Produktes oder einer Tätigkeit, die sich auf deren Eignung zur Erfüllung gegebener Erfordernisse beziehen.

Qualität von Software

Die wichtigsten Qualitätsmerkmale sind in Normen wie der DIN/ISO 9126 geregelt. Gelten bei einem Softwareauftrag/Produkt für ein Grossprojekt andere Qualitätsmassstäbe? Dazu ein Fallbeispiel:

Was kann der Kunde erwarten, wenn er ein Softwareprodukt wie z.B. eine Textverarbeitung kauft?

  • Zuverlässigkeit (Reliability): Fähigkeit der Software, ihr Leistungsniveau unter festgelegten Bedingungen über einen festgelegten Zeitraum zu bewahren.
    Office bearbeitet beliebig komplexe Dokumente gleich.
    Ohne Absturz. (mit/ohne Fehlmanipulation)
  • Funktionalität (Functionality): Vorhandensein von Funktionen mit festgelegten Eigenschaften. Diese Funktionen erfüllen die definierten Anforderungen.
    Office besitzt alle in der Dokumentation angegebenen (nützlichen) Funktionen.
  • Benutzbarkeit (Usability): Aufwand, der zur Benutzung erforderlich ist, und individuelle Beurteilung der Benutzung durch eine festgelegte oder vorausgesetzte Benutzergruppe.
    Office lässt sich intuitiv erlernen und benutzen. (Benutzerführung, klare Fehlermeldung)
  • Effizienz (Efficiency): Verhältnis zwischen dem Leistungsniveau der Software und dem Umfang der eingesetzten Betriebsmittel unter festgelegten Bedingungen.
    Office erledigt alle Funktionen in angemessener Zeitspanne. (Reaktionszeit)
    Flüssiges Arbeiten möglich, Applikation ist möglichst resourcenschonend.
  • Änderbarkeit (Maintainability): Aufwand, der zur Durchführung vorgegebener Änderungen notwendig ist. Änderungen können Korrekturen, Verbesserungen oder Anpassungen an Änderungen der Umgebung, der Anforderungen und der funktionalen Spezifikationen einschliessen.
    Office lässt sich einfach konfigurieren oder erweitern. (Plug ins) 
    Für die Programmierer gilt: Klare Strukturierung, bessere Wartbarkeit.
  • Übertragbarkeit (Portability): Eignung der Software, von einer Umgebung in eine andere übertragen zu werden. Umgebung kann organisatorische Umgebung, Hardware- oder Software-Umgebung einschliessen.
    Office lässt sich auf Windows, Mac, Smartdevices etc. installieren oder aber auch cloud-basiert ausführen.

Auswirkungen von Fehlern

In der Praxis können sehr kleine Fehler sehr grosse Konsequenzen haben:

  • Systemabstürze. Kann auch andere Programme betreffen und kommt für den SW-Lieferanten dem GAU sehr nahe. Bei Powershell: Rote Fehlermeldungen
  • Programmabbrüche
  • Datenverfälschung - Anomalien (siehe auch M104)
  • Falsches Systemverhalten
  • Prestigeverlust des SW-Firma
  • Frustrierte Benutzer/Kunden
  • usw.

Ob ein vorhandener Fehler die Verbreitung einer neuen Version, die Verteilung eines Patches oder eventuell auch keine Massnahme erfordert, ist von einer Vielzahl von Kriterien anhängig. Die Folgen und Kosten von Fehlern hängen stark von der Art des Einsatzes eines Produktes ab:

  • Massenprodukt (grosse Anzahl Anwender, die dem Hersteller nicht direkt bekannt sind)
  • Individuelle Software (kleiner Anwenderkreis mit Integration in den Entwicklungsprozess)
  • Einsatz in einem sicherheitsrelevanten Bereich mit potentiellem Personenschaden, Materialschaden oder finanziellem Verlust. (Spital, technische Anlagen, Finanzsysteme usw.)

Fehlerarten

Es ist also wichtig zu ermitteln, wo welche Fehler entstehen können. Abgesehen von Syntaxfehlern, die bei der Übersetzung eines Programms automatisch erkannt werden und von Laufzeitfehlern, die unmittelbar zum Absturz führen, gibt es weitere Fehlerarten, die meist schwieriger aufzufinden sind:

  • Codierfehler (z.B. bei Zeichencodierung)
  • Logikfehler (z.B. bei Entscheidungen if-else, switch etc.)
  • Strukturfehler (z.B. bei Iterationen, while, do-while, for)
  • Entwurfsfehler (z.B. Realität in SW falsch abgebildet)
  • Datenfehler (z. B. char/integer; Array falsch definiert; int32 anstatt int64; Jahr 2000 Problematik)
  • Umsetzungsfehler (von der Spezifikation/Pflichtenheft ins Programm)
  • Fehler in der Benutzeroberfläche (bei GUI oder sonstiger Benutzerinteraktion)

6.2 Analytische Qualitätssicherung zur Vermeidung von Fehlern

Testgrundlagen

Unter Qualitätssicherung versteht man die Summe aller Massnahmen, die die Qualität des entstehenden Produktes gewährleisten sollen. Im Softwarebereich wird unter der analytischen Qualitätssicherung der Test von Software und Dokumentation verstanden. Durch den Test wird das Ziel verfolgt, Fehler aufzudecken. Jeder gefundene und beseitigte Fehler führt zu einer Produktverbesserung.

Testaktivitäten und Testplanung

Die Testaktivitäten können nicht erst kurz vor Schluss in Angriff genommen werden. Das Testen von Software muss geplant sein, nimmt es doch bei grossen Projekten ca. 10% bis 15% der gesamten Projektzeit in Anspruch.

Es gibt für das Durchführen von Testaktivitäten einige Prinzipien, die sich in der Praxis bewährt haben:

  • Fehler möglichst frühzeitig aufdecken: Das heisst, ein Test sollte frühzeitig und entwicklungsbegleitend erfolgen, um Fehlerauswirkungen und Folgefehler zu minimieren.
  • Aus Fehlern lernen und sie zukünftig vermeiden: Das heisst, Fehlerursachen sind zu analysieren, um Hinweise für eine zukünftige Fehlervermeidung zu erhalten.
  • Bei komplexen Testobjekten unabhängige Tests durchführen: Das heisst, um der Zielsetzung des Testens (Fehleraufdeckung) gerecht zu werden, sollen vor allem bei komplexen und wichtigen Produkten unabhängige Tester eingesetzt werden.
  • Testziele erreichbar und messbar formulieren: Das heisst, dass überprüfbare Ziele (z.B. Forderungen über auszuführende Testfälle) für den Test formuliert werden sollten. Das Testende ist erreicht, wenn die zuvor aufgestellten Testziele erreicht werden.
  • Testfälle professionell handhaben: Das heisst, die Testfälle sind zu speichern und für spätere Testwiederholungen (Regressionstests) verfügbar zu machen.
  • Testaktivitäten planen: Das heisst, ohne Planung werden Testaktivitäten unsystematisch und im Wortsinne "ungeplant" durchgeführt. Die Testplanung ist in die Projektplanung zu integrieren.
  • Testaktivitäten dokumentieren: Das heisst, nur durch Dokumentation ist Transparenz und Revisionssicherheit der Tests gewährleistet. Zur Testdokumentation gehören Testpläne, Testspezifikationen, Testfallbeschreibungen, sowie Test- und Fehlerprotokolle.

Testarten und Zeitpunkte

In der SW-Entwicklung werden während den verschiedenen Entwurfsphasen Vorgaben für die Tests der entsprechenden Ebenen spezifiziert. Bei der Umsetzung werden die Tests dann durchgeführt und entscheiden über den Fortgang der weiteren Entwicklung:

Systematische Tests

Zur Durchführung systematischer Tests wird in der Regel ein Testplan (Protokoll) erstellt. Dies kann bereits in der Design-Phase (Planung) geschehen. Dabei erfolgt die Aufteilung nach Fehlerkategorien. Die Durchführung dieser Tests kann je nach Entwicklungsumgebung von Debuggingtools oder speziellen Testsuiten unterstützt werden.

Datenfehler erkennen:

  • Eingabenprüfung (Richtige und falsche Werte und Typen)
  • Extremwerte prüfen (z.B. negative Zahlen, sehr grosse und sehr kleine Zahlen)
  • Ausgabenprüfung (Verwendung von Testdaten)
  • Erzeugt durch Testdaten-Generator (speziell entwickeltes Programm)
  • Daten aus der Vergangenheit/aus dem existierenden Umfeld (evtl. mit bekannten Ergebnissen)

Strukturfehler erkennen:

  • Pfadanalyse allgemein (Traces)
  • Definieren kritischer Pfade

Statische Methoden: (Bei den statischen Methoden wird der Programmcode nicht ausgeführt. Das heisst, die Programmzeilen werden visuell nach bestimmten Kriterien untersucht.)

  • Audit
  • Inspektion
  • Code-Review
  • Walkthrough

Dynamische Methoden: (Bei den dynamischen Testmethoden wird die Software gestartet und nach bestimmten Testkriterien durchlaufen.)
Es gibt einige Grundsätze für die Testfallermittlung:

  • Die Testfälle sollen minimalistisch sein. D.h dass zur Aufdeckung eines bestimmten Fehlers möglichst nur ein Testfall gebraucht wird.
  • Die Testfälle sollen so zusammengestellt werden, dass das gesamte Testobjekt abgedeckt wird.
  • Die Testfälle sollen nicht nur den Normalfall abdecken, sondern insbesondere auch Grenzfälle und Ausnahmesituationen testen.

White-Box Testmethode
Bei der White-Box-Methode werden die Testfälle mit Kenntnis der internen Strukturen des Testobjekts (Source-Code) entwickelt, d.h. diese Methode kann nur von Entwicklern eingesetzt werden. Der laufende Test beim Programmieren ist ebenfalls ein White-Box- Test. Die meisten Entwicklungsumgebungen stellen dazu umfangreiche Tools zur Verfügung (DEBUG), es sind aber auch eigenständige Applikationen für White-Box- Tests auf dem Markt erhältlich.

Black-Box Testmethode
Bei dieser Methode werden die Testfälle aus der vorliegenden Spezifikation (z.B. Pflichtenheft) oder aber aus der Oberflächenstruktur (z.B. Erfassungsmasken, GUI) hergeleitet. Die innere Struktur des Objektes wird nicht berücksichtigt und kann unbekannt sein. Diese Testmethode kann also auch vom Anwender durchgeführt werden.


6.3 Testplan, Testdefinition

Ein Testplan besteht aus den Testanweisungen in Form der Testbeschreibung, dem Testprotokoll mit den Testfällen, welche in thematische Gruppen zusammengefasst sind und dem Testbericht oder Testbefund. (Sign-Off)

Testbeschreibung

Jeder Test sollte mit einer klar verständlichen Anleitung beschrieben werden. Dazu gehören folgende Punkte:

  • Ziel des Tests
  • Art des Tests
  • Verwendete Hilfsmittel
  • Anforderungen an das Testobjekt
  • Testvorgaben (Set-Up)
  • Abbruchkriterien (Check-Points)
  • Weiteres (z.B Tear-Down/Aufräumarbeiten nach dem Test)

Spezifikation von Testfällen

Die Wahl und Anzahl der Testfälle bestimmt nicht nur die Qualität und Wirksamkeit des Tests, sondern auch über die Dauer und Kosten desselben. Da diese Anforderungen gegenläufig sind, ist ein Mittelweg zu finden. Man versucht mit möglichst wenigen, dafür klug ausgewählten Testfällen möglichst viele Fehlermöglichkeiten auszuschliessen. In der Praxis werden funktional zusammenhängende Testfälle gruppiert und als Ganzes beschrieben.

Kriterien für die Auswahl der Testfälle

  • Funktionsüberdeckung: Jede spezifizierte Funktion wird mindestens von einem Testfall geprüft.
  • Eingabeüberdeckung: Jedes korrekte Eingabedatum wird mindestens von einem Testfall verwendet.
  • Extremwerteingaben: Jedes Eingabefeld wird mit mindestens einer Extremwerteingabe getestet.
  • Falscheingaben: Jedes Eingabefeld wird mit mindestens einer Falscheingabe getestet und eine Fehlermeldung erwartet.
  • Ausgabedatum: Jedes Ausgabedatum wird von mindestens einem Testfall erzeugt.

Zusätzlich bei White-Box-Tests:

  • Anweisungs- und Zweigüberdeckung: Die Testfälle werden so ausgelegt, dass alle Zweige und Anweisungen mindestens einmal ausgeführt werden.
  • Strukturierte Pfadüberdeckung: Die Testfälle werden so gewählt, dass alle möglichen Pfade und jede Schleife mehrmals durchlaufen werden.
  • Bedingungsüberdeckung: Alle Bedingungen des Testobjektes werden mindestens einmal mit "True" und einmal mit "False" durchlaufen.

Abschliessender Testbericht bzw. Testbefund (Sign Off): 
Nach der Durchführung eines Tests sollten die Testresultate dokumentiert werden. Der Bericht umfasst folgende Punkte:

  • Einzel protokollierte Testfallergebnisse mit Status (OK / NOK / Ausnahme)
  • Eventuell Review des Tests. Dient zur Verbesserung des Testplanes.
  • Eventuell Mängelliste mit mögliche Fehlerquelle
  • Schlussstatus (Vollständig akzeptiert / Nicht akzeptiert / Bedingt akzeptiert) mit Vorgehensempfehlung.
  • Datum und Unterschriften der ausführenden Organe.
Hier das Template eines Testprotokolls herunterladen.


Zusammenfassung: SW-Testen

Was ist wichtig und worauf sollten wir in M404 mit Powershell den Fokus setzen:

  • In diesem Modul beschränken wir uns auf SW-Abnahmetests in der Form von Blackboxtests.
  • Die zu testende Lieferung soll über eine Produktbezeichnung und Versionsnummer verfügen und damit eindeutig identifizierbar sein. (Bsp. SUPERCALC_V2.3)
  • Es wird formell getestet. Dies bedingt die schriftliche Form.
  • Sämtliche Test müssen reproduzierbar bzw. nachvollziehbar sein und Darum muss mit konkreten Eingabewerten geprüft werden.
  • Nach Testende muss in einem Schlusssatz bzw. Testbericht mitgeteilt werden, ob die Lieferung akzeptiert, mit Einschränkungen akzeptiert oder nicht akzeptiert wird.

Wenn die Lieferung nicht akzeptiert wird:

  • Die Lieferung wird sie zwecks Fehlerbehebung an den Autor zurückgesandt.
  • Der Lieferant/Autor (SW-Entwickler) kann anhand des Testprotokolls seine Fehler korrigieren
  • Nach der Korrektur wird die korrigierte Version der Lieferung erneut vollständig getestet. Es wird ein weiteres Testprotokoll für die neue Version (z.B. CALC_V2.4) erstellt.

Beispiel: Testen einer Taschenrechner-SW

Pflichtenheft:

  • Robuste Eingabe der Zahl1 (-1'000'000 bis + 1'000'000)
  • Robuste Eingabe der Zahl2 (-1'000'000 bis + 1'000'000)
  • Mathematische Operationen: +, -, *, :
  • Definierter Ausgabewertebereich (Resultat) (-1'000'000'000 bis + 1'000'000'000)
  • Korrektes Resultat auf eine Nachkommastelle gerundet

Eine Auswahl von Testfällen

TESTPROTOKOLL
*************
Lieferung     : SUPERCALC
Version       : 2.3
Testart       : SW-Abnahmetest/Blackboxtest
SW-Entwickler : Felix Muster
Tester        : Hans Nötig
Datum         : dd.mm.yyy

-----------------------------------------------------------------
Falsch wäre ein Testfall wie der folgende: (Weil nicht reproduzierbar)

TESTFALL #001 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: Eine positive Zahl
Eingabe Zahl2: Eine positive Zahl
Operation: +
Erwartete Ausgabe: Ausgabe erfolgt ohne Fehler
Tatsächliche Ausgabe:
OK/NOK:
-----------------------------------------------------------------

Richtig hingegen sind Testfälle wie die folgenden: (Weil reproduzierbar)

TESTFALL #001 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: 150
Eingabe Zahl2: 230
Operation: +
Erwartete Ausgabe: 380
Tatsächliche Ausgabe: 380
OK/NOK: OK

TESTFALL #002 (Wegen dem vorangegangenen Testfall #001 ist dieser unnötig, weil kein 100%-Testen realistisch ist!)
-------------
Eingabe Zahl1: 151 (Dieser Eingabewert ist dem in Testfall #001 zu ähnlich!)
Eingabe Zahl2: 231 (Dieser Eingabewert ist dem in Testfall #001 zu ähnlich!)
Operation: +
Erwartete Ausgabe: 382
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #003 (Testgruppe Eingabeüberdeckungen)
-------------
Eingabe Zahl1: -150
Eingabe Zahl2: -200
Operation: +
Erwartete Ausgabe: -350
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #017 (Testgruppe Ausgabedaten)
-------------
Eingabe Zahl1: 2
Eingabe Zahl2: 3
Operation: :
Erwartete Ausgabe: 0.7
Tatsächliche Ausgabe: 0.66666666666666666
OK/NOK: NOK

TESTFALL #057 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 1'000'001
Eingabe Zahl2: 1'000'001
Operation: +
Erwartete Ausgabe: Meldung: Eingabewerte zu gross!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #058 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 120
Eingabe Zahl2: 0.000001
Operation: *
Erwartete Ausgabe: 0.0
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #059 (Testgruppe Extremwerteingaben)
-------------
Eingabe Zahl1: 1'000'000
Eingabe Zahl2: 0.0001
Operation: :
Erwartete Ausgabe: Meldung: Resultat ausserhalb Wertebereich!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #067 (Testgruppe Falscheingaben)
-------------
Eingabe Zahl1: vierzehn
Eingabe Zahl2: elf
Operation: +
Erwartete Ausgabe: Meldung: Eingabewerte sind keine Zahlen!
Tatsächliche Ausgabe:
OK/NOK:

TESTFALL #125 (Testgruppe Falscheingaben)
-------------
Eingabe Zahl1: 150
Eingabe Zahl2: 0
Operation: :
Erwartete Ausgabe: Meldung: Division durch 0 ist verboten!
Tatsächliche Ausgabe:
OK/NOK:

Testbericht:
------------
Lieferung akzeptiert:                     [JA/NEIN]
Lieferung mit Einschränkungen akzeptiert: [JA/NEIN]
Lieferung nicht akzeptiert:               [JA/NEIN]
Datum und Unterschrift des Testers:       .........