Letzten Freitag hat sich ein IT-Sicherheitsunternehmen (Crowdstrike Holdings, Inc. ) nicht
gerade mit Ruhm bekleckert. Das Ergebnis: zahlreiche Systemadministratoren mussten viele
Überstunden leisten und das Wochenende war für viele Menschen außerhalb der IT-Branche
ruiniert.
Dank der Disziplin und Expertise weltweit beschäftigter Systemadministratoren konnten die
meisten Systeme schlussendlich wieder in Betrieb genommen werden. Auch wenn mein
Arbeitgeber nicht direkt betroffen war und mein Team sich folglich stressfrei ins Wochenende
verabschiedete, möchte ich pünktlich zum System Administrator Appreciation Day 
allen interessierten Informatikern mit diesem Beitrag meine Wertschätzung ausdrücken und
einige praktische Lösungsschablonen aus meiner beruflichen Praxis vorstellen.
Lösungsschablonen:
↓ Entscheidungsfindung für Dateidownload über ein Datumsvergleich
↓ Microsoft Exchange 2016: Berichtserstellung über Verteilergruppenbesitzer
↓ PHP: LDAP-Authentifizierung
↓ Benutzeroberfläche: Interaktive GUIs für PowerShell-Skripte entwerfen
Entscheidungsfindung für Dateidownload über ein Datumsvergleich
In der beruflichen Praxis eines Systemadministrators kommt es häufig vor, dass bestimmte
Dateien nur dann heruntergeladen oder aktualisiert werden müssen, wenn sie neuer sind als
die bereits vorhandenen. Eine einfache, aber effektive Methode zur Entscheidungsfindung für Dateidownloads basiert auf einem Datumsvergleich.
Hinzu kommt, dass der vom Betriebssystem aufgedrückte Zeitstempel nach dem Download mit
dem Zeitstempel der Downloadquelle synchronisiert werden muss, um einen effizienten
Datumsvergleich (ab dem zweiten Durchlauf) durchzuführen.
Ausschlaggebend für den Download ist der Zeitstempel - beispielsweise: 2024-07-26 13:45.
Hier ist ein praktisches Beispiel, wie dies mit einem PowerShell-Skript umgesetzt werden kann:
<#
RSA-Update-Downloader Ver. 0.2
(C) Alexander H. / STEALTHCHIP.DE
#>
Start-Transcript -Path "C:\Skripte\RSAUPDATE\RSAKEYDATE-Transcript.log"
$url = "https://trustcenter-data.itsg.de/dale/gesamt-rsa4096.key"
$verzeichnis = "C:\Skripte\RSAUPDATE\RSAFILE"
$logdateipfad = "C:\Skripte\RSAUPDATE\RSAKEYDATE.log"
$dateipfad = Join-Path $verzeichnis (Split-Path $url -Leaf)
if (Test-Path $dateipfad) {
$lastModifiedRemote = (Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing).Headers["Last-Modified"]
$lastModifiedRemoteDateTime = [DateTime]::ParseExact($lastModifiedRemote, "ddd, dd MMM yyyy HH:mm:ss 'GMT'", [System.Globalization.CultureInfo]::InvariantCulture)
$creationTimeLocal = (Get-Item $dateipfad).CreationTime
if ($creationTimeLocal -lt $lastModifiedRemoteDateTime) {
Invoke-WebRequest -Uri $url -OutFile $dateipfad
(Get-Item $dateipfad).CreationTime = $lastModifiedRemoteDateTime
$logText = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Datei heruntergeladen und Erstelldatum aktualisiert."
Add-Content -Path $logdateipfad -Value $logText
} else {
$logText = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Die Datei ist bereits vorhanden und aktuell."
Add-Content -Path $logdateipfad -Value $logText
}
} else {
Invoke-WebRequest -Uri $url -OutFile $dateipfad
$lastModified = (Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing).Headers["Last-Modified"]
$lastModifiedDateTime = [DateTime]::ParseExact($lastModified, "ddd, dd MMM yyyy HH:mm:ss 'GMT'", [System.Globalization.CultureInfo]::InvariantCulture)
(Get-Item $dateipfad).CreationTime = $lastModifiedDateTime
$logText = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Datei heruntergeladen und Erstelldatum aktualisiert."
Add-Content -Path $logdateipfad -Value $logText
}
Stop-Transcript
Der unter $verzeichnis angegebene Ordner muss vor Skriptausführung erstellt werden.
Ein häufiger Fehler beim Kontrollieren ist; visuell abweichende, aber korrekte Zeitstempel aufgrund von
unterschiedlichen Zeitzonen falsch zu interpretieren (zum Beispiel 2024-07-26 13:45 > 2024-07-26 11:45).
Visuell abweichender aber korrekter Zeitstempel (2024-07-26 13:45 > 2024-07-26 11:45) - Zeitzone...
Update 2024-09-10:
Microsoft Exchange 2016: Berichtserstellung über Verteilergruppenbesitzer
Benutzer, Gruppen, Postfächer und andere Exchange-bezogene Einstellungen werden über das
EAC (Exchange Admin Center) verwaltet. Die webbasierte Verwaltungsoberfläche für Exchange
Server bietet jedoch nicht immer die gewünschten Lösungen - insbesondere wenn es um die
Exportmöglichkeit aller Verteilergruppen mit ihren Besitzern geht. Hier bietet die Exchange
Management Shell eine leistungsstarke Alternative.
Im integrierten Reportdialog stehen die Verteilerbesitzer nicht zur Auswahl.
Mit einem PowerShell-Skript lassen sich detaillierte Berichte über die Besitzer von
Verteilergruppen erstellen. Im Folgenden wird gezeigt, wie dies umgesetzt werden kann:
<#
Exchange-Verwaltungsgruppen-Reportgenerator Ver. 0.1
(C) Alexander H. / STEALTHCHIP.DE
#>
# Ermitteln des Skriptverzeichnisses
$scriptPath = $PSScriptRoot
$exportPath = Join-Path -Path $scriptPath -ChildPath "DistributionGroups.csv"
$logPath = Join-Path -Path $scriptPath -ChildPath "DistributionGroups.log"
# Logdatei erstellen oder überschreiben
Out-File -FilePath $logPath -Encoding UTF8
# Alle Verteilergruppen und dynamischen Verteilergruppen abrufen
$distributionGroups = Get-DistributionGroup -ResultSize Unlimited
$dynamicDistributionGroups = Get-DynamicDistributionGroup -ResultSize Unlimited
# Beide Gruppenarten in einer Sammlung zusammenführen
$allGroups = $distributionGroups + $dynamicDistributionGroups
# Ein Array initialisieren, um die Gruppeninformationen zu speichern
$result = @()
# Über jede Gruppe iterieren, um die erforderlichen Informationen zu sammeln
foreach ($group in $allGroups) {
$owners = $group.ManagedBy
$ownerEmails = @()
foreach ($owner in $owners) {
# Versuchen, die E-Mail-Adresse des Besitzers abzurufen
$ownerEmail = (Get-Recipient $owner -ErrorAction SilentlyContinue).PrimarySmtpAddress
if ($ownerEmail) {
$ownerEmails += $ownerEmail
} else {
# Protokollieren des Fehlers in die Logdatei
$logMessage = "Der Besitzer $owner konnte nicht abgerufen werden. Zugewiesene Gruppe: $($group.DisplayName)"
$logMessage | Out-File -FilePath $logPath -Append -Encoding UTF8
}
}
$groupType = if ($group.RecipientTypeDetails -eq "DynamicDistributionGroup") { "Dynamische Verteilergruppe" } else { "Verteilergruppe" }
$result += [PSCustomObject]@{
DisplayName = $group.DisplayName
EmailAddress = $group.PrimarySmtpAddress
GroupType = $groupType
Owners = $ownerEmails -join "; "
}
}
# Die Ergebnisse in eine CSV-Datei exportieren
$result | Export-Csv -Path $exportPath -NoTypeInformation -Encoding UTF8
Write-Host "Export abgeschlossen. Die CSV-Datei befindet sich unter $exportPath"
Write-Host "Fehlerhafte Besitzer wurden in der Logdatei protokolliert: $logPath"
Update 2024-09-13:
PHP: LDAP-Authentifizierung
In vielen Unternehmensnetzwerken ist LDAP (Lightweight Directory Access Protocol) ein
essenzieller Bestandteil zur Verwaltung und Authentifizierung von Benutzern.
Eine LDAP-Authentifizierung kann auch in einer PHP-Anwendung implementiert werden:
<!DOCTYPE html>
<html>
<head>
<title>LDAP-Anmeldungstemplate Ver. 0.1</title>
<!--(C) Alexander H. / STEALTHCHIP.DE-->
<meta charset="UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-form {
display: flex;
flex-direction: column;
width: 300px;
}
.login-form label {
text-align: left;
margin-bottom: 5px;
}
.login-form input[type="text"], .login-form input[type="password"], .login-form input[type="submit"] {
width: 100%;
margin-bottom: 10px;
padding: 8px;
box-sizing: border-box;
}
</style>
<script>
function logToConsole(message) {
console.log(message);
}
</script>
</head>
<body>
<div class="login-container">
<form class="login-form" method="POST" action="">
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password">
<input type="submit" value="Anmelden">
</form>
</div>
<?php
function log_message($message) {
echo "<script>logToConsole('$message');</script>";
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = isset($_POST['username']) ? $_POST['username'] : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
if (empty($username) || empty($password)) {
log_message('Benutzername und Passwort dürfen nicht leer sein.');
} else {
$server = '';
$domain = '@';
$port = 389;
$ldap_connection = ldap_connect($server, $port);
if (!$ldap_connection) {
log_message('Verbindung zum LDAP-Server fehlgeschlagen.');
exit;
}
ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, 0);
$ldap_bind = @ldap_bind($ldap_connection, $username . $domain, $password);
if (!$ldap_bind) {
log_message('Anmeldung am LDAP-Server fehlgeschlagen.');
} else {
log_message('Anmeldung erfolgreich!');
}
ldap_close($ldap_connection);
}
}
?>
</body>
</html>
Diese LDAP-Authentifizierungslogik kann als grundlegende Vorlage für eigene Webdienste
verwendet werden. Für eine erfolgreiche Authentifizierung sind jedoch noch der Server
und die Domäne in den dafür vorgesehenen Variablen anzugeben.
Fehler- und Erfolgsmeldungen werden in der Debugkonsole des Browsers ausgegeben.
Update 2025-01-27:
Benutzeroberfläche: Interaktive GUIs für PowerShell-Skripte entwerfen
PowerShell ist ein mächtiges Werkzeug für die Automatisierung und Verwaltung von Systemen.
Oft ist jedoch eine GUI (Graphical User Interface), also eine grafische Benutzeroberfläche,
wünschenswert, um die Nutzung eines Skripts zu vereinfachen und auch weniger technisch
versierten Anwendern Zugriff zu ermöglichen.
Eine einfache Möglichkeit, GUIs für PowerShell-Skripte zu erstellen, bietet die Integration von
WinForms (Windows Forms). Mit Tools wie ConvertForm von Laurent Dardenne können GUIs,
die zuvor beispielsweise in einer IDE wie Visual Studio Community designt wurden, direkt in
PowerShell-Syntax umgesetzt werden.
Im folgenden Beispiel wird jedoch bewusst auf den Einsatz von Tools verzichtet und eine kleine
Anwendung manuell erstellt, die Benutzereingaben entgegennimmt und verarbeitet:
<#
GUI-Beispiel für PowerShell Ver. 0.1
(C) Alexander H. / STEALTHCHIP.DE
#>
Add-Type -AssemblyName System.Windows.Forms
# Formular erstellen
$form = New-Object System.Windows.Forms.Form
$form.Text = "GUI-Beispiel"
$form.Size = New-Object System.Drawing.Size(400, 200)
# Eingabefeld für Benutzer
$inputTextBox = New-Object System.Windows.Forms.TextBox
$inputTextBox.Location = New-Object System.Drawing.Point(10, 10)
$inputTextBox.Width = 360
$form.Controls.Add($inputTextBox)
# Read-Only-Ausgabefeld
$outputTextBox = New-Object System.Windows.Forms.TextBox
$outputTextBox.Location = New-Object System.Drawing.Point(10, 50)
$outputTextBox.Width = 360
$outputTextBox.ReadOnly = $true
$form.Controls.Add($outputTextBox)
# Button
$button = New-Object System.Windows.Forms.Button
$button.Text = "OK"
$button.Location = New-Object System.Drawing.Point(150, 90)
$form.Controls.Add($button)
# Event-Handler für den Button
$button.Add_Click({
# Zeige den eingegebenen Text im gesperrten Textfeld an
$outputTextBox.Text = $inputTextBox.Text
})
# Formular anzeigen
$form.ShowDialog()
Erklärung des Skripts: Das Skript erstellt eine einfache grafische Benutzeroberfläche, die aus
einem Eingabefeld, einem gesperrten Ausgabefeld und einem Button besteht. Der Benutzer
kann nun einen Text in das Eingabefeld eingeben, und nach einem Klick auf den OK-Button
wird der eingegebene Text im Ausgabefeld angezeigt. Dieses Ausgabefeld ist mit der
Eigenschaft ReadOnly = $true versehen, wodurch es zwar Text anzeigen kann, jedoch keine
Eingaben zulässt. Dadurch wird das Ergebnis des Skripts direkt auf der Benutzeroberfläche
sichtbar und klar dargestellt.
...und so sieht dann die Benutzeroberfläche am Ende aus.
Aber zurück zu ConvertForm . Auf der GitHub-Projektseite wird die Installation transparent
beschrieben, die Verwendung jedoch nicht - zumindest auf den ersten Blick. Um den Einstieg
zu erleichtern, folgt hier ein Skripttemplate, das die Konvertierung einer Windows Forms-App
in PowerShell demonstriert:
$Source = "" #Die Pfadangabe zur XYZ.Designer.cs-Datei ist anzugeben.
$Destination = "" #Die Pfadangabe zum Konvertierungsziel ist anzugeben.
Convert-Form -Path $Source -Destination $Destination -Encoding ascii -force
Damit stünde einer Umsetzung wesentlich komplexerer GUIs nichts mehr im Wege.
Eine PowerShell-GUI mit einem MenuStrip und den Standard-Items, die kaum Rückschlüsse auf einen Skripteinsatz zulässt.
|