monsitj - stock.adobe.com

So schreiben Sie Protokollierungs-Funktionen mit PowerShell

Administratoren arbeiten mit Protokollen, um Fehler aufzuspüren. Wir erklären, wie Sie eine PowerShell-Funktion schreiben, um Protokolle aus verschiedenen Quellen zu sammeln.

Wenn Sie vorausschauend denken und in Ihren PowerShell-Skripten von Anfang an eine ordnungsgemäße Protokollierung implementieren, haben Sie es bei der Fehlersuche viel leichter.

Es gibt viele verschiedene Möglichkeiten, diese Aufgabe anzugehen. In PowerShell bedeutet das, dass Sie die Ereignisse in Ihrer Umgebung auf dem Host, in einer Datei, im Windows-Ereignisprotokoll, in einer Datenbank, im Syslog unter Linux oder sogar in einer Kombination dieser Möglichkeiten protokollieren. Je mehr Optionen Sie kombinieren, desto mehr Flexibilität und Möglichkeiten haben Sie, aber desto komplizierter wird auch der Prozess zum Einrichten der Protokolle und die Fehlersuche.

Es hat sich bewährt, eine benutzerdefinierte PowerShell-Protokollierungsfunktion einzusetzen, die ihre Ausgabe dorthin leitet, wo Sie sie für Ihre Skripte und Funktionen benötigen. Auf diese Weise sammeln Administratoren die Informationen aus den Systemen und Geräten, die ihnen beim Lösen von Problemen helfen und finden sie alle am selben Ort. Die potenzielle Zeitersparnis bei der Fehlersuche ist kaum zu überschätzen.

Aufbau des Frameworks

Entscheiden Sie zunächst, wo Sie überall protokollieren möchten. Eine gute Ausgangsbasis ist das Protokollieren auf dem Host, in einer Datei und in einer Datenbank.

Als nächstes erstellen Sie das Grundgerüst der PowerShell-Protokollierungsfunktion.

Function Write-Log {

     [CmdletBinding()]

     Param (

         $Message

     )

     Process {

         Write-Host $Message

     }

 }

Im einfachsten Fall definieren wir einen einzelnen Parameter und geben ihn an den Host zurück. Durch CmdletBinding können wir die Vorteile der erweiterten Cmdlet-Funktionen nutzen, die wir im weiteren Verlauf dieses Tutorials kennenlernen.

Vorbereiten der Parameter

Als nächstes fügen Sie einige zusätzliche Parameter hinzu. Eine der üblichen Funktionen in der PowerShell-Protokollierung ist die Protokollierungsebene (Level), zum Beispiel Information, Verbose, Error oder Debug.

Fügen Sie einige Parameter in den Param-Block und einige Anweisungen ein, um die PowerShell-Protokollierungsfunktion verständlicher zu gestalten.

Param (

     [Parameter(

         Mandatory=$true,

         ValueFromPipeline=$true,

         Position=0)]

     [ValidateNotNullorEmpty()]

     [String]$Message,

   [Parameter(Position=1)]

     [ValidateSet("Information","Warning","Error","Debug","Verbose")]

     [String]$Level = 'Information',

     [String]$Path = [IO.Path]::GetTempPath(),

     [String]$Server,

     [String]$Database,

     [String]$Table,

     [Switch]$NoHost,

     [Switch]$SQL,

     [Switch]$File

 )

Die neuen Parameter im Skript bewirken Folgendes:

  • Message. Dies ist eine Zeichenfolge, die an die verschiedenen Ziele im Skript gesendet wird. Sie ist obligatorisch, in der Pipeline aktiviert und lässt sich ohne Definition eines Parameters verwenden.
  • Level. Das Level hängt ab von: information, verbose, error, debug. Die Voreinstellung ist information. Das Skript macht dies nutzbar, ohne einen Parameter zu definieren.
  • Path. Wenn Sie die Protokolle in eine Datei schreiben, verwenden Sie den standardmäßigen temporären Pfad. Das Skript verwendet [IO.Path]::GetTempPath(), um plattformübergreifend zu arbeiten, und nutzt standardmäßig den temporären Pfad unter Linux und Windows.
  • Server. Um Protokolle in einer Datenbank zu speichern – in diesem Fall SQL Server, stellt das Skript den Server bereit, der sich auf die Windows-Authentifizierung stützt.
  • Database. Zusammen mit dem Server liefert das Skript eine Datenbank zum Speichern der Daten.
  • Table. In diesem Abschnitt definieren wir die Tabelle, an die wir die Protokolle senden werden.
  • NoHost. Dieser Teil erzeugt Protokolleinträge, ohne sie an den Host auszugeben.
  • SQL. Der Schalter legt fest, ob Protokolle an den SQL Server gesendet werden sollen. Dies erfordert, dass Sie wie oben Server, Datenbank und Tabelle festlegen.
  • File. Der Schalter legt fest, ob die Protokollausgabe an die Datei gesendet werden soll.

Definieren der Logik

Der nächste Teil unserer PowerShell-Protokollierungsfunktion konzentriert sich auf die Ausgabe.

$DateFormat = "%m/%d/%Y %H:%M:%S"

 If (-Not $NoHost) {

   Switch ($Level) {

     "information" {

       Write-Host ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

       Break

     }

     "warning" {

       Write-Warning ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

       Break

     }

     "error" {

       Write-Error ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

       Break

     }

     "debug" {

       Write-Debug ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Debug:$true

       Break

     }

     "verbose" {
       Write-Verbose ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Verbose:$true

       Break

     }

   }

 }

 If ($File) {

Add-Content -Path (Join-Path $Path 'log.txt') -Value ("[{0}] ({1}) {2}" -F (Get-Date -UFormat $DateFormat), $Level, $Message)

 }

 If ($SQL) {

   If (-Not $Server -Or -Not $Database -Or -Not $Table) {

     Write-Error "Fehlende Parameter"

     Return

   }

   $connection                  = New-Object System.Data.SqlClient.SqlConnection

   $connection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security=SSPI;"

   If (-Not ($connection.State -like "Open")) {

     $connection.Open()

   }

   $sqlCommand = New-Object System.Data.SqlClient.SqlCommand

   $sqlCommand.Connection = $connection

   $sqlCommand.CommandText = "INSERT INTO [$Database].dbo.$table ( DateTime, Level, Message ) VALUES ( @DateTime, @Level, @Message )"

   $sqlCommand.Parameters.Add("@DateTime", [System.Data.SqlDbType]::VarChar, 255) | Out-Null

   $sqlCommand.Parameters.Add("@Level",    [System.Data.SqlDbType]::VarChar, 255) | Out-Null

   $sqlCommand.Parameters.Add("@Message",  [System.Data.SqlDbType]::NText) | Out-Null

   $sqlCommand.Parameters['@DateTime'].Value = ( Get-Date -UFormat $DateFormat )

   $sqlCommand.Parameters['@Level'].Value    = $Level

   $sqlCommand.Parameters['@Message'].Value  = ($message | Out-String)

   Try {

     $sqlCommand.ExecuteNonQuery() | Out-Null

   } Catch {

     Write-Error "Unable to Insert Log Record: $($_.Exception.Message)"

   }

   If ($connection.State -like "Open") {

     $connection.Close()

   }

 }

Der Code besteht aus drei verschiedenen Abschnitten, die so strukturiert sind, dass mehrere Protokollierungsvarianten möglich sind, zum Beispiel die gleichzeitige Ausgabe an drei verschiedene Quellen.

  • Host: Sie können die Ausgabe auf der Grundlage des gewünschten Levels generieren, zum Beispiel verbose, warning oder error. Wenn Sie den Schalter nohost definiert haben, dann überspringen Sie diesen Teil. Andernfalls ist die Standardeinstellung unserer Funktion die Ausgabe auf dem Host. Das Skript zwingt die Funktionen Write-Debug und Write-Verbose mit -Debug:US-Dollartrue und -Verbose:US-Dollartrue, die Ausgabe an den Host zu senden.
  • File. Die Ausgabe in eine Datei ist die einfachste Variante. Das Skript fügt den Inhalt für jede neue Protokollzeile in eine Datei ein.
  • SQL. Die komplizierteste Funktion ist die SQL-Funktionalität. Ein Großteil des Skripts besteht aus Standardcode, der die Verbindung einrichtet und den Einfügebefehl vorbereitet. Das Skript verwendet die SQL-Parametrisierung, um den Einfügecode sicherer zu machen.

Nachfolgend sehen Sie ein Beispiel für eine Logs-Tabelle. Sie enthält die in der Einfügeabfrage definierten Felder, um die Datenbank zum Einfügen der Daten einzurichten.

CREATE TABLE [dbo].[Logs](

   [DateTime] [varchar](255) NOT NULL,

   [Level] [varchar](255) NOT NULL,

   [Message] [ntext] NOT NULL,

 );

Der Einfachheit halber hier der vollständige Code.

Function Write-Log {

  [CmdletBinding()]

     Param (

         [Parameter(

             Mandatory=$true,

             ValueFromPipeline=$true,

             Position=0)]

         [ValidateNotNullorEmpty()]

         [String]$Message,

       [Parameter(Position=1)]

         [ValidateSet("Information","Warning","Error","Debug","Verbose")]

         [String]$Level = 'Information',

         [String]$Path = [IO.Path]::GetTempPath(),

         [String]$Server,

         [String]$Database,

         [String]$Table,

         [Switch]$NoHost,

         [Switch]$SQL,

         [Switch]$File

     )

     Process {

         $DateFormat = "%m/%d/%Y %H:%M:%S"

         If (-Not $NoHost) {

           Switch ($Level) {

             "information" {

               Write-Host ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

               Break

             }

             "warning" {

               Write-Warning ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

               Break

             }

             "error" {

               Write-Error ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)

               Break

             }

             "debug" {

               Write-Debug ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Debug:$true

               Break

             }

             "verbose" {

               Write-Verbose ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Verbose:$true

               Break

             }

           }

         }

         If ($File) {

           Add-Content -Path (Join-Path $Path 'log.txt') -Value ("[{0}] ({1}) {2}" -F (Get-Date -UFormat $DateFormat), $Level, $Message)

         If ($SQL) {

           If (-Not $Server -Or -Not $Database -Or -Not $Table) {

             Write-Error "Fehlende Parameter"

             Return

           }

           $connection                  = New-Object System.Data.SqlClient.SqlConnection

           $connection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security=SSPI;"

           If (-Not ($connection.State -like "Open")) {

             $connection.Open()

           }

           $sqlCommand = New-Object System.Data.SqlClient.SqlCommand

           $sqlCommand.Connection = $connection

           $sqlCommand.CommandText = "INSERT INTO [$Database].dbo.$table ( DateTime, Level, Message ) VALUES ( @DateTime, @Level, @Message )"

           $sqlCommand.Parameters.Add("@DateTime", [System.Data.SqlDbType]::VarChar, 255) | Out-Null

           $sqlCommand.Parameters.Add("@Level",    [System.Data.SqlDbType]::VarChar, 255) | Out-Null

           $sqlCommand.Parameters.Add("@Message",  [System.Data.SqlDbType]::NText) | Out-Null

           $sqlCommand.Parameters['@DateTime'].Value = ( Get-Date -UFormat $DateFormat )

           $sqlCommand.Parameters['@Level'].Value    = $Level

           $sqlCommand.Parameters['@Message'].Value  = ($message | Out-String)

           Try {

             $sqlCommand.ExecuteNonQuery() | Out-Null

           } Catch {

             Write-Error "Einfügen der Log-Aufzeichnung unmöglich: $($_.Exception.Message)"

           }

           If ($connection.State -like "Open") {

             $connection.Close()

           }

         }

     }

 }

Die Protokollierungsfunktion in der Praxis

Wie verwenden wir unsere Funktion, jetzt, da wir sie geschrieben haben? Gibt es noch etwas, das wir protokollieren sollten? Was ist für die Fehlerbehebung in der Zukunft am sinnvollsten?

Die folgenden Beispiele zeigen, wie Sie die neue Protokollierungsfunktion verwenden können:

Write-Log -Message "Erste Nachricht"

 Write-Log "Zweite Meldung" -Level "Warnung"

 Write-Log "Dritte Meldung" -NoHost -File

 Write-Log "Vierte Meldung" -SQL -Server "SQLServer" -Database "Logging" -Table "Logs"

 Write-Log "Fünfte Meldung" -Level "Fehler" -File

Was ist, wenn wir in SQL protokollieren, aber nicht wiederholt dieselben Parameter definieren wollen? PowerShell erlaubt es Ihnen, Standardparameter zu verwenden, sofern Sie diese nicht pro Funktionsaufruf außer Kraft gesetzt haben.

$PSDefaultParameterValues = @{

     'Write-Log:SQL'      = $true

     'Write-Log:Server'   = 'SQLServer'

     'Write-Log:Database' = 'Logging'

     'Write-Log:Table'      = 'Logs'

 }

 }

Wenn Sie einige Standardparameter definieren, kann das Skript Folgendes verwenden.

# Vollständige Funktion mit allen Parametern

 Write-Log "Protokollierungsmeldung" -SQL -Server "SQLServer" -Database "Protokollierung" -Table "Protokolle"

 # Gleicher Funktionsaufruf mit Standardparametern

 Write-Log "Logging-Meldung"

Dies macht es viel einfacher, das Protokollierungsverhalten zu steuern und bietet mehr Flexibilität.

Das Endergebnis ist eine PowerShell-Protokollierungsfunktion, die eine Ausgabe erzeugt, ähnlich wie in Abbildung 1.

Abbildung 1: Beispiel für die Ausgabe der PowerShell Logging-Funktion.
Abbildung 1: Beispiel für die Ausgabe der PowerShell Logging-Funktion.

Zusätzliche Protokollierungsfunktionen, die Sie berücksichtigen sollten

Die PowerShell-Protokollierungsfunktion in diesem Artikel ist an sich schon nützlich, aber es gibt noch viel mehr Möglichkeiten, die Sie hinzufügen können. Dazu gehört die Ausgabe an syslog in Linux und sogar die interne Protokollierung für die Funktion selbst, um zu diagnostizieren, wenn das Skript nicht wie erwartet funktioniert.

Erfahren Sie mehr über Desktop-Management