Ronnys Blog Die Antwort lautet immer noch 42

Confirm und WhatIf

20 Januar, 2020

Mit Hilfe von [CmdletBinding()] lassen sich die Standardparameter von Powershell einbinden.

Möchte man die Parameter WhatIf und Confirm verwenden, muss man beim CmdletBinding Werte übergeben

Whatif

Beispielfunktion:

function HelloWorld{
  [CmdletBinding(SupportsShouldProcess=$True)]
  param(
    [Parameter(Mandatory=$True)][System.String]$Greeting
  )
  begin{}
  process{
    if ($PSCmdlet.ShouldProcess($Greeting, "Parameter ausgeben")){
      Write-Host $Greeting;
    }
  }
  end{}
}

Diese Beispielfunktion gibt den String aus, den man mit dem Parameter **Greeting** übergibt. Beispiel:

PS C:\Powershell> HelloWorld -Greeting "Bon Jour!"
Bon Jour!

PS C:\Powershell> HelloWorld -Greeting "Bon Jour!"
Bon Jour!

Wenn man jetzt den Parameter WhatIf übergibt, führt die Powershell den Befehl nicht aus, sondern beschreibt, was wir zuvor angegeben haben:

PS C:\Powershell> HelloWorld -Greeting "Hallo!" -WhatIf
What if: Performing the operation "Parameter ausgeben" on target "Hallo!".

Confirm

Möchte man zusätzlich gefragt werden, ob eine Aktivität ausgeführt werden darf (Z.B. Löschen, Ändern von Dingen, o.ä.), kann man den Confirm-Level mit übergeben:

function HelloWorld{
  [cmdletBinding(SupportsShouldProcess=$True,ConfirmImpact='High')]
  ...

Standardmäßig fragt die Powershell immer dann nach, wenn das in einer Funktion oder Cmdlet angegebene Confirm-Level gleich oder höher ist als das gegenwertig in der Session angegebene Confirm-Level ($ConfirmPreference). Möglich sind die Werte 'None', 'Low', 'Medium' und 'High'

Testet man die so angepasste Funktion erneut aus, fragt die Powershell explizit nach:

PS C:\Powershell>HelloWorld -Greeting "Aloha!"

Confirm
Are you sure you want to perform this action?
Performing the operation "Parameter ausgeben" on target "Aloha!".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Y
Aloha!

Quelle, Quelle2

Logging-Modul überarbeitet

24 Februar, 2019

Ich habe heute mal meine freie Zeit genutzt und das Logging-Modul überarbeitet.

Anders als vorher ist das gesamt Ding jetzt deutlich modularer und bietet zudem zwei weitere LogProvider an: Memory und EventLog.

Zu finden ist das Modul (und eine Kurzanleitung über die Verwendung) wie gehabt unter https://gitlab.com/rldml/PowershellLogging

Logging-Modul Fehler behoben

10 September, 2018

Mein Logging-Modul hatte einen kleinen Fehler in der Funktion Find-LogFile. Sobald die erste Logdatei voll ist, soll das Modul eine zweite Logdatei anlegen. An der Stelle ich versehentlich nicht den Dateipfad, sondern das Dateiobjekt übergeben.

Der Fehler ist jetzt korrigiert und im Repository von Gitlab verfügbar: https://gitlab.com/rldml/PowershellLogging

$VerbosePreference korrekt neu setzen

14 Mai, 2018

Wenn man öfter Scripte in Powershell schreiben muss, dann wünscht man sich gelegentlich einen dynamischeren Umgang mit der Darstellung von Infonachrichten.

Powershell bietet von Haus an, Verbose-Nachrichten zu erzeugen. Dies geschieht mittels des Cmdlets:

Write-Verbose "Insert $Message here!"

damit Verbose-Benachrichtigungen bei der Ausführung eines Scriptes angezeigt werden, gibt es mehrere Möglichkeiten:

Die erste Variante ist die, dass wir dem Cmdlet selbst den Verbose-Parameter mitgeben:

Write-Verbose "Insert $Message here!" -Verbose

Dies ist natürlich ein Stück weit sinnbefreit. Deshalb ergibt es Sinn, zu Beginn eines Scriptes folgende Definitionen zu setzen (analog zu einer Powershell-Funktion)

[CmdletBinding()]
param()

Anschließend lässt das Script beim Aufruf den Verbose-Parameter zu:

script-test.ps1 -Verbose

Die Powershell setzt für die Laufzeit des Scripts den Variable $VerbosePreference auf "Continue" (falls der Parameter übergeben wird) oder auf "SilentlyContinue" (falls der Parameter fehlt).

Dies kann man innerhalb des Scripts ausnutzen und unbeliebte Teile des Scripts (zum Beispiel beim Import von Powershell-Modulen) ausblenden

$VerbosePreferenceOLD = $VerbosePreference
$VerbosePreference = [System.Management.Automation.ActionPreference]::SilentlyContinue

Import-Module ActiveDirectory
Import-Module SomeOtherModule
.
.
.

$VerbosePreference = $VerbosePreferenceOLD

Man kann zwar auch

$VerbosePreference = "Continue"

schreiben (und bis heute hat das auch fast immer bei mir geklappt), jedoch die Variable sauber zu definieren stellt sicher, dass das Script auch in späteren Powershellversionen funktionieren wird.

Brückentag Masters...

11 Mai, 2018

Das tolle an Brückentagen ist ja, dass man relativ entspannt Dinge erledigen kann, die sonst immer im Büro liegen bleiben.

Die letzten Tage habe ich daran gearbeitet, das Logging von Powershell-Scripten zu erweitern, bzw. zu dynamisieren. Das Problem, auf dass ich in meinen Scripten gestoßen bin, ist kurz gesagt, dass ich zum Zeitpunkt des Erstellens eines Scriptes noch nicht sicher sagen kann, wo und wie Dinge zu loggen sind.

Wird mein Script direkt in der Konsole ausgeführt? Dann würde es ausreichen, den Log-Krempel in den Verbose-Stream zu werfen. Falls das Script jedoch als geplanter Task oder indirekt über ein drittes Script ausgeführt wird, hilft mir der Verbose-Channel relativ wenig. Ich kann ein Script zwar durch ein zweites ummanteln, die Verbose-Benachrichtigungen auffangen und mir wiederrum in eine Datei oder Datenbank schreiben, aber dann habe ich nur die Nachrichten ohne Zeitstempel.

Letzteres ist oft zwar kein Problem, aber so manches Mal eben doch noch ein Stück weit relevant.

Meine Idee ist, anstatt jetzt in jedem Script fest vorzugeben, was wohin geloggt wird, überlasse ich das dem Ausführenden des Script.

Der Befehl zum Schreiben eines Logs wird dabei in der Art wie diese Zeile aufgerufen:

Write-LogMessage -Message "Hello World!" -Type "Information"

Denkbar einfach und ohne großen Stress. Wenn ich das Ziel dynamisieren will, muss ich dem Write-LogMessage noch mitgeben, wohin die Nachricht gespeichert werden soll. Ich habe mir dafür eine Powershell-Klasse konstruiert:

class LogProfileData{
    [string]$BasePath
    [int]$MaxSize
    [switch]$FileRotation
    [int]$MaxFiles
    [string]$Mode
}

Das kann man wahrscheinlich noch wunderschön optimieren, aber für meine Zwecke reicht es zunächst.

Die Variable $Mode soll verschiedene Möglichkeiten repräsentieren, wohin eine Logzeile geschrieben werden soll. Für den Anfang reichen mir hier zwei Werte: "Verbose" und "File". Die anderen Werte in der Klasse beschreiben genauer, wo Logfiles abzulegen sind (Falls als $Mode der Wert "File" übergeben wird.

Die Funktion Write-LogMessage habe ich um einen weiteren Parameter erweitert:

Write-LogMessage -Message "Hello World2!" -Type "Informational" -LogProfile $Log

Die Parameterfunktionalität ValueFromPipeline und der foreach-Block in dem process-Teil der Funktion ermöglichen mir jetzt, sowohl ein einzelnes Logprofil zu übergeben, oder ein Array von mehreren Profilen. Bei jeder Nachricht wird geprüft, von welchem Typ die Nachricht sein soll und entsprechend verfahren.

Bei einem Logprofil mit $Mode = "Verbose" wird zum Beispiel einfach eine Logzeile an den Verbosestream ausgegeben.

Das PS-Modul hat noch ein wenig Feinschliff nötig, macht aber bereits einen guten Job. Zudem ist das Modul beliebig um weitere LogProfil-Varianten erweiterbar: Du willst einen Splunk-Server mit Nachrichten beglücken? Dann erweitern wir das LogProfil um einen weiteren Modus. Wichtig ist lediglich, dass man von Powershell oder .NET aus die Nachricht absetzen kann.

Eine erste Version des Moduls findet ihr hier:

https://gitlab.com/rldml/PowershellLogging

An dem Teil muss ich noch einiges an Krempel erledigen, bevor es wirklich spruchreif ist, aber diese Version lässt sich bereits verwenden

Klassen in Powershell (ab Version 5)

02 Mai, 2018

Ich habe ein neues Feature in der Powershell entdeckt, dass mir bei meinem Scripting-Alltag das Leben deutlich erleichtert: Klassen.

Analog zu anderen Sprachen kann man in der Powershell Datenkonstrukte definieren und diese an vorhandene Cmdlets oder selbst geschriebene Funktionen weiter reichen. Für gewöhnlich nimmt man dazu entweder ein Array, eine Hashtable oder (in manchen Fällen) fragt die erforderlichen Einzelwerte eines Datensets in Form von Parametern ab.

Möchte man jedoch sicherstellen, dass man ein definiertes Set an Daten übergeben wird, kann man dieses Ziel bisher nur schwer erreichen:

  • Ein Array kann beliebige Daten enthalten, oder auch die benötigten Daten in der falschen Reihenfolge. Es gibt keine Möglichkeit, zuverlässig zu überprüfen, ob ein übergebenes Array die angeforderten Daten enthält, dass die Funktion zum Arbeiten benötigt.
  • Eine Hashtable kann beliebig definiert werden. Schränkt man den Funktionsparameter so ein, dass er nur noch eine Hashtable entgegen nimmt, hat man dennoch keine Garantie, dass die übergebene Hashtable die korrekten Datenfelder beinhaltet.
  • Jeden abzurufenden Wert in einen eigenen Parameter zu hüllen, ist zwar eine valide Möglichkeit, aber in manchen Situationen dennoch unpraktisch, da die Korrektheit der Daten in jeder Funktion separat geprüft werden muss (Das kann man zwar ebenfalls wieder in eine Funktion extrahieren, aber irgendwann wird es dann auch unübersichtlich)

Mit Klassen in Powershell (Version 5) kann man diese Probleme umgehen und sie sind denkbar einfach geschrieben:

class MyClass{
  [string]$Name
  [int]$Age
}

Dies erzeugt einen Datentyp mit dem Namen MyClass und den Eigenschaften Name und Age. Ein Objekt wird denkbar einfach mit folgender Zeile erzeugt:

$object = New-Object -TypeName MyClass

 Und befüllen ist ähnlich leicht:

$object.Name = "Hans"
$object.Age = 34

Wollen wir nun eine Funktion schreiben, die ein Objekt der Klasse "MyClass" entgegen nehmen soll, können wir folgenden Funktionsaufruf definieren:

function HelloPerson{
	[CmdletBinding()]
	param(
		[Parameter(Mandatory=$true)][MyClass]$Person
	)
	begin{}
	process{
		$HelloWorld = "Hello " + $Person.Name
		$HelloWorld
	}
	end{}
}

Die Funktion wird für den Parameter Person jetzt nur noch ein Objekt der Klasse MyClass entgegen nehmen.

Darüber hinaus kann man auch Methoden für die Powershell-Klassen definieren. Es steht also nichts im Wege, innerhalb der Klasse eine Methode Check() zu definieren, die überprüft, ob die Werte der Klasse valide sind.

Die Prüfung kann man dann in jeder weiteren Funktion mit

$object.check()

abhandeln und direkt mit dem Inhalt der eigentlichen Aufgabe ausführen.

Wie man Methoden in Klassen definiert, dazu schreibe ich später mehr, sobald ich mich damit befasst habe.

Home