3️⃣. PS – Get-GeoIP & check if the IP is blocked

PowerShell
function Get-GeoIP 
{
    [cmdletbinding()]
    param(
    [Parameter(Mandatory=$true)]
    [System.Net.IPAddress]$ip,
    [switch]$block
    #$ip
    )
    try{
        $webclient = New-Object System.Net.webclient
        if($block -eq $true){
            $blockL ="http://api.blocklist.de/api.php?ip=$ip&start=1"
            $blockData = $webclient.downloadstring($blockL)
            $blockData.Replace('<br />'," ")
        }
        else{
            $providerRoot = "http://ip2c.org/"
            $geoData = $webclient.downloadstring($providerRoot+$ip)
            Write-Output $geoData.Split(';')[3]
        }
        #$geoData = Invoke-RestMethod -Method Get -Uri $providerRoot+$ip -TimeoutSec 35 -ErrorAction SilentlyContinue
    }catch{"NA"}
}

🔍 Überblick und Funktionalität

Das Skript Get-GeoIP hat folgende Hauptfunktionen:

  • Geolokalisierung: Ermittelt das Land (und optional detaillierte Standortinformationen) einer angegebenen IP-Adresse über externe Dienste.
  • Blocklistenprüfung: Überprüft, ob die IP-Adresse in bekannten Blocklisten (z. B. von blocklist.de) enthalten ist.
  • Pipeline-Unterstützung: Ermöglicht den gleichzeitigen Abruf von Geolokalisierungsdaten mehrerer IP-Adressen.
  • Fehlerbehandlung: Fängt mögliche Fehler ab und liefert aussagekräftige Fehlermeldungen.

📝 Parameter und Beispiele

Das Skript akzeptiert folgende Parameter:

  • -IP (erforderlich): Die IP-Adresse, die abgefragt werden soll. Der Parameter unterstützt sowohl IPv4- als auch IPv6-Adressen und validiert das Format.
  • -Block (Switch): Wird dieser Parameter gesetzt, prüft das Skript, ob die IP-Adresse in Blocklisten enthalten ist.
  • -Detailed (Switch): Aktiviert den Abruf detaillierter Geolokalisierungsdaten (z. B. Stadt, Region, Koordinaten, Zeitzone und ISP).
PowerShell
# Gibt das Land der IP 8.8.8.8 zurück
Get-GeoIP -IP 8.8.8.8

# Gibt detaillierte Geolokalisierungsdaten für 8.8.8.8 zurück
Get-GeoIP -IP "8.8.8.8" -Detailed

# Prüft, ob 8.8.8.8 in Blocklisten enthalten ist
Get-GeoIP -IP "8.8.8.8" -Block

# Mehrere IPs über die Pipeline verarbeiten
"8.8.8.8", "1.1.1.1" | Get-GeoIP

🔧 Funktionsweise im Detail

Im begin-Block wird eine Verbose-Nachricht ausgegeben, um den Start der GeoIP-Abfrage zu protokollieren. Im process-Block übernimmt das Skript folgende Aufgaben:

  • Es erzwingt die Nutzung von TLS 1.2 für sichere Web-Anfragen.
  • Es erstellt ein WebClient-Objekt mit einem benutzerdefinierten User-Agent-Header.
  • Wird der -Block Parameter gesetzt, fragt das Skript über blocklist.de ab, ob die IP-Adresse als bösartig gemeldet wurde.
  • Ohne -Block wird, abhängig vom -Detailed Switch, entweder über ipapi.co (für detaillierte Informationen) oder über ip2c.org (für schnelle Länderinformationen) abgefragt.
  • Die Rückgabe erfolgt als PSCustomObject mit den ermittelten Feldern, sodass die Ergebnisse leicht weiterverarbeitet oder in Berichten verwendet werden können.

Fehler werden mittels try/catch abgefangen und in der Ausgabe dokumentiert.

PowerShell
function Get-GeoIP 
{
    [cmdletbinding()]
    param(
    [Parameter(Mandatory=$true)]
    [System.Net.IPAddress]$ip,
    [switch]$block
    #$ip
    )
    try{
        $webclient = New-Object System.Net.webclient
        if($block -eq $true){
            $blockL ="http://api.blocklist.de/api.php?ip=$ip&start=1"
            $blockData = $webclient.downloadstring($blockL)
            $blockData.Replace('<br />'," ")
        }
        else{
            $providerRoot = "http://ip2c.org/"
            $geoData = $webclient.downloadstring($providerRoot+$ip)
            Write-Output $geoData.Split(';')[3]
        }
        #$geoData = Invoke-RestMethod -Method Get -Uri $providerRoot+$ip -TimeoutSec 35 -ErrorAction SilentlyContinue
    }catch{"NA"}
}

Wenn man den ersten noch etwas aufhübscht durch Comment-Based-Help und erweiterte Funktionalität mit detaillierten Informationen und besserer Fehlerbehandlung durch hinzufügen von Verbose, sowie die Pipeline-Unterstützung.

PowerShell
<#
.SYNOPSIS
 Ermittelt Geolokalisierungsdaten für eine IP-Adresse oder prüft, ob diese in Blocklisten enthalten ist.

.DESCRIPTION
 Diese Funktion ruft Geolokalisierungsdaten für eine angegebene IP-Adresse ab 
 oder prüft, ob die IP-Adresse in bekannten Blocklisten enthalten ist.
 Es werden externe Dienste verwendet, um die Daten zu ermitteln (ip2c.org, ipapi.co und blocklist.de).
 Die Funktion kann sowohl einfache Länderinformationen als auch detaillierte Standortdaten liefern.

.PARAMETER IP
 Die IP-Adresse, die geolokalisiert oder auf Blocklisten überprüft werden soll.
 Dieser Parameter ist obligatorisch.

.PARAMETER Block
 Gibt an, ob die IP-Adresse in Blocklisten überprüft werden soll.
 Wenn dieser Parameter gesetzt ist, wird der Dienst blocklist.de abgefragt.
 Standardwert: $false

.PARAMETER Detailed
 Gibt an, ob detaillierte Geolokalisierungsinformationen abgerufen werden sollen.
 Wenn dieser Parameter gesetzt ist, werden Stadt, Region, Koordinaten usw. zurückgegeben.
 Standardwert: $false

.EXAMPLE
 Get-GeoIP -IP "8.8.8.8"
 Gibt das Land zurück, in dem die IP-Adresse 8.8.8.8 registriert ist.

.EXAMPLE
 Get-GeoIP -IP "8.8.8.8" -Detailed
 Gibt detaillierte Geolokalisierungsdaten (Land, Stadt, Region, Koordinaten) für die IP-Adresse 8.8.8.8 zurück.

.EXAMPLE
 Get-GeoIP -IP "8.8.8.8" -Block
 Prüft, ob die IP-Adresse 8.8.8.8 in bekannten Blocklisten enthalten ist.

.EXAMPLE
 "8.8.8.8", "1.1.1.1" | Get-GeoIP
 Verarbeitet mehrere IP-Adressen über die Pipeline und gibt deren Geolokalisierungsdaten zurück.

.NOTES
 Dateiname:      Get-GeoIP.ps1
 Autor:          Ismahil Ahmed
 Erstelldatum:   13.03.2023
 Version:        2.1
 
 Änderungsprotokoll:
 1.0 - Ursprüngliche Version
 2.0 - Erweiterte Funktionalität mit detaillierten Informationen, besserer Fehlerbehandlung und Pipeline-Unterstützung
 2.1 - Timeout-Einstellung entfernt, da nicht in allen PowerShell-Versionen verfügbar
#>

function Get-GeoIP {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, 
                   ValueFromPipeline = $true, 
                   ValueFromPipelineByPropertyName = $true,
                   HelpMessage = "IP-Adresse, die geolokalisiert werden soll")]
        [ValidateScript({
            $_ -match "^(\d{1,3}\.){3}\d{1,3}$" -or 
            $_ -match "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"
        })]
        [System.Net.IPAddress]$IP,

        [Parameter(HelpMessage = "Prüft, ob die IP in einer Blockliste aufgeführt ist")]
        [switch]$Block,

        [Parameter(HelpMessage = "Zeigt detaillierte Standortinformationen an")]
        [switch]$Detailed
    )

    begin {
        # Protokolliert den Start der Funktion
        Write-Verbose "Starte GeoIP-Abfrage"
    }

    process {
        try {
            # Verwenden von TLS 1.2 für mehr Sicherheit
            [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
            
            # Erstellen eines WebClient-Objekts
            $webClient = New-Object System.Net.WebClient
            $webClient.Headers.Add("User-Agent", "PowerShell/Get-GeoIP")
            
            if ($Block) {
                Write-Verbose "Prüfe Blocklisten-Status für IP: $IP"
                $blockListUrl = "https://api.blocklist.de/api.php?ip=$IP&start=1"
                
                $blockData = $webClient.DownloadString($blockListUrl)
                
                if ($blockData -eq "no attacks" -or $blockData -match "attacks: 0<br />reports: 0<br />") {
                    [PSCustomObject]@{
                        IP = $IP
                        Status = "Nicht blockiert"
                        Details = "Keine Angriffe von dieser IP gemeldet"
                    }
                }
                else {
                    [PSCustomObject]@{
                        IP = $IP
                        Status = "Blockiert"
                        Details = $blockData.Replace('<br />', ', ')
                    }
                }
            }
            else {
                Write-Verbose "Geolokalisiere IP: $IP"
                
                if ($Detailed) {
                    # Verwende ipapi.co für detaillierte Informationen
                    $geoDetailUrl = "https://ipapi.co/$IP/json/"
                    $geoDetailData = $webClient.DownloadString($geoDetailUrl)
                    $geoDetailJson = $geoDetailData | ConvertFrom-Json
                    
                    [PSCustomObject]@{
                        IP = $IP
                        Land = $geoDetailJson.country_name
                        LandCode = $geoDetailJson.country_code
                        Region = $geoDetailJson.region
                        Stadt = $geoDetailJson.city
                        Postleitzahl = $geoDetailJson.postal
                        Zeitzone = $geoDetailJson.timezone
                        Latitude = $geoDetailJson.latitude
                        Longitude = $geoDetailJson.longitude
                        ISP = $geoDetailJson.org
                    }
                }
                else {
                    # Verwende ip2c.org für einfache Länderinformationen (schneller)
                    $providerRoot = "http://ip2c.org/"
                    $geoData = $webClient.DownloadString($providerRoot + $IP)
                    $geoDataParts = $geoData.Split(';')
                    
                    if ($geoDataParts[0] -eq "1") {
                        [PSCustomObject]@{
                            IP = $IP
                            LandCode = $geoDataParts[1]
                            Land = $geoDataParts[3]
                        }
                    }
                    else {
                        Write-Warning "Für IP $IP wurden keine Geolokalisierungsdaten gefunden"
                        [PSCustomObject]@{
                            IP = $IP
                            LandCode = "NA"
                            Land = "Nicht verfügbar"
                        }
                    }
                }
            }
        }
        catch {
            Write-Error "Fehler bei der GeoIP-Abfrage für $IP : $($_.Exception.Message)"
            [PSCustomObject]@{
                IP = $IP
                LandCode = "ERROR"
                Land = "Fehler bei der Abfrage: $($_.Exception.Message)"
            }
        }
        finally {
            if ($webClient) {
                $webClient.Dispose()
            }
        }
    }

    end {
        Write-Verbose "GeoIP-Abfrage abgeschlossen"
    }
}

📚 Fazit

Das PowerShell-Skript Get-GeoIP demonstriert, wie externe Webdienste in Skripten integriert werden können, um Geolokalisierungsdaten zu ermitteln und gleichzeitig Sicherheitsaspekte durch Blocklistenabfragen zu berücksichtigen. Dank der Unterstützung von Pipeline-Eingaben und einer robusten Fehlerbehandlung ist es ein leistungsstarkes Werkzeug für Administratoren, die Netzwerkinformationen automatisiert auswerten möchten.

Hast du Fragen oder eigene Erweiterungen zu diesem Skript? Dann teile deine Erfahrungen und Anregungen gern in den Kommentaren!

🛡️ ActiveDirectory-Get-UserLoginHistory

Dieses Skript dient dazu sämtliche logins aus den Eventlogs aller Domaincontroller zu lesen. Man kann die Domaincontroller, den Benutzer, die Zeit, sowie die Anzahl der Events restriktieren und das Ganze in eine CSV exportieren. Die Ansicht wird per default als Tabelle dargestellt, lässt sich jedoch mit dem Parameter „-ViewType“ leicht auf „Grid-View“ oder „None“ umstellen. „Non“ heißt hier ohne Tabellen Formatierung.

Das Skript zeigt alle Anmeldungen von einem Domaincontroller, also folgende Anmeldeformen:

  • "Interaktiv"
  • "Netzwerk"
  • "Batch"
  • "Dienst"
  • "Entsperrt"
  • "NetworkCleartext"
  • "NewCredentials"
  • "RemoteInteraktiv"
  • "CachedInteraktiv"
PowerShell
# Standardansicht für einen bestimmten Benutzer
Get-UserLoginHistory -Username "Samaaccountname"

# Interaktive GridView-Anzeige
Get-UserLoginHistory -ViewType Grid

# Auf einzelnem DC mit höherem Limit für mehr Ergebnisse
Get-UserLoginHistory -DomainControllers "domaincontroller" -MaxEvents 5000 -ViewType Full

# Schnellerer Export ohne Anzeige
Get-UserLoginHistory -Hours 48 -ExportToCsv -ViewType None

Das Skript wird durch „Runspace Pools“ parallel verarbeitet. Zur Vereinfachung könnte man hier auch mit Powershell-Jobs arbeiten, falls es einem lieber ist.

PowerShell
function Get-UserLoginHistory {
  <#
  .SYNOPSIS
      Ermittelt den Anmeldeverlauf von Benutzern aus den Sicherheitsprotokollen der Domain-Controller.
  
  .DESCRIPTION
      Diese Funktion durchsucht die Sicherheitsprotokolle aller oder bestimmter Domain-Controller
      nach Anmeldeereignissen (4624, 4625) und zeigt Details zu diesen Anmeldungen in einer übersichtlichen Form an.
      Die Funktion wurde für bessere Performance optimiert.
      
  .PARAMETER Username
      Optional. Der Benutzername, nach dem gesucht werden soll. Kann im Format "Benutzername", 
      "Domain\Benutzername" oder "[email protected]" angegeben werden.
  
  .PARAMETER DomainControllers
      Optional. Eine Liste von Domain-Controllern, die durchsucht werden sollen.
      Wenn nicht angegeben, werden alle Domain-Controller der aktuellen Domäne durchsucht.
  
  .PARAMETER Hours
      Optional. Die Anzahl der Stunden in der Vergangenheit, die durchsucht werden sollen.
      Standard: 24 Stunden
  
  .PARAMETER MaxEvents
      Optional. Die maximale Anzahl von Ereignissen, die pro Domain-Controller abgerufen werden sollen.
      Standard: 1000 Ereignisse
  
  .PARAMETER ExportToCsv
      Optional. Exportiert die Ergebnisse in eine CSV-Datei.
  
  .PARAMETER LogFilePath
      Optional. Pfad zur CSV-Datei, wenn ExportToCsv aktiviert ist.
      Standard: "C:\Logs\UserLoginHistory.csv"
  
  .PARAMETER ViewType
      Optional. Art der Anzeige der Ergebnisse:
      - Table: Kompakte Tabellendarstellung (Standard)
      - Full: Ausführliche Tabellendarstellung
      - Grid: Anzeige im Out-GridView-Dialog (interaktiv)
      - None: Keine Anzeige, nur Rückgabe der Objekte
  
  .EXAMPLE
      Get-UserLoginHistory -Username "mustermann"
      Zeigt die Anmeldungen des Benutzers "mustermann" der letzten 24 Stunden an.
  
  .EXAMPLE
      Get-UserLoginHistory -Username "mustermann" -Hours 48 -MaxEvents 5000 -ViewType Grid
      Zeigt die Anmeldungen des Benutzers "mustermann" der letzten 48 Stunden im GridView-Dialog an.
  
  .EXAMPLE
      Get-UserLoginHistory -DomainControllers "DC01.domain.com" -ExportToCsv
      Zeigt Anmeldungen vom angegebenen Domain-Controller und exportiert sie in eine CSV-Datei.
  
  .NOTES
      Erfordert entsprechende Berechtigungen zum Lesen der Sicherheitsprotokolle der Domain-Controller.
		Dateiname:      Get-UserLoginHistory.ps1
  	Autor:          Ismahil Ahmed
  	Erstelldatum:   12.02.2025
  	Version:        1.6
  #>
  
  [CmdletBinding()]
  param (
      [Parameter(Mandatory=$false, Position=0)]
      [string]$Username,
      
      [Parameter(Mandatory=$false)]
      [string[]]$DomainControllers,
      
      [Parameter(Mandatory=$false)]
      [int]$Hours = 24,
      
      [Parameter(Mandatory=$false)]
      [int]$MaxEvents = 1000,
      
      [Parameter(Mandatory=$false)]
      [switch]$ExportToCsv,
      
      [Parameter(Mandatory=$false)]
      [string]$LogFilePath = "C:\Logs\UserLoginHistory.csv",
      
      [Parameter(Mandatory=$false)]
      [switch]$IncludeFailedLogins = $true,
      
      [Parameter(Mandatory=$false)]
      [ValidateSet("Table", "Full", "Grid", "None")]
      [string]$ViewType = "Table"
  )
  
  begin {
      # Optimierung: Verwende RunspacePool für parallele Verarbeitung
      $runspacePool = [runspacefactory]::CreateRunspacePool(1, [Environment]::ProcessorCount)
      $runspacePool.Open()
      $runspaces = @()
      $scriptBlock = {
          param (
              [string]$DomainController,
              [datetime]$StartTime,
              [datetime]$EndTime,
              [int[]]$EventIDs,
              [int]$MaxEventsToGet,
              [string]$UserFilter,
              [string]$UserDomain,
              [string]$UserPrincipalName
          )
          
          # Ergebnisstruktur
          $results = @{
              DC = $DomainController
              Success = $false
              Events = @()
              Error = $null
              EventCount = 0
              ProcessedCount = 0
              MatchedCount = 0
          }
          
          try {
              # Teste Verbindung
              if (-not (Test-Connection -ComputerName $DomainController -Count 1 -Quiet)) {
                  $results.Error = "Domain-Controller nicht erreichbar"
                  return $results
              }
              
              # Erstelle FilterHashtable
              $filter = @{
                  LogName = 'Security'
                  ID = $EventIDs
                  StartTime = $StartTime
                  EndTime = $EndTime
              }
              
              # Versuche Zugriff zu testen
              try {
                  $null = Get-WinEvent -ComputerName $DomainController -LogName Security -MaxEvents 1 -ErrorAction Stop
              }
              catch {
                  $results.Error = "Zugriff auf Sicherheitsprotokolle nicht möglich: $($_.Exception.Message)"
                  return $results
              }
              
              # Hauptabfrage - OPTIMIERT durch limitierte Feldabfrage
              $events = Get-WinEvent -ComputerName $DomainController -FilterHashtable $filter -MaxEvents $MaxEventsToGet -ErrorAction Stop
              
              $results.EventCount = $events.Count
              $results.Success = $true
              
              # Verarbeite Ereignisse - OPTIMIERT durch selektive Verarbeitung
              foreach ($event in $events) {
                  $results.ProcessedCount++
                  
                  try {
                      $eventXml = [xml]$event.ToXml()
                      $eventData = $eventXml.Event.EventData.Data
                      
                      # Extrahiere Benutzernamen
                      $targetUsername = ($eventData | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
                      $targetDomain = ($eventData | Where-Object { $_.Name -eq 'TargetDomainName' }).'#text'
                      
                      # Benutzernamen vergleichen, wenn ein Benutzerfilter angegeben ist
                      $matchUser = $true
                      if ($UserFilter) {
                          $matchUser = $false
                          
                          # Vereinfachte Matching-Strategien für bessere Performance
                          if ($targetUsername -eq $UserFilter) {
                              $matchUser = $true
                          }
                          elseif ($UserDomain -and ($targetDomain -like "*$UserDomain*") -and ($targetUsername -eq $UserFilter)) {
                              $matchUser = $true
                          }
                          elseif ($UserPrincipalName -and ("$targetUsername@$targetDomain" -like "*$UserPrincipalName*")) {
                              $matchUser = $true
                          }
                      }
                      
                      # Nur Benutzeranmeldungen (keine Systemkonten)
                      if ($matchUser -and 
                          $targetUsername -notmatch '^\$' -and 
                          $targetUsername -ne 'ANONYMOUS LOGON' -and
                          $targetUsername -ne 'SYSTEM' -and
                          $targetUsername -ne 'LOCAL SERVICE' -and
                          $targetUsername -ne 'NETWORK SERVICE') {
                          
                          $results.MatchedCount++
                          
                          # Extrahiere nur die minimal notwendigen Informationen
                          $ipAddress = ($eventData | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
                          if ([string]::IsNullOrEmpty($ipAddress) -or $ipAddress -eq '-') {
                              $ipAddress = "Lokal"
                          }
                          
                          $workstation = ($eventData | Where-Object { $_.Name -eq 'WorkstationName' }).'#text'
                          if ([string]::IsNullOrEmpty($workstation)) {
                              $workstation = "Unbekannt"
                          }
                          
                          $logonType = ($eventData | Where-Object { $_.Name -eq 'LogonType' }).'#text'
                          $logonTypeDesc = switch ($logonType) {
                              2 { "Interaktiv" }
                              3 { "Netzwerk" }
                              4 { "Batch" }
                              5 { "Dienst" }
                              7 { "Entsperrt" }
                              8 { "NetworkCleartext" }
                              9 { "NewCredentials" }
                              10 { "RemoteInteraktiv" }
                              11 { "CachedInteraktiv" }
                              default { "Typ $logonType" }
                          }
                          
                          $results.Events += [PSCustomObject]@{
                              Zeitstempel = $event.TimeCreated
                              DC = $DomainController
                              Benutzername = "$targetDomain\$targetUsername"
                              IPAdresse = $ipAddress
                              Computername = $workstation
                              EreignisID = $event.Id
                              Status = if ($event.Id -eq 4624) { "Erfolgreich" } else { "Fehlgeschlagen" }
                              AnmeldungsTyp = $logonTypeDesc
                          }
                      }
                  }
                  catch {
                      # Ignoriere einzelne Fehler bei der Ereignisverarbeitung
                      continue
                  }
              }
          }
          catch {
              if ($_.Exception.Message -like "*Es wurden keine Ereignisse gefunden*") {
                  $results.Success = $true
                  $results.EventCount = 0
              }
              else {
                  $results.Error = $_.Exception.Message
              }
          }
          
          return $results
      }
      
      # Erstellen des Log-Verzeichnisses, falls es nicht existiert und Export aktiviert ist
      if ($ExportToCsv) {
          $logDir = Split-Path $LogFilePath -Parent
          if (-not (Test-Path $logDir)) {
              New-Item -ItemType Directory -Path $logDir -Force | Out-Null
          }
      }
      
      # Funktion zum Abrufen von Domain-Controller-Namen
      function Get-DCList {
          try {
              # Verwende Get-ADDomainController falls RSAT-Tools installiert sind
              if (Get-Command Get-ADDomainController -ErrorAction SilentlyContinue) {
                  return (Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName)
              } else {
                  # Fallback-Methode
                  $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
                  return $domain.DomainControllers | ForEach-Object { $_.Name }
              }
          } catch {
              Write-Warning "Fehler beim Abrufen der Domain-Controller: $_"
              return $null
          }
      }
      
      # Zeitraum berechnen
      $endTime = Get-Date
      $startTime = $endTime.AddHours(-$Hours)
      
      Write-Host "Suche nach Anmeldeereignissen zwischen $($startTime.ToString('yyyy-MM-dd HH:mm:ss')) und $($endTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Cyan
      
      # Ereignis-IDs festlegen
      if ($IncludeFailedLogins) {
          $eventIDs = @(4624, 4625)  # Erfolgreiche und fehlgeschlagene Anmeldungen
      } else {
          $eventIDs = @(4624)  # Nur erfolgreiche Anmeldungen
      }
      
      # Wenn keine Domain-Controller angegeben wurden, versuche, sie abzurufen
      if (-not $DomainControllers) {
          $DomainControllers = Get-DCList
          if (-not $DomainControllers) {
              Write-Error "Keine Domain-Controller gefunden. Bitte gib explizit Domain-Controller an."
              return
          }
      }
      
      Write-Host "Domain-Controller: $($DomainControllers -join ', ')" -ForegroundColor Cyan
      
      # Benutzernamenformat verarbeiten
      $userSamAccountName = $null
      $userDomain = $null
      $userPrincipalName = $null
      
      if ($Username) {
          Write-Host "Suche nach Benutzername: $Username" -ForegroundColor Cyan
          
          # Prüfe verschiedene Formate
          if ($Username -match '^(.+)\\(.+)$') {
              # Format: DOMAIN\username
              $userDomain = $Matches[1]
              $userSamAccountName = $Matches[2]
              Write-Host "  Format erkannt: DOMAIN\username (Domäne: $userDomain, Benutzername: $userSamAccountName)" -ForegroundColor Yellow
          }
          elseif ($Username -match '^(.+)@(.+)$') {
              # Format: [email protected]
              $userSamAccountName = $Matches[1]
              $userDomain = $Matches[2]
              $userPrincipalName = $Username
              Write-Host "  Format erkannt: [email protected] (Benutzername: $userSamAccountName, Domäne: $userDomain)" -ForegroundColor Yellow
          }
          else {
              # Format: username
              $userSamAccountName = $Username
              Write-Host "  Format erkannt: Nur Benutzername (Benutzername: $userSamAccountName)" -ForegroundColor Yellow
              
              # Versuche, den vollständigen Benutzernamen aus Active Directory zu bekommen
              if (Get-Command Get-ADUser -ErrorAction SilentlyContinue) {
                  try {
                      $adUser = Get-ADUser -Identity $userSamAccountName -Properties UserPrincipalName, DistinguishedName
                      if ($adUser) {
                          $userPrincipalName = $adUser.UserPrincipalName
                          $userDomain = ($adUser.DistinguishedName -split ',DC=')[1]
                          Write-Host "  AD-Benutzerinformationen gefunden: $($adUser.DistinguishedName)" -ForegroundColor Green
                      }
                  }
                  catch {
                      Write-Warning "  Konnte keine AD-Benutzerinformationen für '$userSamAccountName' finden: $_"
                  }
              }
          }
      }
  }
  
  process {
      # Starte die parallele Verarbeitung der Domain-Controller
      Write-Host "`nStarte parallele Abfrage von $($DomainControllers.Count) Domain-Controllern..." -ForegroundColor Yellow
      
      foreach ($dc in $DomainControllers) {
          $runspaceParams = @{
              DomainController = $dc
              StartTime = $startTime
              EndTime = $endTime
              EventIDs = $eventIDs
              MaxEventsToGet = $MaxEvents
              UserFilter = $userSamAccountName
              UserDomain = $userDomain
              UserPrincipalName = $userPrincipalName
          }
          
          $runspace = [powershell]::Create().AddScript($scriptBlock).AddParameters($runspaceParams)
          $runspace.RunspacePool = $runspacePool
          
          $runspaces += [PSCustomObject]@{
              Runspace = $runspace
              Handle = $runspace.BeginInvoke()
              DC = $dc
              Completed = $false
          }
      }
      
      # Erstelle eine ArrayList für die Ergebnisse
      $allResults = New-Object System.Collections.ArrayList
      
      # Überwache den Fortschritt der Runspaces
      $completed = 0
      $total = $runspaces.Count
      
      while ($runspaces | Where-Object { -not $_.Completed }) {
          foreach ($item in $runspaces | Where-Object { -not $_.Completed }) {
              if ($item.Handle.IsCompleted) {
                  $results = $item.Runspace.EndInvoke($item.Handle)
                  $item.Completed = $true
                  $completed++
                  
                  # Status anzeigen
                  if ($results.Success) {
                      Write-Host "Domain-Controller $($item.DC): $($results.MatchedCount) von $($results.EventCount) Ereignissen gefunden" -ForegroundColor Green
                  } else {
                      Write-Host "Domain-Controller $($item.DC): Fehler - $($results.Error)" -ForegroundColor Red
                  }
                  
                  # Ergebnisse zur Gesamtliste hinzufügen
                  if ($results.Events.Count -gt 0) {
                      [void]$allResults.AddRange($results.Events)
                  }
                  
                  # Runspace bereinigen
                  $item.Runspace.Dispose()
              }
          }
          
          # Fortschrittsanzeige aktualisieren
          Write-Progress -Activity "Abfrage von Domain-Controllern" -Status "$completed von $total abgeschlossen" -PercentComplete (($completed / $total) * 100)
          
          # Kurze Pause für CPU-Entlastung
          Start-Sleep -Milliseconds 100
      }
      
      Write-Progress -Activity "Abfrage von Domain-Controllern" -Completed
      
      # Sortiere die Ergebnisse nach Zeitstempel (neueste zuerst)
      $sortedResults = $allResults | Sort-Object -Property Zeitstempel -Descending
      
      # Gib Statistiken aus
      $resultCount = $sortedResults.Count
      Write-Host "`nGefundene Anmeldungen: $resultCount" -ForegroundColor Cyan
      
      # Exportiere zu CSV, falls gewünscht
      if ($ExportToCsv -and $resultCount -gt 0) {
          $sortedResults | Export-Csv -Path $LogFilePath -NoTypeInformation -Encoding UTF8
          Write-Host "Ergebnisse wurden in $LogFilePath gespeichert." -ForegroundColor Green
      }
      
      # Zeige die Ergebnisse je nach ViewType an
      if ($resultCount -gt 0) {
          switch ($ViewType) {
              "Table" {
                  # Kompakte Tabellendarstellung mit ausgewählten Spalten
                  $displayCount = [Math]::Min(20, $resultCount)
                  Write-Host "Zeige die neuesten $displayCount Anmeldungen in Tabellenform:" -ForegroundColor Cyan
                  
                  # Format-Table mit benutzerdefinierter Formatierung für bessere Lesbarkeit
                  $sortedResults | Select-Object -First $displayCount | 
                      Format-Table -Property @{
                          Label = "Zeitpunkt"; 
                          Expression = { $_.Zeitstempel.ToString("yyyy-MM-dd HH:mm:ss") };
                          Width = 19
                      },
                      @{
                          Label = "Benutzer"; 
                          Expression = { ($_.Benutzername -split '\\')[-1] };
                          Width = 15
                      },
                      @{
                          Label = "Status"; 
                          Expression = { 
                              if ($_.Status -eq "Erfolgreich") { 
                                  "✓" 
                              } else { 
                                  "✗" 
                              }
                          };
                          Width = 6
                      },
                      @{
                          Label = "Anmeldungstyp"; 
                          Expression = { $_.AnmeldungsTyp };
                          Width = 15
                      },
                      @{
                          Label = "IP-Adresse"; 
                          Expression = { $_.IPAdresse };
                          Width = 15
                      },
                      @{
                          Label = "Computer"; 
                          Expression = { $_.Computername };
                          Width = 15
                      },
                      @{
                          Label = "Domain-Controller"; 
                          Expression = { ($_.DC -split '\.')[0] };
                          Width = 15
                      }
                  
                  if ($resultCount -gt 20) {
                      Write-Host "... und $($resultCount - 20) weitere Anmeldungen." -ForegroundColor Yellow
                      Write-Host "Vollständige Anzeige mit 'Get-UserLoginHistory -ViewType Grid' oder 'Get-UserLoginHistory -ViewType Full'" -ForegroundColor Yellow
                  }
                  
                  # Bei Table-Ansicht keine Objekte zurückgeben
                  return
              }
              "Full" {
                  # Ausführliche Tabellendarstellung mit allen Spalten
                  Write-Host "Zeige die Anmeldungen in ausführlicher Tabellenform:" -ForegroundColor Cyan
                  $sortedResults | Format-Table -AutoSize
                  
                  # Bei Full-Ansicht keine Objekte zurückgeben
                  return
              }
              "Grid" {
                  # GridView-Darstellung (interaktiv)
                  Write-Host "Öffne interaktives GridView-Fenster mit den Ergebnissen..." -ForegroundColor Cyan
                  $sortedResults | Out-GridView -Title "Benutzeranmeldungen ($resultCount Einträge)" -PassThru
                  
                  # Bei Grid-Ansicht keine Objekte zurückgeben
                  return
              }
              "None" {
                  # Keine Anzeige, nur Rückgabe der Objekte
                  Write-Host "Keine Anzeige ausgewählt. Die Ergebnisse sind als Objekte verfügbar." -ForegroundColor Yellow
              }
          }
      }
      else {
          Write-Host "Keine Anmeldungen gefunden, die den Filterkriterien entsprechen." -ForegroundColor Yellow
      }
      
      # Gib die Ergebnisse nur zurück, wenn ViewType "None" ist oder keine Ergebnisse gefunden wurden
      # Bei anderen ViewTypes wurde die Rückgabe bereits mit "return" beendet
      return $sortedResults
  }
  
  end {
      # Runspace-Pool schließen
      $runspacePool.Close()
      $runspacePool.Dispose()
  }
}

🛡️ ActiveDirectory-PowerShell-Modul

Der Praxis-Guide für Admins

Mit dem Modul ActiveDirectory in PowerShell steht Administratoren ein mächtiges Werkzeug zur Verfügung, um Active-Directory-Objekte wie Benutzer, Gruppen, Computer, Organisationseinheiten (OUs) und Gruppenrichtlinien schnell, skriptgesteuert und zuverlässig zu verwalten.

Dieser Beitrag gibt Ihnen einen erweiterten Überblick über die wichtigsten Cmdlets, den produktiven Einsatz in der Domäne und bewährte Best Practices.

✅ Voraussetzungen

  • Windows Server mit Active Directory-Rolle oder ein Windows-Client mit den RSAT-Tools.
  • PowerShell-Version ≥ 5.1 (oder Windows PowerShell 7+ mit Import-Module).
  • Import des Moduls (meist automatisch verfügbar auf AD-Servern):
PowerShell
Import-Module ActiveDirectory

🧩 Modulüberblick und Hilfe anzeigen

PowerShell
Get-Module -ListAvailable ActiveDirectory
Get-Command -Module ActiveDirectory
Get-Help Get-ADUser -Detailed  # Beispiel um die Funktionsweise vom Command "Get-ADUser" zu verstehen

👨‍💼 Top 10 Cmdlets für Domänen-Administratoren

1. Benutzer suchen

PowerShell
Get-ADUser -Filter * -SearchBase "OU=Benutzer,DC=testdomain,DC=local"
Get-ADUser -Filter * -SearchBase "OU=Benutzer,DC=testdomain,DC=local" | ft

Die Ausgabe der Benutzer sieht wie nachfolgend aus und kann zur Übersicht noch formatiert und angepasst werden.

PowerShell
DistinguishedName : CN=Nachname\, Vorname,OU=Benutzer, DC=testdomain,DC=local
Enabled           : True
GivenName         : Vorname
Name              : Nachname, Vorname
ObjectClass       : user
ObjectGUID        : eb3867da-5c0b-4633-b19e-e366700d1032
SamAccountName    : Anmeldename
SID               : S-1-5-21-2875105853-2476587791-2145578461-4692
Surname           :
UserPrincipalName : testuser@testdomain.local

1. Benutzer mit bestimmten Attributen finden

PowerShell
Get-ADUser -Filter {Enabled -eq $true -and Department -eq "IT"} -Properties Department

3. Neuen Benutzer erstellen

PowerShell
New-ADUser -Name "Max Mustermann" `
  -GivenName "Max" `
  -Surname "Mustermann" `
  -SamAccountName "mmustermann" `
  -UserPrincipalName "[email protected]" `
  -Path "OU=Benutzer,DC=firma,DC=local" `
  -AccountPassword (ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force) `
  -Enabled $true

4. Passwort zurücksetzen

PowerShell
Set-ADAccountPassword -Identity mmustermann -NewPassword (ConvertTo-SecureString "NeuesPass123!" -AsPlainText -Force) -Reset
Unlock-ADAccount -Identity mmustermann

5. Benutzer zu einer Gruppe hinzufügen

PowerShell
Add-ADGroupMember -Identity "IT-Admins" -Members mmustermann

6. Benutzer aus Gruppe entfernen

PowerShell
Remove-ADGroupMember -Identity "IT-Admins" -Members mmustermann -Confirm:$false

7. Gruppenmitglieder anzeigen

PowerShell
Get-ADComputer -Filter * -SearchBase "OU=Clients,DC=firma,DC=local"

8. Computerobjekte finden

PowerShell
Get-ADComputer -Filter * -SearchBase "OU=Clients,DC=firma,DC=local"

9. Letztes Anmeldedatum von Benutzern abfragen

PowerShell
Get-ADUser -Filter * -Properties LastLogonDate | Select Name, LastLogonDate

Nachfolgend zwei Beispiele mit Ausgabe:

PowerShell
Get-ADUser testuser -Properties LastLogonDate | FT -Property Name, LastLogonDate -A

Name    LastLogonDate
----    -------------
testuser  11.03.2025 09:11:49

Get-ADUser testuser -Properties LastLogonDate

DistinguishedName : CN=testuser,OU=Test,OU=Benutzer,OU=domainOU,DC=testdomain,DC=de
Enabled           : True
GivenName         : testuser
LastLogonDate     : 11.03.2025 09:11:49
Name              : testuser  
ObjectClass       : user
ObjectGUID        : adffdd6e-02a3-4651-8606-1a7ad1923cfe
SamAccountName    : testuser
SID               : S-1-5-21-2875105854-2476583791-2145578461-1438
Surname           : test
UserPrincipalName : testuser@testdomain.de

10. Gruppen verschachtelt anzeigen (rekursiv)

PowerShell
Get-ADGroupMember -Identity "Domain Admins" -Recursive

Alternativen – Active Directory Service Interface oder auch adsi

PowerShell
$searcher = New-Object DirectoryServices.DirectorySearcher
$searcher.SearchRoot = [ADSI]"LDAP://RootDSE" # Die root Domain
$searcher.Filter = "(&(objectCategory=User)(samAccountName=USERNAME))" # Userobjecte werden gesucht mit dem bestimmten anmeldenamen
$result = $searcher.FindOne() # FindAll()

Das Ganze kann man auch als simple Funktion aufbauen und so direkt nach dem User suchen.

PowerShell
function Get-ADUser {
    param([string]$UserName)
    return (New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=User)(samAccountName=$UserName))")).FindOne()
}

🧠 Best Practices für den produktiven Einsatz

Um das ActiveDirectory-Modul effizient und sicher zu nutzen, empfehlen sich folgende Vorgehensweisen:

  1. Sichere Passwortverwaltung: Verwende Secure Strings für Passwörter – auch in Skripten.
  2. Effiziente Filterung: Setze den -Filter {} Parameter ein, anstatt Where-Object zu nutzen, um die Performance zu optimieren.
  3. Reporting: Generiere regelmäßige Berichte und exportiere Daten mit Export-Csv:
  4. Testen mit -WhatIf: Führe neue Skripte zunächst mit -WhatIf oder -Confirm:$false aus, um unbeabsichtigte Änderungen zu vermeiden.
  5. Überwachung inaktiver Konten: Analysiere LastLogonDate Werte, um inaktive Benutzerkonten zu identifizieren und zu verwalten.

📚 Weitere Ressourcen

Vertiefende Informationen und aktuelle Entwicklungen findest du in diesen Quellen:

🏁 Fazit

Das ActiveDirectory-Modul in PowerShell ist ein unverzichtbares Tool für die Verwaltung komplexer Domänenumgebungen. Mit den vorgestellten Cmdlets, erweiterten Anwendungsfällen und Best Practices kannst du administrative Aufgaben automatisieren, detaillierte Reports erstellen und schnell auf Veränderungen reagieren. Richtig eingesetzt verbessert es die Effizienz und Sicherheit deiner IT-Infrastruktur erheblich.

Dies ist der Auftakt zu einer fortlaufenden Serie, in der wir noch tiefer in fortgeschrittene Techniken, praxisnahe Skripte und optimierte Abläufe in Windows-Domänenumgebungen eintauchen. Hast du Fragen oder möchtest du deine eigenen Erfahrungen teilen? Dann diskutiere gern in den Kommentaren mit!

2️⃣. PS – Get-MappedDrives

Das Skript dient dazu die Laufwerke remote, mittels RemoteRegistry, von einem Client in der Domäne auszulesen .

PowerShell
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $computer)

Das Grundskript ist recht simple aufgebaut. Es wird Remote auf dem Client unter HKEY_USERS nachgesehen. Diese Hive enthält Benutzerprofile (SIDs) und damit auch deren gemappte Laufwerke.

PowerShell
$reg.GetSubKeyNames() | ? {$_ -match '\d{4,5}$'}

Der Bereich filtert nur SIDs (Security Identifier), die auf eine Ziffernfolge enden, z. B. S-1-5-21-...-1001. Das sind in der Regel die aktiven Benutzerprofile (statt z. B. .DEFAULT oder Classes).

🚀 Die erste Version vom Skript

PowerShell
function Get-MappedDrives {
    param (
        $computer = $env:COMPUTERNAME
    )

    $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $computer)
    $reg.GetSubKeyNames() | ? {$_ -match '\d{4,5}$'} | % {
        $sid = $_
        $reg.OpenSubKey("$sid\Network").GetSubKeyNames() | % {
            New-Object psobject -Property @{
                Computer = $computer
                User = ([System.Security.Principal.SecurityIdentifier]($sid)).Translate([System.Security.Principal.NTAccount]).Value
                DriveLetter = $_
                Map = $reg.OpenSubKey("$sid\Network\$_").GetValue('RemotePath')
            }
        }
    }
}

📖Legende zur Funktion Get-MappedDrives

PowerShell
- 'param ($computer = $env:COMPUTERNAME)' -- Standardmäßig wird der lokale Computer verwendet, kann aber überschrieben werden
- '[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $computer)' -- Öffnet die Registry-Hive HKEY_USERS des angegebenen Computers (lokal oder remote)
- '?' -- ist ein Alias für Where-Object. Es filtert also die Liste.
- '%' -- ist ein Alias für foreach
- '$_' -- ist das aktuelle Element in der Pipeline (also ein Schlüsselname wie S-1-5-  21-...-1000).
- "-match '\d{4,5}$'" --  prüft per Regulärem Ausdruck (Regex), ob das Ende des Strings aus genau 4 oder 5 Ziffern besteht
- '$reg.GetSubKeyNames()' = Listet alle SIDs (Benutzer und Systemkonten), die in der Registry vertreten sind
- '$reg.OpenSubKey("$sid\Network")' -- Greift auf Registry-Schlüssel zu, in denen Windows Netzlaufwerk-Zuordnungen speichert
- '.GetSubKeyNames()' -- Holt die Laufwerksbuchstaben, z. B. Z, P, etc.
- '([SecurityIdentifier]$sid).Translate([NTAccount])' -- Wandelt die SID in einen Benutzernamen um (z. B. DOMAIN\Max.Mustermann)
- ".GetValue('RemotePath')" -- Liest den Pfad des gemappten Netzlaufwerks aus, z. B. \\server\freigabe
- 'New-Object psobject -Property @{ ... }' -- Baut ein Objekt mit den Informationen: Computer, Benutzer, Laufwerksbuchstabe, Pfad


regex:
- '\d' -- eine Ziffer (0–9)
- '{4,5}' -- genau 4 oder 5 Ziffern
- '$' -- Am ende des Strings

Da das Skript noch sehr rudimentär ist, sollte man dieses noch erweitern und Fehlerhandling, sowie Performance verbessern. Die Perfomance kann man alleine dadurch verbessern, dass wir vorher einen Ping an den Host senden, bevor wir versuchen Remote auf die Registry zuzugreifen.

🔍 Die Komplette Funktion

PowerShell
function Get-MappedDrives {
    param (
        [string]$computer = $env:COMPUTERNAME
    )

    # Prüfe ob der Host erreichbar ist, bevor weitermachen
    try {
        if (-not (Test-Connection -ComputerName $computer -Count 1 -Quiet -ErrorAction Stop)) {
            Write-Warning "Computer '$computer' ist nicht erreichbar."
            return
        }
    }
    catch {
        Write-Warning "Verbindungsfehler zu '$computer': $_"
        return
    }

    # Sammle zuerst alle Daten in einer Hashtable, gruppiert nach Benutzern
    $userDrives = @{}

    try {
        $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $computer)
        
        $reg.GetSubKeyNames() | ? {$_ -match '\d{4,5}$'} | % {
            $sid = $_
            $networkKey = $reg.OpenSubKey("$sid\Network")
            
            if ($networkKey) {
                try {
                    # Wandle SID in Benutzername um - nur einmal pro Benutzer
                    $userName = ([System.Security.Principal.SecurityIdentifier]($sid)).Translate([System.Security.Principal.NTAccount]).Value
                    
                    # Erstelle einen Eintrag für diesen Benutzer, falls noch nicht vorhanden
                    if (-not $userDrives.ContainsKey($userName)) {
                        $userDrives[$userName] = @()
                    }
                    
                    $networkKey.GetSubKeyNames() | % {
                        $driveLetter = $_
                        $driveKey = $networkKey.OpenSubKey($driveLetter)
                        
                        if ($driveKey) {
                            $driveInfo = New-Object psobject -Property @{
                                Computer = $computer
                                User = $userName
                                DriveLetter = $driveLetter
                                Map = $driveKey.GetValue('RemotePath')
                            }
                            
                            # Füge die Laufwerksinformation zum Array dieses Benutzers hinzu
                            $userDrives[$userName] += $driveInfo
                            
                            # Ressourcen freigeben
                            $driveKey.Close()
                        }
                    }
                }
                catch {
                    Write-Verbose "Fehler bei Verarbeitung von SID '$sid': $_"
                }
                
                # Ressourcen freigeben
                $networkKey.Close()
            }
        }
        
        # Ausgabe der nach Benutzern gruppierten Daten
        $userDrives.GetEnumerator() | % {
            $userName = $_.Key
            Write-Output "Netzlaufwerke für Benutzer: $userName"
            Write-Output "----------------------------------------"
            $_.Value | Format-Table -AutoSize
            Write-Output ""
        }
    }
    catch {
        Write-Error "Fehler beim Zugriff auf den Computer '$computer': $_"
    }
    finally {
        if ($reg) { $reg.Close() }
    }
}

Da man im Best Practice die Verwendung von Aliasen vermeidet. Hier noch mal das Skript mit volle Cmdlet-Schreibweise. Und natürlich das Kommentarbasiertes Help-System um die Bedienung für neue Benutzer zu erleichtern.

📃Das Fertige Skript

PowerShell
function Get-MappedDrives {
<#
.SYNOPSIS
    Retrieves mapped network drives for all users on a remote computer.

.DESCRIPTION
    Connects to the remote registry of a specified computer to enumerate network drives
    mapped under all user SIDs found in the HKU hive.

.PARAMETER ComputerName
    The target computer name. Defaults to the local computer.

.EXAMPLE
    Get-MappedDrives -ComputerName SRV-01

.NOTES
    Requires the Remote Registry service to be running on the target machine.
    Dateiname:      Get-MappedDrives.ps1
    Autor:          Ismahil Ahmed
    Erstelldatum:   19.02.2022
    Version:        1.0
    

#>

    [CmdletBinding()]  
    param (
        [Parameter(Position = 0)]
        [string]$ComputerName = $env:COMPUTERNAME  
    )

    try {
        if (-not (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop)) {
            Write-Warning "Computer '$ComputerName' is not reachable."
            return
        }
    }
    catch {
        Write-Warning "Connection error to '$ComputerName': $_"
        return
    }

    $userDrives = @{}

    try {
        $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $ComputerName)

        $reg.GetSubKeyNames() | Where-Object { $_ -match '\d{4,5}$' } | ForEach-Object {  
            $sid = $_
            $networkKey = $reg.OpenSubKey("$sid\Network")

            if ($networkKey) {
                try {
                    $userName = ([System.Security.Principal.SecurityIdentifier]$sid).Translate([System.Security.Principal.NTAccount]).Value

                    if (-not $userDrives.ContainsKey($userName)) {
                        $userDrives[$userName] = @()
                    }

                    $networkKey.GetSubKeyNames() | ForEach-Object {
                        $driveLetter = $_
                        $driveKey = $networkKey.OpenSubKey($driveLetter)

                        if ($driveKey) {
                            $driveInfo = [PSCustomObject]@{
                                Computer    = $ComputerName
                                User        = $userName
                                DriveLetter = $driveLetter
                                Map         = $driveKey.GetValue('RemotePath')
                            }

                            $userDrives[$userName] += $driveInfo
                            $driveKey.Close()
                        }
                    }
                }
                catch {
                    Write-Verbose "Error processing SID '$sid': $_"  
                }
                $networkKey.Close()
            }
        }

        $userDrives.GetEnumerator() | ForEach-Object {
            $userName = $_.Key
            Write-Output "Mapped drives for user: $userName"
            Write-Output "----------------------------------------"
            $_.Value | Format-Table -AutoSize
            Write-Output ""
        }
    }
    catch {
        Write-Error "Error accessing computer '$ComputerName': $_"
    }
    finally {
        if ($reg) { $reg.Close() }
    }
}

1️⃣. PS – Apex One Agent Deploy Script

„Install-TrendMicro.ps1“ – Das Skript um den Apex One Security Agent von Trend Micro zu deployen!

In diesem Beitrag stelle ich dir ein Powershell-Skript vor, das für die automatische Installation und Überwachung eines Trend Micro OfficeScan-Updates in einer Windows-Domänenumgebung entwickelt wurde. Das Skript führt verschiedene Aufgaben aus – von der Logrotation über das Auslesen von Versionsinformationen bis hin zur Startsteuerung relevanter Dienste. Es richtet sich an fortgeschrittene Administratoren, die Prozesse automatisieren und gleichzeitig eine lückenlose Dokumentation sicherstellen möchten.


🔍 Überblick über das Skript

Es handelt sich im Grunde nur um zwei Funktionen, eine dient zum loggen und die andere Funktion „Install-TM“ beinhaltet den Kernteil. Im Kernteil werden zusätzlich die Trend Micro Dienste lokal auf dem Client geprüft und ggf. gestartet. Den Teil mit den Diensten könnte man noch in eine eigene Funktion überführen.

✅ Write-Log
✅ Install-TM #TM Dienste werden zusätzlich geprüft

PowerShell
param (
    # Das Logverzeichnis – standardmäßig C:\Temp\log
    [string]$LogFilePath = "C:\Temp\log",
    # $Server ist verpflichtend (z.B. "Exampleserver.domain.de")
    [Parameter(Mandatory = $true)]
    [string]$Server,
    # Der Share wird aus dem Servernamen gebildet
	#[string]$ServerShare = '\\$Server\ofcscan',
    [string]$ServerShare = ('\\{0}\ofcscan' -f $Server),
    [string]$FileName = 'OFCSCAN.INI'
)

# Erstelle den ServerFilePath
$ServerFilePath = Join-Path $ServerShare "ofcscan.ini"

# Erstelle das Logverzeichnis, falls es nicht existiert
if (-not (Test-Path $LogFilePath)) {
    New-Item -Path $LogFilePath -ItemType Directory -Force | Out-Null
}

# Definiere den Pfad der Logdatei und das Archiv (als ZIP) im gleichen Verzeichnis
$LogFile = Join-Path $LogFilePath "log.txt"

Das Skript beginnt mit der Definition von Parametern, die folgende Informationen beinhalten:

  1. LogFilePath: Das Verzeichnis, in dem Logdateien gespeichert werden (Standard: C:\Temp\log).
  2. Server: Der Name des Servers (z. B. Exampleserver.domain.de), von dem die Konfigurationsdatei geladen wird – dieser Parameter ist verpflichtend.
  3. ServerShare: Der Pfad zum freigegebenen Ordner auf dem Server, in dem sich die zentrale Konfigurationsdatei (OFCSCAN.INI) befindet.
  4. FileName: Der Name der Konfigurationsdatei, standardmäßig OFCSCAN.INI.

Anschließend wird ein Pfad zur Server-Konfigurationsdatei erstellt und das Logverzeichnis erzeugt, falls es noch nicht existiert. Alle wichtigen Aktionen werden in einer Logdatei dokumentiert – inklusive einer automatischen Logrotation, wenn die Logdatei 5 MB überschreitet.


🚀 Teil 1: Logdatei und Logrotation

PowerShell
# Das Archiv erhält einen Zeitstempel im Dateinamen; standardmäßig wird hier eine ZIP erstellt
$DefaultArchiveZipFilePattern = "log_archive_{0:yyyyMMdd_HHmmss}.zip"
$ArchiveZipFile = Join-Path $LogFilePath ([string]::Format($DefaultArchiveZipFilePattern, (Get-Date)))

function Write-Log {
    param (
        [string]$Message,
        [string]$LogFile
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Out-File -FilePath $LogFile -Append
}

Die Funktion Write-Log fügt jedem Logeintrag einen Zeitstempel hinzu. Ist die Logdatei zu groß, wird sie temporär umbenannt, komprimiert und archiviert – so bleibt der Logordner übersichtlich und alte Daten werden gesichert.


🚀 Teil 2: Lokale und Server-Konfiguration auslesen

PowerShell
function Install-TM {
    param (
        [string]$LogFile,
        [string]$LogFilePath,
        [string]$ArchiveZipFile,
        [string]$ServerFilePath,
        [string]$FileName
    )
    # Log-Rotation prüfen und durchführen
    if (Test-Path $LogFile) {
        $logSize = (Get-Item $LogFile).Length
        if ($logSize -gt 5MB) {
            $tempArchive = "$LogFile.tmp"
            Rename-Item -Path $LogFile -NewName $tempArchive -Force
            try {
                Compress-Archive -Path $tempArchive -DestinationPath $ArchiveZipFile -Force
                Remove-Item $tempArchive -Force
                Write-Log -Message "Archivierte Logdatei komprimiert zu: $ArchiveZipFile" -LogFile $LogFile
            }
            catch {
                Write-Log -Message "Fehler beim Komprimieren der archivierten Logdatei: $_" -LogFile $LogFile
            }
        }
    }
    Write-Log -Message "TM Skript läuft" -LogFile $LogFile

    # Lokale Pfade definieren (z.B. OfficeScan Client und Security Agent)
    $localPaths = @(
        Join-Path $env:"ProgramFiles(x86)" "Trend Micro\OfficeScan Client",
        Join-Path $env:"ProgramFiles(x86)" "Trend Micro\Security Agent"
    )

    # Lokale INI-Datei einlesen
    $lokalFile = $null
    foreach ($path in $localPaths) {
        $iniPath = Join-Path -Path $path -ChildPath $FileName
        if (Test-Path $iniPath) {
            try {
                $lokalFile = Get-Content -Path $iniPath -ErrorAction Stop
                if ($lokalFile) { break }
            }
            catch {
                Write-Log -Message "Fehler beim Lesen der lokalen INI-Datei in $iniPath: $_" -LogFile $LogFile
            }
        }
    }
    if (-not $lokalFile) {
        Write-Log -Message "Lokale INI-Datei nicht gefunden." -LogFile $LogFile
        return
    }

    # Lokale Version auslesen
    try {
        $localVersionString = ($lokalFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $localVersionParts = $localVersionString.Split(".")
        $localVersion = @{
            Version    = [int]$localVersionParts[0]
            SubVersion = [int]$localVersionParts[1]
            BuildNum   = [int]( ($lokalFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim() )
        }
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der lokalen Version: $_" -LogFile $LogFile
        return
    }

    # Server INI-Datei einlesen
    if (-not (Test-Path $ServerFilePath)) {
        Write-Log -Message "Server INI-Datei nicht gefunden: $ServerFilePath" -LogFile $LogFile
        return
    }
    try {
        $serverFile = Get-Content -Path $ServerFilePath -ErrorAction Stop
    }
    catch {
        Write-Log -Message "Fehler beim Lesen der Server INI-Datei: $_" -LogFile $LogFile
        return
    }

    # Server Version auslesen
    try {
        $serverVersionString = ($serverFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $serverVersionParts = $serverVersionString.Split(".")
        $serverVersion = @{
            Version    = [int]$serverVersionParts[0]
            SubVersion = [int]$serverVersionParts[1]
            BuildNum   = [int]( ($serverFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim() )
        }
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der Server Version: $_" -LogFile $LogFile
        return
    }

    # Installationsentscheidung treffen
    $clientPath = Join-Path $env:"ProgramFiles(x86)" "Trend Micro\OfficeScan Client"
    $startInstall = $false
    if (-not (Test-Path $clientPath)) {
        Write-Log -Message "OfficeScan Client nicht gefunden. Installation wird gestartet." -LogFile $LogFile
        $startInstall = $true
    }
    else {
        if ($localVersion.Version -lt $serverVersion.Version) {
            $startInstall = $true
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and $localVersion.SubVersion -lt $serverVersion.SubVersion) {
            $startInstall = $true
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and $localVersion.SubVersion -eq $serverVersion.SubVersion -and $localVersion.BuildNum -lt $serverVersion.BuildNum) {
            $startInstall = $true
        }
    }

Im nächsten Schritt sucht das Skript in zwei vordefinierten Verzeichnissen nach der lokalen OFCSCAN.INI Datei. Wird die Datei gefunden, wird der darin enthaltene Versionsstring (sowohl PccNT_Version als auch PccNT_BuildNum) ausgelesen und in eine Hashtable überführt. Anschließend erfolgt das Auslesen der entsprechenden Werte aus der zentralen Konfigurationsdatei, die über einen UNC-Pfad erreichbar ist.

Durch den Vergleich der Versionsnummern wird ermittelt, ob eine Aktualisierung erforderlich ist:

  1. Ist die lokale Hauptversion kleiner als die Server-Version, wird ein Update initiiert.
  2. Ist die Hauptversion gleich, aber die Nebenversion kleiner, erfolgt ebenfalls ein Update.
  3. Bei gleichen Haupt- und Nebenversionen wird zusätzlich die Buildnummer verglichen.

🚀 Teil 3: Update-Entscheidung und Dienststeuerung

PowerShell
 if ($startInstall) {
        Write-Log -Message "Starte Installation von Trend Micro. Lokale Version: $($localVersion.Version).$($localVersion.SubVersion).$($localVersion.BuildNum) - Server Version: $($serverVersion.Version).$($serverVersion.SubVersion).$($serverVersion.BuildNum)" -LogFile $LogFile
        try {
            Start-Process -FilePath "\\$Server\ofcscan\Autopcc.exe"
        }
        catch {
            Write-Log -Message "Fehler beim Starten der Installation: $_" -LogFile $LogFile
        }
    }
    else {
        Write-Log -Message "Keine Installation notwendig. Lokale Version ist aktuell." -LogFile $LogFile
    }

    # Dienste überprüfen und starten
    $services = @(
        'ntrtscan',
        'tmccsf',
        'tmlisten',
        'TmPfw',
        'TmWSCSvc'
    )

    foreach ($service in $services) {
        try {
            $svc = Get-Service -Name $service -ErrorAction Stop
            if ($svc.Status -ne 'Running') {
                Write-Log -Message "Starte Dienst: $($svc.DisplayName)" -LogFile $LogFile
                Start-Service -Name $service
            }
            else {
                Write-Log -Message "Dienst läuft bereits: $($svc.DisplayName)" -LogFile $LogFile
            }
        }
        catch {
            Write-Log -Message "Fehler beim Verarbeiten des Dienstes $service: $_" -LogFile $LogFile
        }
    }
}

Wenn festgestellt wird, dass die lokale Version veraltet ist, startet das Skript das Update, indem es die Anwendung Autopcc.exe vom zentralen Server ausführt. Zusätzlich prüft das Skript eine Liste von wichtigen Trend Micro-Diensten (wie ntrtscan, tmccsf und weitere) – sollten diese nicht laufen, werden sie automatisch gestartet.


📌 Das Ergebnis

PowerShell
param (
    # Das Logverzeichnis – standardmäßig C:\Temp\log
    [string]$LogFilePath = "C:\Temp\log",
    
    # $Server ist verpflichtend (z.B. "Exampleserver.domain.de")
    [Parameter(Mandatory = $true)]
    [string]$Server,
    
    # Der Share wird aus dem Servernamen gebildet
    [string]$ServerShare = ('\\{0}\ofcscan' -f $Server),
    
    # Name der INI-Datei
    [string]$FileName = 'OFCSCAN.INI'
)

# Erstelle den ServerFilePath
$ServerFilePath = Join-Path -Path $ServerShare -ChildPath "ofcscan.ini"

# Erstelle das Logverzeichnis, falls es nicht existiert
if (-not (Test-Path -Path $LogFilePath)) {
    New-Item -Path $LogFilePath -ItemType Directory -Force | Out-Null
}

# Definiere den Pfad der Logdatei und das Archiv (als ZIP) im gleichen Verzeichnis
$LogFile = Join-Path -Path $LogFilePath -ChildPath "log.txt"
# Das Archiv erhält einen Zeitstempel im Dateinamen; standardmäßig wird hier eine ZIP erstellt
$DefaultArchiveZipFilePattern = "log_archive_{0:yyyyMMdd_HHmmss}.zip"
$ArchiveZipFile = Join-Path -Path $LogFilePath -ChildPath ([string]::Format($DefaultArchiveZipFilePattern, (Get-Date)))

function Write-Log {
    param (
        [string]$Message,
        [string]$LogFile
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Out-File -FilePath $LogFile -Append
}

function Install-TM {
    param (
        [string]$LogFile,
        [string]$LogFilePath,
        [string]$ArchiveZipFile,
        [string]$ServerFilePath,
        [string]$FileName
    )
    # Log-Rotation prüfen und durchführen
    if (Test-Path -Path $LogFile) {
        $logSize = (Get-Item -Path $LogFile).Length
        if ($logSize -gt 5MB) {
            $tempArchive = "$LogFile.tmp"
            Rename-Item -Path $LogFile -NewName $tempArchive -Force
            try {
                Compress-Archive -Path $tempArchive -DestinationPath $ArchiveZipFile -Force
                Remove-Item -Path $tempArchive -Force
                Write-Log -Message "Archivierte Logdatei komprimiert zu: $ArchiveZipFile" -LogFile $LogFile
            }
            catch {
                Write-Log -Message "Fehler beim Komprimieren der archivierten Logdatei: $($_.Exception.Message)" -LogFile $LogFile
            }
        }
    }
    
    Write-Log -Message "TM Skript läuft" -LogFile $LogFile

    # Lokale Pfade definieren (z.B. OfficeScan Client und Security Agent)
    $localPaths = @(
        (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\OfficeScan Client"),
        (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\Security Agent")
    )

    # Lokale INI-Datei einlesen
    $lokalFile = $null
    foreach ($path in $localPaths) {
        $iniPath = Join-Path -Path $path -ChildPath $FileName
        if (Test-Path -Path $iniPath) {
            try {
                $lokalFile = Get-Content -Path $iniPath -ErrorAction Stop
                if ($lokalFile) { break }
            }
            catch {
                Write-Log -Message "Fehler beim Lesen der lokalen INI-Datei in $iniPath : $($_.Exception.Message)" -LogFile $LogFile
            }
        }
    }
    
    if (-not $lokalFile) {
        Write-Log -Message "Lokale INI-Datei nicht gefunden." -LogFile $LogFile
        return
    }

    # Lokale Version auslesen
    try {
        $localVersionString = ($lokalFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $localVersionParts = $localVersionString.Split(".")
        $localVersion = @{
            Version    = [int]$localVersionParts[0]
            SubVersion = [int]$localVersionParts[1]
            BuildNum   = [int](($lokalFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim())
        }
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der lokalen Version: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Server INI-Datei einlesen
    if (-not (Test-Path -Path $ServerFilePath)) {
        Write-Log -Message "Server INI-Datei nicht gefunden: $ServerFilePath" -LogFile $LogFile
        return
    }
    
    try {
        $serverFile = Get-Content -Path $ServerFilePath -ErrorAction Stop
    }
    catch {
        Write-Log -Message "Fehler beim Lesen der Server INI-Datei: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Server Version auslesen
    try {
        $serverVersionString = ($serverFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $serverVersionParts = $serverVersionString.Split(".")
        $serverVersion = @{
            Version    = [int]$serverVersionParts[0]
            SubVersion = [int]$serverVersionParts[1]
            BuildNum   = [int](($serverFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim())
        }
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der Server Version: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Installationsentscheidung treffen
    $clientPath = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\OfficeScan Client"
    $startInstall = $false
    
    if (-not (Test-Path -Path $clientPath)) {
        Write-Log -Message "OfficeScan Client nicht gefunden. Installation wird gestartet." -LogFile $LogFile
        $startInstall = $true
    }
    else {
        if ($localVersion.Version -lt $serverVersion.Version) {
            $startInstall = $true
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and $localVersion.SubVersion -lt $serverVersion.SubVersion) {
            $startInstall = $true
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and $localVersion.SubVersion -eq $serverVersion.SubVersion -and $localVersion.BuildNum -lt $serverVersion.BuildNum) {
            $startInstall = $true
        }
    }

    if ($startInstall) {
        Write-Log -Message "Starte Installation von Trend Micro. Lokale Version: $($localVersion.Version).$($localVersion.SubVersion).$($localVersion.BuildNum) - Server Version: $($serverVersion.Version).$($serverVersion.SubVersion).$($serverVersion.BuildNum)" -LogFile $LogFile
        try {
            Start-Process -FilePath "\\$Server\ofcscan\Autopcc.exe"
        }
        catch {
            Write-Log -Message "Fehler beim Starten der Installation: $($_.Exception.Message)" -LogFile $LogFile
        }
    }
    else {
        Write-Log -Message "Keine Installation notwendig. Lokale Version ist aktuell." -LogFile $LogFile
    }

    # Dienste überprüfen und starten
    $services = @(
        'ntrtscan',
        'tmccsf',
        'tmlisten',
        'TmPfw',
        'TmWSCSvc'
    )
    
    foreach ($service in $services) {
        try {
            $svc = Get-Service -Name $service -ErrorAction Stop
            if ($svc.Status -ne 'Running') {
                Write-Log -Message "Starte Dienst: $($svc.DisplayName)" -LogFile $LogFile
                Start-Service -Name $service
            }
            else {
                Write-Log -Message "Dienst läuft bereits: $($svc.DisplayName)" -LogFile $LogFile
            }
        }
        catch {
            Write-Log -Message "Fehler beim Verarbeiten des Dienstes $service : $($_.Exception.Message)" -LogFile $LogFile
        }
    }
}

Install-TM -LogFile $LogFile -LogFilePath $LogFilePath -ArchiveZipFile $ArchiveZipFile -ServerFilePath $ServerFilePath -FileName $FileName

Dieses Skript bietet eine umfassende Lösung zur Automatisierung von OfficeScanAgent-Updates in einer Windows-Domänenumgebung. Durch die Kombination von Logrotation, Versionsvergleich und automatisierter Dienststeuerung können Administratoren sicherstellen, dass ihre Systeme stets auf dem neuesten Stand und optimal geschützt sind.

Wenn man das Ganze noch etwas aufhübscht, die comment-based Hilfe hinzufügt und das logging erweitert, erhält man ein gutes Skript und leicht verständliches Skript.

PowerShell
<#
.SYNOPSIS
 Installiert oder aktualisiert den Trend Micro OfficeScan Client.

.DESCRIPTION
 Dieses Skript vergleicht die lokal installierte Version des Trend Micro OfficeScan Clients
 mit der Version auf dem angegebenen Server. Wenn die lokale Version älter ist oder der Client
 nicht installiert ist, wird automatisch eine Installation oder ein Update durchgeführt.
 Zusätzlich werden relevante Trend Micro Dienste überprüft und bei Bedarf gestartet.

.PARAMETER LogFilePath
 Pfad zum Logverzeichnis. Wenn nicht angegeben, wird standardmäßig "C:\Temp\log" verwendet.

.PARAMETER Server
 Name des Servers, auf dem die Trend Micro OfficeScan-Installationsdateien liegen.
 Dieser Parameter ist obligatorisch.

.PARAMETER ServerShare
 Netzwerkpfad zum OfficeScan-Share auf dem Server. Wird automatisch aus dem Servernamen generiert.
 Standardwert: \\[Server]\ofcscan

.PARAMETER FileName
 Name der INI-Datei, die für den Versionsvergleich verwendet wird.
 Standardwert: OFCSCAN.INI

.EXAMPLE
 .\Install-TrendMicro.ps1 -Server "Exampleserver.domain.de"
 Führt das Skript mit dem angegebenen Server und Standardwerten für die anderen Parameter aus.

.EXAMPLE
 .\Install-TrendMicro.ps1 -Server "Exampleserver.domain.de" -LogFilePath "D:\Logs\TrendMicro"
 Führt das Skript mit angepasstem Logverzeichnis aus.

.NOTES
 Dateiname:      Install-TrendMicro.ps1
 Autor:          Ismahil Ahmed
 Erstelldatum:   04.04.2023
 Version:        1.1 
 Änderungsprotokoll:
 1.0 - Ursprüngliche Version
 1.1 - Skriptverbesserungen und Comment-Help hinzugefügt
#>

[CmdletBinding()]
param (
    # Das Logverzeichnis – standardmäßig C:\Temp\log
    [string]$LogFilePath = "C:\Temp\log", 
    
    # $Server ist verpflichtend (z.B. "Exampleserver.domain.de")
    [Parameter(Mandatory = $true)]
    [string]$Server, 
    
    # Der Share wird aus dem Servernamen gebildet
    [string]$ServerShare = ('\\{0}\ofcscan' -f $Server), 
    
    # Name der INI-Datei
    [string]$FileName = 'OFCSCAN.INI'
)

# Erstelle den ServerFilePath
$ServerFilePath = Join-Path -Path $ServerShare -ChildPath $FileName

# Erstelle das Logverzeichnis, falls es nicht existiert
if (-not (Test-Path -Path $LogFilePath)) {
    New-Item -Path $LogFilePath -ItemType Directory -Force | Out-Null
}

# Definiere den Pfad der Logdatei und das Archiv (als ZIP) im gleichen Verzeichnis
$LogFile = Join-Path -Path $LogFilePath -ChildPath "log.txt"
# Das Archiv erhält einen Zeitstempel im Dateinamen; standardmäßig wird hier eine ZIP erstellt
$DefaultArchiveZipFilePattern = "log_archive_{0:yyyyMMdd_HHmmss}.zip"
$ArchiveZipFile = Join-Path -Path $LogFilePath -ChildPath ([string]::Format($DefaultArchiveZipFilePattern, (Get-Date)))

function Write-Log {
    <#
    .SYNOPSIS
    Schreibt Nachrichten mit Zeitstempel in eine Logdatei. 
    
    .PARAMETER Message
    Die zu protokollierende Nachricht. 
    
    .PARAMETER LogFile
    Pfad zur Logdatei.
    #>
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message, 
        
        [Parameter(Mandatory = $true)]
        [string]$LogFile
    ) 
    
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Out-File -FilePath $LogFile -Append
}

function Install-TM {
    <#
    .SYNOPSIS
    Hauptfunktion zur Installation und Aktualisierung von Trend Micro OfficeScan. 
    
    .DESCRIPTION
    Prüft die lokale Version gegen die Server-Version und installiert oder aktualisiert den Client bei Bedarf.
    Startet außerdem relevante Dienste. 
    
    .PARAMETER LogFile
    Pfad zur Logdatei. 
    
    .PARAMETER LogFilePath
    Pfad zum Logverzeichnis. 
    
    .PARAMETER ArchiveZipFile
    Pfad zur archivierten Logdatei (ZIP). 
    
    .PARAMETER ServerFilePath
    Vollständiger Pfad zur INI-Datei auf dem Server. 
    
    .PARAMETER FileName
    Name der INI-Datei.
    #>
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogFile, 
        
        [Parameter(Mandatory = $true)]
        [string]$LogFilePath, 
        
        [Parameter(Mandatory = $true)]
        [string]$ArchiveZipFile, 
        
        [Parameter(Mandatory = $true)]
        [string]$ServerFilePath, 
        
        [Parameter(Mandatory = $true)]
        [string]$FileName
    ) 
    
    # Log-Rotation prüfen und durchführen
    if (Test-Path -Path $LogFile) {
        $logSize = (Get-Item -Path $LogFile).Length
        if ($logSize -gt 5MB) {
            $tempArchive = "$LogFile.tmp"
            Rename-Item -Path $LogFile -NewName $tempArchive -Force
            try {
                Compress-Archive -Path $tempArchive -DestinationPath $ArchiveZipFile -Force
                Remove-Item -Path $tempArchive -Force
                Write-Log -Message "Archivierte Logdatei komprimiert zu: $ArchiveZipFile" -LogFile $LogFile
            }
            catch {
                Write-Log -Message "Fehler beim Komprimieren der archivierten Logdatei: $($_.Exception.Message)" -LogFile $LogFile
            }
        }
    } 
    
    Write-Log -Message "TM Skript läuft" -LogFile $LogFile

    # Hilfsfunktion zur Installation
    function Start-Installation {
        Write-Log -Message "Starte Installation/Update von Trend Micro." -LogFile $LogFile
        try {
            $installPath = "\\$Server\ofcscan\Autopcc.exe"
            Write-Log -Message "Starte Installer von: $installPath" -LogFile $LogFile 
            
            $process = Start-Process -FilePath $installPath -PassThru -Wait -ErrorAction Stop 
            
            if ($process.ExitCode -eq 0) {
                Write-Log -Message "Installation/Update erfolgreich abgeschlossen." -LogFile $LogFile
            }
            else {
                Write-Log -Message "Installation/Update mit Exit-Code $($process.ExitCode) beendet." -LogFile $LogFile
            }
        }
        catch {
            Write-Log -Message "Fehler beim Starten der Installation: $($_.Exception.Message)" -LogFile $LogFile
        }
    } 
    
    # Hilfsfunktion zum Starten der Dienste
    function Start-TrendMicroServices {
        $services = @(
            'ntrtscan',
            'tmccsf',
            'tmlisten',
            'TmPfw',
            'TmWSCSvc'
        ) 
        
        Write-Log -Message "Überprüfe Trend Micro Dienste..." -LogFile $LogFile 
        
        foreach ($service in $services) {
            try {
                $svc = Get-Service -Name $service -ErrorAction Stop
                if ($svc.Status -ne 'Running') {
                    Write-Log -Message "Starte Dienst: $($svc.DisplayName)" -LogFile $LogFile
                    Start-Service -Name $service -ErrorAction Stop 
                    
                    # Warte kurz und prüfe, ob der Dienst gestartet wurde
                    Start-Sleep -Seconds 2
                    $svc = Get-Service -Name $service
                    if ($svc.Status -eq 'Running') {
                        Write-Log -Message "Dienst $($svc.DisplayName) erfolgreich gestartet." -LogFile $LogFile
                    }
                    else {
                        Write-Log -Message "Dienst $($svc.DisplayName) konnte nicht gestartet werden." -LogFile $LogFile
                    }
                }
                else {
                    Write-Log -Message "Dienst läuft bereits: $($svc.DisplayName)" -LogFile $LogFile
                }
            }
            catch {
                Write-Log -Message "Fehler beim Verarbeiten des Dienstes $service : $($_.Exception.Message)" -LogFile $LogFile
            }
        } 
        
        Write-Log -Message "Dienste-Überprüfung abgeschlossen." -LogFile $LogFile
    }

    # Lokale Pfade definieren (z.B. OfficeScan Client und Security Agent)
    $path1 = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\OfficeScan Client"
    $path2 = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\Security Agent"
    $localPaths = @($path1, $path2)

    # Lokale INI-Datei einlesen
    $lokalFile = $null
    foreach ($path in $localPaths) {
        $iniPath = Join-Path -Path $path -ChildPath $FileName
        if (Test-Path -Path $iniPath) {
            try {
                $lokalFile = Get-Content -Path $iniPath -ErrorAction Stop
                if ($lokalFile) { 
                    Write-Log -Message "Lokale INI-Datei gefunden in: $iniPath" -LogFile $LogFile
                    break 
                }
            }
            catch {
                Write-Log -Message "Fehler beim Lesen der lokalen INI-Datei in $iniPath : $($_.Exception.Message)" -LogFile $LogFile
            }
        }
    } 
    
    if (-not $lokalFile) {
        Write-Log -Message "Lokale INI-Datei nicht gefunden. Installation wird gestartet." -LogFile $LogFile
        Start-Installation
        return
    }

    # Lokale Version auslesen
    try {
        $localVersionString = ($lokalFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $localVersionParts = $localVersionString.Split(".")
        $localVersion = @{
            Version    = [int]$localVersionParts[0]
            SubVersion = [int]$localVersionParts[1]
            BuildNum   = [int](($lokalFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim())
        } 
        
        Write-Log -Message "Lokale Version: $($localVersion.Version).$($localVersion.SubVersion) Build $($localVersion.BuildNum)" -LogFile $LogFile
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der lokalen Version: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Server INI-Datei einlesen
    if (-not (Test-Path -Path $ServerFilePath)) {
        Write-Log -Message "Server INI-Datei nicht gefunden: $ServerFilePath" -LogFile $LogFile
        return
    } 
    
    try {
        $serverFile = Get-Content -Path $ServerFilePath -ErrorAction Stop
    }
    catch {
        Write-Log -Message "Fehler beim Lesen der Server INI-Datei: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Server Version auslesen
    try {
        $serverVersionString = ($serverFile | Select-String -Pattern "PccNT_Version").Line.Split("=")[1].Trim()
        $serverVersionParts = $serverVersionString.Split(".")
        $serverVersion = @{
            Version    = [int]$serverVersionParts[0]
            SubVersion = [int]$serverVersionParts[1]
            BuildNum   = [int](($serverFile | Select-String -Pattern "PccNT_BuildNum").Line.Split("=")[1].Trim())
        } 
        
        Write-Log -Message "Server Version: $($serverVersion.Version).$($serverVersion.SubVersion) Build $($serverVersion.BuildNum)" -LogFile $LogFile
    }
    catch {
        Write-Log -Message "Fehler beim Auslesen der Server Version: $($_.Exception.Message)" -LogFile $LogFile
        return
    }

    # Installationsentscheidung treffen
    $clientPath = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Trend Micro\OfficeScan Client"
    $startInstall = $false 
    
    if (-not (Test-Path -Path $clientPath)) {
        Write-Log -Message "OfficeScan Client nicht gefunden. Installation wird gestartet." -LogFile $LogFile
        $startInstall = $true
    }
    else {
        if ($localVersion.Version -lt $serverVersion.Version) {
            $startInstall = $true
            Write-Log -Message "Hauptversion ist veraltet. Update erforderlich." -LogFile $LogFile
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and $localVersion.SubVersion -lt $serverVersion.SubVersion) {
            $startInstall = $true
            Write-Log -Message "Unterversion ist veraltet. Update erforderlich." -LogFile $LogFile
        }
        elseif ($localVersion.Version -eq $serverVersion.Version -and 
            $localVersion.SubVersion -eq $serverVersion.SubVersion -and 
            $localVersion.BuildNum -lt $serverVersion.BuildNum) {
            $startInstall = $true
            Write-Log -Message "Build-Nummer ist veraltet. Update erforderlich." -LogFile $LogFile
        }
    }

    # Installation oder Update starten
    if ($startInstall) {
        Start-Installation
    }
    else {
        Write-Log -Message "Keine Installation notwendig. Lokale Version ist aktuell." -LogFile $LogFile
    }

    # Dienste überprüfen und starten
    Start-TrendMicroServices 
}

# Hauptskript ausführen
try {
    Write-Log -Message "=== Skript gestartet ===" -LogFile $LogFile
    Install-TM -LogFile $LogFile -LogFilePath $LogFilePath -ArchiveZipFile $ArchiveZipFile -ServerFilePath $ServerFilePath -FileName $FileName
    Write-Log -Message "=== Skript erfolgreich beendet ===" -LogFile $LogFile
}
catch {
    Write-Log -Message "=== Skript mit Fehler beendet: $($_.Exception.Message) ===" -LogFile $LogFile
}

Hast du Fragen oder Anregungen zu diesem Ansatz? Teile deine Erfahrungen und Tipps gern in den Kommentaren mit!

0️⃣. PS – Client Cleanup Script

In diesem Beitrag möchte ich euch zeigen, wie man mit einem simplen Skript, ein wenig Ordnung in die Domäne bringt. Das Skript beinhaltet mehrere Funktionen zum löschen und bereinigen von Verzeichnissen, Logs, Updates etc.

Folgende Funktionen sind enthalten:

✅ cleanup-mgr
✅ delete-folders
✅ clear-event-logs
✅ cleanup-windows-update
✅ update-group-policy
❌ cleanup-user-profiles
✅ cleanup-temp-files
✅ cleanup-dns-cache
❌ uninstall-softwarepackage
❌ get-installedsoftware

u1. Funktion zum Loggen von Nachrichten in eine Datei

PowerShell
function Log-Message {
    param (
        [string]$Message,
        [string]$Type = "INFO"
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $formattedMessage = "$timestamp [$Type] $Message"
    Write-Output $formattedMessage | Out-File -FilePath "C:\temp\log\system_cleanup.log" -Append
    # Optional: Protokoll auch zur Konsole ausgeben
    Write-Output $formattedMessage
}

Zweck:
Diese Funktion ist das Herzstück der Protokollierung im Skript. Jeder Schritt der Systembereinigung wird hierüber dokumentiert.

Funktionsweise:

  • Parameter:
    • $Message: Der eigentliche Text der Meldung.
    • $Type: Der Typ der Nachricht (Standard: „INFO“, aber auch „ERROR“ oder „WARNING“ möglich).
  • Ablauf:
    • Es wird ein Zeitstempel erstellt, der dann zusammen mit dem Nachrichtentext und dem Typ formatiert wird.
    • Der formattierte Text wird sowohl in eine Log-Datei geschrieben als auch (optional) in die Konsole ausgegeben.
  • Nutzen:
    • Durch die zentrale Log-Funktion können spätere Fehler oder unerwartete Systemzustände leichter nachvollzogen und analysiert werden.

u2. Funktion zum Abrufen installierter Software

PowerShell
function get-installedsoftware {
    param (
        [string]$Name,
        [string]$Version = $null
    )
    try {
        Log-Message "Suche nach installierter Software: Name=$Name, Version=$Version"
        $package = Get-Package -Provider Programs -IncludeWindowsInstaller -Name $Name
        if ($Version) {
            $package = $package | Where-Object { $_.Version -eq $Version }
        }
        Log-Message "Gefundene Pakete: $($package.Count)"
        return $package
    } catch {
        Log-Message "Fehler beim Abrufen installierter Software: $_" "ERROR"
    }
}

Zweck:
Mit dieser Funktion können installierte Softwarepakete abgefragt werden. Das ist hilfreich, wenn man z. B. den Status oder die Version einer bestimmten Software prüfen möchte.

Funktionsweise:

  • Parameter:
    • $Name: Der Name der gesuchten Software.
    • $Version: Optional, um gezielt nach einer bestimmten Version zu suchen.
  • Ablauf:
    • Vor der Suche wird eine Log-Meldung ausgegeben.
    • Mittels Get-Package werden Pakete abgefragt, und – falls ein Versionsfilter gesetzt wurde – zusätzlich gefiltert.
    • Abschließend wird die Anzahl gefundener Pakete protokolliert und das Ergebnis zurückgegeben.
  • Nutzen:
    • Ermöglicht ein schnelles und automatisiertes Inventar der installierten Software.

u3. Funktion zum Deinstallieren eines Softwarepakets

PowerShell
function uninstall-softwarepackage {
    param (
        [Microsoft.PackageManagement.Packaging.SoftwareIdentity]$Package
    )
    try {
        Log-Message "Versuche Deinstallation des Pakets: $($Package.Name)"
        if ($Package.ProviderName -eq "Programs") {
            $xml = [xml]$Package.SwidTagText
            $uninstallString = $xml.SoftwareIdentity.Meta.UninstallString
            if ($uninstallString -match 'msiexec.* /i.*') {
                $uninst = (($uninstallString -split ' ')[1] -replace '/I', '/X') + ' /qb'
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            } elseif (Test-Path -LiteralPath $uninstallString) {
                Start-Process -FilePath $uninstallString -Wait -NoNewWindow -ErrorAction Stop
            } elseif ($uninstallString -match 'msiexec.* /x.*') {
                $uninst = ($uninstallString -split ' ')[1] + ' /qb'
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            }
            Log-Message "Deinstallation erfolgreich: $($Package.Name)"
        } else {
            $Package | Uninstall-Package -ErrorAction Stop
            Log-Message "Deinstallation erfolgreich über Provider: $($Package.Name)"
        }
    } catch {
        Log-Message "Fehler bei der Deinstallation des Pakets: $_" "ERROR"
    }
}

Zweck:
Diese Funktion dient der Deinstallation eines Softwarepakets. Sie ist so aufgebaut, dass sie unterschiedliche Deinstallationsszenarien abdeckt.

Funktionsweise:

  • Parameter:
    • $Package: Ein Objekt, das Informationen über das Softwarepaket enthält.
  • Ablauf:
    • Zunächst wird über die Log-Funktion vermerkt, welches Paket deinstalliert werden soll.
    • Wird festgestellt, dass der Provider „Programs“ ist, wird der XML-Inhalt des Pakets ausgelesen, um den richtigen Deinstallationsbefehl zu ermitteln.
    • Unterschiedliche Ansätze (z. B. Ersetzen von /I zu /X bei MSI-Installationen oder direkter Aufruf eines externen Deinstallationsprogramms) werden durchlaufen.
    • Ist der Provider ein anderer, wird der generische Befehl Uninstall-Package genutzt.
    • Bei Erfolg oder im Fehlerfall wird eine entsprechende Log-Meldung ausgegeben.
  • Nutzen:
    • Sorgt für eine saubere Entfernung von Software und differenziert zwischen verschiedenen Deinstallationsmethoden.

u4. Funktion zur Ausführung von Cleanmgr mit Voreinstellungen

PowerShell
function cleanup-mgr {
    try {
        Log-Message "Starte cleanup-mgr"
        $regList = @(
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Active Setup Temp Folders"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\BranchCache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\D3D Shader Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Delivery Optimization Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Diagnostic Data Viewer database files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Downloaded Program Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Internet Cache Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Language Pack"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Old ChkDsk Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Recycle Bin"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\RetailDemo Offline Content"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Setup Log Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error memory dump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error minidump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Temporary Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Thumbnail Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Update Cleanup"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\User file versions"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Defender"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Error Reporting Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Upgrade Log Files"; Key = 2 }
        )
        $keyname = 'StateFlags1337'
        foreach($key in $regList) {
            New-ItemProperty -Path $key.Pfad -Name $keyname -Value $key.Key -Type DWORD -Force | Out-Null
        }
        Start-Process "$env:SystemRoot\System32\cleanmgr.exe" -ArgumentList "/D C /verylowdisk /sagerun:1337" -WindowStyle Hidden
        Log-Message "cleanup-mgr erfolgreich abgeschlossen"
    } catch {
        Log-Message "Fehler in cleanup-mgr: $_" "ERROR"
    }
}

Zweck:
Diese Funktion nutzt den Windows „Cleanmgr“ (Datenträgerbereinigung) zur Entfernung von überflüssigen Systemdateien und Caches.

Funktionsweise:

  • Ablauf:
    • Es wird ein Array mit Registry-Pfaden definiert, die auf unterschiedliche Cache-Bereiche des Systems verweisen (z. B. Internet Cache, temporäre Dateien usw.).
    • Für jeden Eintrag wird ein spezieller Registry-Wert (StateFlags1337) mit einem definierten Wert (0, 2 etc.) gesetzt – dies signalisiert Cleanmgr, welche Bereiche bereinigt werden sollen.
    • Anschließend wird Cleanmgr mit Parametern gestartet, die den Bereinigungsprozess automatisieren (z. B. "/sagerun:1337" für die zuvor definierten Einstellungen).
    • Alle Schritte werden geloggt.
  • Nutzen:
    • Ermöglicht eine automatisierte und konsistente Bereinigung des Systems, um Speicherplatz freizugeben und die Leistung zu optimieren.

u5. Funktion zum Löschen bestimmter Ordner

PowerShell
function delete-folders {
    try {
        Log-Message "Starte delete-folders"
        $chocoPath = 'C:\ProgramData\choco-cache\'
        $adobeARM = 'C:\ProgramData\Adobe\ARM\'
        if ((Get-ChildItem -Path  $chocoPath -ErrorAction SilentlyContinue | Measure-Object -ErrorAction SilentlyContinue  -Property Length -Sum).Sum /1MB -ge 100) {
            Remove-Item -Path "$chocoPath*" -Recurse -Force
            Log-Message "Chocolatey Cache gelöscht"
        }
        if (Test-Path -Path $adobeARM) {
            Remove-Item -Path "$adobeARM*" -Recurse -Force
            Log-Message "Adobe ARM-Daten gelöscht"
        }
    } catch {
        Log-Message "Fehler in delete-folders: $_" "ERROR"
    }
}

Zweck:
Mit dieser Funktion werden gezielt bestimmte Ordner gelöscht – hier etwa der Chocolatey-Cache und Adobe ARM-Daten.

Funktionsweise:

  • Ablauf:
    • Zunächst wird der Pfad des Chocolatey-Caches überprüft. Liegt dessen Größe über einem definierten Schwellenwert (hier 100 MB), wird der gesamte Inhalt gelöscht.
    • Ebenso wird der Adobe ARM-Pfad überprüft und – falls vorhanden – geleert.
    • Beide Aktionen werden protokolliert.
  • Nutzen:
    • Reduziert den unnötig belegten Speicherplatz durch veraltete oder temporäre Cache-Daten.

u6. Funktion zur Bereinigung von Event-Logs

PowerShell
function clear-event-logs {
    try {
        Log-Message "Starte clear-event-logs"
        $threeMonthsAgo = (Get-Date).AddMonths(-4)

        # Liste aller Event-Logs
        $eventLogs = Get-WinEvent -ListLog *  | Where { $_.RecordCount -gt 0}

        foreach ($log in $eventLogs) {
            $logName = $log.LogName

            try {
                # Prüfen, wann das letzte Ereignis erstellt wurde
                $latestEvent = Get-WinEvent -LogName $logName -MaxEvents 1 -ErrorAction Stop
                if ($latestEvent) {
                    $latestEventTime = $latestEvent.TimeCreated
                    if ($latestEventTime -lt $threeMonthsAgo) {
                        # Log löschen, wenn das letzte Ereignis älter als 3 Monate ist
                        Clear-EventLog -LogName $logName
                        Log-Message "Event-Log $logName erfolgreich bereinigt"
                    }
                }
            } catch {
                # Wenn keine Ereignisse gefunden werden, überspringen
                if ($_.Exception -is [System.Management.Automation.RuntimeException]) {
                    Log-Message "Keine Ereignisse im Log $logName gefunden oder Fehler beim Abrufen der Ereignisse: $_" "WARNING"
                } else {
                    Log-Message "Fehler beim Abrufen von Ereignissen im Log $logName : $_" "ERROR"
                }
            }
        }
    } catch {
        Log-Message "Fehler in clear-event-logs: $_" "ERROR"
    }
}

Zweck:
Event-Logs können über längere Zeiträume wachsen und unnötig Speicher belegen. Diese Funktion löscht jene Logs, die über einen definierten Zeitraum (hier: älter als ca. 4 Monate) nicht aktualisiert wurden.

Funktionsweise:

  • Ablauf:
    • Es wird ein Schwellenwert-Datum berechnet, das angibt, ab wann Logs als „alt“ gelten.
    • Mit Get-WinEvent werden alle Event-Logs abgefragt, die Einträge enthalten.
    • Für jedes Log wird das Datum des letzten Eintrags überprüft.
    • Sind die Logs „veraltet“, wird Clear-EventLog aufgerufen, um den Inhalt zu löschen.
    • Besondere Fehlerfälle (z. B. wenn keine Ereignisse vorhanden sind) werden separat behandelt und geloggt.
  • Nutzen:
    • Sorgt dafür, dass alte und nicht mehr relevante Log-Daten entfernt werden – ein wichtiger Schritt zur Systemwartung und zur Einsparung von Festplattenspeicher.

u7. Funktion zur Bereinigung des Windows Update Caches

PowerShell
function cleanup-windows-update {
    try {
        Log-Message "Starte cleanup-windows-update"
        net stop wuauserv
        Remove-Item -LiteralPath "C:\Windows\SoftwareDistribution\*" -Recurse -Force
        net start wuauserv
        Log-Message "Windows Update Cache erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-windows-update: $_" "ERROR"
    }
}

Zweck:
Diese Funktion räumt den Windows Update-Cache auf, der sich mit der Zeit ansammeln und potenziell zu fehlerhaften Update-Vorgängen führen kann.

Funktionsweise:

  • Ablauf:
    • Der Windows Update Service (wuauserv) wird gestoppt, um sicherzustellen, dass keine Dateien in Benutzung sind.
    • Der Inhalt des Ordners C:\Windows\SoftwareDistribution\* wird gelöscht.
    • Anschließend wird der Dienst wieder gestartet.
    • Alle Schritte werden im Log dokumentiert.
  • Nutzen:
    • Hilft bei der Behebung von Update-Problemen und stellt sicher, dass der Update-Cache stets auf dem neuesten Stand ist.

u8. Funktion zur Aktualisierung von Gruppenrichtlinien

PowerShell
function update-group-policy {
    try {
        Log-Message "Starte update-group-policy"
        gpupdate /force
        Log-Message "Gruppenrichtlinien erfolgreich aktualisiert"
    } catch {
        Log-Message "Fehler in update-group-policy: $_" "ERROR"
    }
}

Zweck:
Mit dieser Funktion werden Gruppenrichtlinien sofort aktualisiert, sodass alle Änderungen unverzüglich wirksam werden.

Funktionsweise:

  • Ablauf:
    • Der Befehl gpupdate /force wird ausgeführt, um alle Gruppenrichtlinien zu aktualisieren.
    • Erfolgreiche Aktualisierungen sowie etwaige Fehler werden geloggt.
  • Nutzen:
    • Stellt sicher, dass alle aktuellen Richtlinien (z. B. Sicherheits- und Konfigurationsvorgaben) auf dem System aktiv sind.

u9. Funktion zur Bereinigung alter Benutzerprofile

PowerShell
function cleanup-user-profiles {
    try {
        Log-Message "Starte cleanup-user-profiles"
        $profilesToDelete = Get-WmiObject -Class Win32_UserProfile | Where-Object {
            $_.Special -eq $false -and $_.LastUseTime -lt ((Get-Date).AddDays(-6000)).ToFileTime()
        }

        foreach ($profile in $profilesToDelete) {
            Remove-WmiObject -InputObject $profile
            Log-Message "Benutzerprofil gelöscht: $($profile.LocalPath)"
        }

        Log-Message "Alte Benutzerprofile erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-user-profiles: $_" "ERROR"
    }
}

Zweck:
Diese Funktion entfernt alte Benutzerprofile, die seit langer Zeit nicht mehr verwendet wurden – etwa von ehemaligen Mitarbeitern oder inaktiven Nutzern.

Funktionsweise:

  • Ablauf:
    • Mithilfe von Get-WmiObject und der Klasse Win32_UserProfile werden alle Benutzerprofile abgefragt.
    • Es werden nur Profile berücksichtigt, die nicht als „Special“ markiert sind und deren letzter Nutzungszeitpunkt (LastUseTime) vor einem bestimmten Datum liegt.
    • Jedes gefundene Profil wird mittels Remove-WmiObject gelöscht.
    • Jeder Löschvorgang wird protokolliert.
  • Nutzen:
    • Bereinigt das System von überflüssigen Benutzerprofilen, was zu einer verbesserten Systemleistung und einer geringeren Fragmentierung der Benutzerverzeichnisse führt.

u1u0. Funktion zur Bereinigung temporärer Dateien

PowerShell
function cleanup-temp-files {
    try {
        Log-Message "Starte cleanup-temp-files"
        Remove-Item -LiteralPath "C:\Windows\Temp\*" -Recurse -Force
        Remove-Item -LiteralPath "$env:TEMP\*" -Recurse -Force
        Log-Message "Temporäre Dateien erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-temp-files: $_" "ERROR"
    }
}

Zweck:
Diese Funktion löscht temporäre Dateien, die sich sowohl im Windows-Temp-Ordner als auch im benutzerspezifischen Temp-Verzeichnis angesammelt haben.

Funktionsweise:

  • Ablauf:
    • Es werden die Inhalte des Systemordners C:\Windows\Temp\* sowie des benutzerspezifischen Temp-Ordners ($env:TEMP\*) rekursiv gelöscht.
    • Auch hier sorgt die Fehlerbehandlung dafür, dass eventuelle Probleme im Löschvorgang geloggt werden.
  • Nutzen:
    • Durch das Entfernen von temporären Dateien wird unnötig belegter Speicher freigegeben und das System bleibt „sauber“, was wiederum die Performance verbessert.

u1u1. Funktion zur Bereinigung des DNS-Caches

PowerShell
function cleanup-dns-cache {
    try {
        Log-Message "Starte cleanup-dns-cache"
        Clear-DnsClientCache
        Log-Message "DNS-Cache erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-dns-cache: $_" "ERROR"
    }
}

Zweck:
Mit dieser Funktion wird der DNS-Cache geleert, um veraltete DNS-Einträge zu entfernen und so mögliche Netzwerkprobleme (z. B. falsche Namensauflösung) zu vermeiden.

Funktionsweise:

  • Ablauf:
    • Der Befehl Clear-DnsClientCache wird ausgeführt, um alle zwischengespeicherten DNS-Einträge zu löschen.
    • Erfolg oder Fehler werden ebenfalls protokolliert.
  • Nutzen:
    • Hilft bei der Behebung von Netzwerkproblemen, wenn DNS-Einträge aktualisiert wurden, aber der alte Cache weiterhin verwendet wird.

u1u2. Hauptfunktion zur Reinigung des Systems

PowerShell
function cleanup {
    try {
        Log-Message "Starte Systembereinigung"
        cleanup-mgr
        delete-folders
        clear-event-logs
        cleanup-windows-update
        update-group-policy
        #cleanup-user-profiles
        cleanup-temp-files
        cleanup-dns-cache
        Log-Message "Systembereinigung erfolgreich abgeschlossen"
    } catch {
        Log-Message "Fehler in cleanup: $_" "ERROR"
    }
}
# Ausführung der Systembereinigung
cleanup

Zweck:
Diese zentrale Funktion orchestriert die Ausführung aller einzelnen Bereinigungsroutinen, um eine umfassende Systemwartung in einem Durchgang zu ermöglichen.

Funktionsweise:

  • Ablauf:
    • Zu Beginn wird eine Log-Meldung ausgegeben, die den Start der Systembereinigung signalisiert.
    • Nacheinander werden alle vorher definierten Funktionen (wie cleanup-mgr, delete-folders, clear-event-logs, cleanup-windows-update, update-group-policy, cleanup-temp-files und cleanup-dns-cache) aufgerufen.
    • (Hinweis: Die Funktion cleanup-user-profiles ist auskommentiert – je nach Bedarf kann sie aktiviert werden.)
    • Abschließend wird ein Log-Eintrag erstellt, der den erfolgreichen Abschluss der Bereinigung bestätigt.
  • Nutzen:
    • Sorgt für einen strukturierten und automatisierten Ablauf, der alle wichtigen Wartungsaufgaben zusammenfasst.

Das Komplette Skript sieht dann wie folgt aus:

PowerShell
# Funktion zum Loggen von Nachrichten in eine Datei
function Log-Message {
    param (
        [string]$Message,
        [string]$Type = "INFO"
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $formattedMessage = "$timestamp [$Type] $Message"
    Write-Output $formattedMessage | Out-File -FilePath "C:\temp\log\system_cleanup.log" -Append
    # Optional: Protokoll auch zur Konsole ausgeben
    Write-Output $formattedMessage
}

# Funktion zum Abrufen installierter Software
function get-installedsoftware {
    param (
        [string]$Name,
        [string]$Version = $null
    )
    try {
        Log-Message "Suche nach installierter Software: Name=$Name, Version=$Version"
        $package = Get-Package -Provider Programs -IncludeWindowsInstaller -Name $Name
        if ($Version) {
            $package = $package | Where-Object { $_.Version -eq $Version }
        }
        Log-Message "Gefundene Pakete: $($package.Count)"
        return $package
    } catch {
        Log-Message "Fehler beim Abrufen installierter Software: $_" "ERROR"
    }
}

# Funktion zum Deinstallieren eines Softwarepakets
function uninstall-softwarepackage {
    param (
        [Microsoft.PackageManagement.Packaging.SoftwareIdentity]$Package
    )
    try {
        Log-Message "Versuche Deinstallation des Pakets: $($Package.Name)"
        if ($Package.ProviderName -eq "Programs") {
            $xml = [xml]$Package.SwidTagText
            $uninstallString = $xml.SoftwareIdentity.Meta.UninstallString
            if ($uninstallString -match 'msiexec.* /i.*') {
                $uninst = (($uninstallString -split ' ')[1] -replace '/I', '/X') + ' /qb'
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            } elseif (Test-Path -LiteralPath $uninstallString) {
                Start-Process -FilePath $uninstallString -Wait -NoNewWindow -ErrorAction Stop
            } elseif ($uninstallString -match 'msiexec.* /x.*') {
                $uninst = ($uninstallString -split ' ')[1] + ' /qb'
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            }
            Log-Message "Deinstallation erfolgreich: $($Package.Name)"
        } else {
            $Package | Uninstall-Package -ErrorAction Stop
            Log-Message "Deinstallation erfolgreich über Provider: $($Package.Name)"
        }
    } catch {
        Log-Message "Fehler bei der Deinstallation des Pakets: $_" "ERROR"
    }
}

# Funktion zur Ausführung von Cleanmgr mit Voreinstellungen
function cleanup-mgr {
    try {
        Log-Message "Starte cleanup-mgr"
        $regList = @(
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Active Setup Temp Folders"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\BranchCache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\D3D Shader Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Delivery Optimization Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Diagnostic Data Viewer database files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Downloaded Program Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Internet Cache Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Language Pack"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Old ChkDsk Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Recycle Bin"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\RetailDemo Offline Content"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Setup Log Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error memory dump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error minidump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Temporary Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Thumbnail Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Update Cleanup"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\User file versions"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Defender"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Error Reporting Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Upgrade Log Files"; Key = 2 }
        )
        $keyname = 'StateFlags1337'
        foreach($key in $regList) {
            New-ItemProperty -Path $key.Pfad -Name $keyname -Value $key.Key -Type DWORD -Force | Out-Null
        }
        Start-Process "$env:SystemRoot\System32\cleanmgr.exe" -ArgumentList "/D C /verylowdisk /sagerun:1337" -WindowStyle Hidden
        Log-Message "cleanup-mgr erfolgreich abgeschlossen"
    } catch {
        Log-Message "Fehler in cleanup-mgr: $_" "ERROR"
    }
}

# Funktion zum Löschen bestimmter Ordner
function delete-folders {
    try {
        Log-Message "Starte delete-folders"
        $chocoPath = 'C:\ProgramData\choco-cache\'
        $adobeARM = 'C:\ProgramData\Adobe\ARM\'
        if ((Get-ChildItem -Path  $chocoPath -ErrorAction SilentlyContinue | Measure-Object -ErrorAction SilentlyContinue  -Property Length -Sum).Sum /1MB -ge 100) {
            Remove-Item -Path "$chocoPath*" -Recurse -Force
            Log-Message "Chocolatey Cache gelöscht"
        }
        if (Test-Path -Path $adobeARM) {
            Remove-Item -Path "$adobeARM*" -Recurse -Force
            Log-Message "Adobe ARM-Daten gelöscht"
        }
    } catch {
        Log-Message "Fehler in delete-folders: $_" "ERROR"
    }
}

# Funktion zur Bereinigung von Event-Logs
function clear-event-logs {
    try {
        Log-Message "Starte clear-event-logs"
        $threeMonthsAgo = (Get-Date).AddMonths(-4)

        # Liste aller Event-Logs
        $eventLogs = Get-WinEvent -ListLog *  | Where { $_.RecordCount -gt 0}

        foreach ($log in $eventLogs) {
            $logName = $log.LogName

            try {
                # Prüfen, wann das letzte Ereignis erstellt wurde
                $latestEvent = Get-WinEvent -LogName $logName -MaxEvents 1 -ErrorAction Stop
                if ($latestEvent) {
                    $latestEventTime = $latestEvent.TimeCreated
                    if ($latestEventTime -lt $threeMonthsAgo) {
                        # Log löschen, wenn das letzte Ereignis älter als 3 Monate ist
                        Clear-EventLog -LogName $logName
                        Log-Message "Event-Log $logName erfolgreich bereinigt"
                    }
                }
            } catch {
                # Wenn keine Ereignisse gefunden werden, überspringen
                if ($_.Exception -is [System.Management.Automation.RuntimeException]) {
                    Log-Message "Keine Ereignisse im Log $logName gefunden oder Fehler beim Abrufen der Ereignisse: $_" "WARNING"
                } else {
                    Log-Message "Fehler beim Abrufen von Ereignissen im Log $logName : $_" "ERROR"
                }
            }
        }
    } catch {
        Log-Message "Fehler in clear-event-logs: $_" "ERROR"
    }
}



# Funktion zur Bereinigung des Windows Update Caches
function cleanup-windows-update {
    try {
        Log-Message "Starte cleanup-windows-update"
        net stop wuauserv
        Remove-Item -LiteralPath "C:\Windows\SoftwareDistribution\*" -Recurse -Force
        net start wuauserv
        Log-Message "Windows Update Cache erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-windows-update: $_" "ERROR"
    }
}

# Funktion zur Aktualisierung von Gruppenrichtlinien
function update-group-policy {
    try {
        Log-Message "Starte update-group-policy"
        gpupdate /force
        Log-Message "Gruppenrichtlinien erfolgreich aktualisiert"
    } catch {
        Log-Message "Fehler in update-group-policy: $_" "ERROR"
    }
}

# Funktion zur Bereinigung alter Benutzerprofile
function cleanup-user-profiles {
    try {
        Log-Message "Starte cleanup-user-profiles"
        $profilesToDelete = Get-WmiObject -Class Win32_UserProfile | Where-Object {
            $_.Special -eq $false -and $_.LastUseTime -lt ((Get-Date).AddDays(-6000)).ToFileTime()
        }

        foreach ($profile in $profilesToDelete) {
            Remove-WmiObject -InputObject $profile
            Log-Message "Benutzerprofil gelöscht: $($profile.LocalPath)"
        }

        Log-Message "Alte Benutzerprofile erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-user-profiles: $_" "ERROR"
    }
}

# Funktion zur Bereinigung temporärer Dateien
function cleanup-temp-files {
    try {
        Log-Message "Starte cleanup-temp-files"
        Remove-Item -LiteralPath "C:\Windows\Temp\*" -Recurse -Force
        Remove-Item -LiteralPath "$env:TEMP\*" -Recurse -Force
        Log-Message "Temporäre Dateien erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-temp-files: $_" "ERROR"
    }
}

# Funktion zur Bereinigung des DNS-Caches
function cleanup-dns-cache {
    try {
        Log-Message "Starte cleanup-dns-cache"
        Clear-DnsClientCache
        Log-Message "DNS-Cache erfolgreich bereinigt"
    } catch {
        Log-Message "Fehler in cleanup-dns-cache: $_" "ERROR"
    }
}

# Hauptfunktion zur Reinigung des Systems
function cleanup {
    try {
        Log-Message "Starte Systembereinigung"
        cleanup-mgr
        delete-folders
        clear-event-logs
        cleanup-windows-update
        update-group-policy
        #cleanup-user-profiles
        cleanup-temp-files
        cleanup-dns-cache
        Log-Message "Systembereinigung erfolgreich abgeschlossen"
    } catch {
        Log-Message "Fehler in cleanup: $_" "ERROR"
    }
}

# Ausführung der Systembereinigung
cleanup

Wenn man das Ganze noch etwas aufhübscht, die comment-based Hilfe hinzufügt und das logging erweitert, erhält man ein gutes und leicht verständliches Skript.

PowerShell
<#
.SYNOPSIS
    System-Cleanup-Skript für Windows-Systeme.
    
.DESCRIPTION
    Dieses Skript führt verschiedene Reinigungsoperationen auf einem Windows-System durch,
    einschließlich Entfernung temporärer Dateien, Bereinigung des Disk Space Manager,
    Löschen des Windows Update-Caches, Bereinigung von Event-Logs und mehr.
    
.NOTES
    Dateiname: system_cleanup.ps1
    Autor: Ismahiil Ahmed
    Version: 1.1
    Datum: 2025-03-11
    Voraussetzungen: PowerShell 5.1 oder höher, Administratorrechte
    
.EXAMPLE
    .\system_cleanup.ps1
    
    Führt alle Bereinigungsoperationen durch.
#>

# Stellen Sie sicher, dass das Protokollverzeichnis existiert
$logPath = "C:\temp\log"
if (-not (Test-Path -Path $logPath)) {
    New-Item -Path $logPath -ItemType Directory -Force | Out-Null
}

<#
.SYNOPSIS
    Protokolliert Nachrichten in eine Datei.
    
.DESCRIPTION
    Protokolliert Nachrichten mit Zeitstempel und Typ in eine Protokolldatei
    und gibt sie optional auch in der Konsole aus.
    
.PARAMETER Message
    Die zu protokollierende Nachricht.
    
.PARAMETER Type
    Der Nachrichtentyp (z.B. INFO, WARNING, ERROR).
    
.EXAMPLE
    Log-Message "Operation abgeschlossen" "INFO"
    
    Protokolliert "Operation abgeschlossen" als INFO-Nachricht.
#>
function Log-Message {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Message,
        
        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateSet("INFO", "WARNING", "ERROR", "SUCCESS")]
        [string]$Type = "INFO"
    )
    
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $formattedMessage = "$timestamp [$Type] $Message"
    
    # Stellen Sie sicher, dass die Protokolldatei existiert
    $logFile = "C:\temp\log\system_cleanup.log"
    if (-not (Test-Path -Path $logFile)) {
        New-Item -Path $logFile -ItemType File -Force | Out-Null
    }
    
    Add-Content -Path $logFile -Value $formattedMessage
    
    # Farbcodierung für Konsolenausgabe
    switch ($Type) {
        "INFO" { Write-Host $formattedMessage -ForegroundColor Cyan }
        "WARNING" { Write-Host $formattedMessage -ForegroundColor Yellow }
        "ERROR" { Write-Host $formattedMessage -ForegroundColor Red }
        "SUCCESS" { Write-Host $formattedMessage -ForegroundColor Green }
        default { Write-Host $formattedMessage }
    }
}

<#
.SYNOPSIS
    Ruft installierte Software ab.
    
.DESCRIPTION
    Sucht nach installierter Software anhand des Namens und optional der Version.
    
.PARAMETER Name
    Der Name der zu suchenden Software. Unterstützt Platzhalter (*).
    
.PARAMETER Version
    Optional: Die spezifische Version der zu suchenden Software.
    
.EXAMPLE
    get-installedsoftware "Adobe*"
    
    Gibt alle installierten Adobe-Softwarepakete zurück.
    
.EXAMPLE
    get-installedsoftware "Microsoft Office" "16.0.12345.67890"
    
    Gibt Microsoft Office mit der angegebenen Version zurück, falls installiert.
    
.OUTPUTS
    Microsoft.PackageManagement.Packaging.SoftwareIdentity
    Ein oder mehrere Softwareidentitätsobjekte, die der Suche entsprechen.
#>
function get-installedsoftware {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        
        [Parameter(Mandatory = $false, Position = 1)]
        [string]$Version = $null
    )
    
    try {
        Log-Message "Suche nach installierter Software: Name=$Name, Version=$Version"
        
        # Hole alle Pakete, die dem Namen entsprechen
        $packages = Get-Package -Provider Programs -IncludeWindowsInstaller -Name $Name -ErrorAction Stop
        
        # Filtere nach Version, falls angegeben
        if ($Version) {
            $packages = $packages | Where-Object { $_.Version -eq $Version }
        }
        
        $count = if ($packages) { $packages.Count } else { 0 }
        Log-Message "Gefundene Pakete: $count"
        
        return $packages
    } 
    catch {
        Log-Message "Fehler beim Abrufen installierter Software: $_" "ERROR"
        return $null
    }
}

<#
.SYNOPSIS
    Deinstalliert ein Softwarepaket.
    
.DESCRIPTION
    Deinstalliert ein Softwarepaket basierend auf dem übergebenen Paketobjekt.
    Unterstützt verschiedene Deinstallationsmethoden je nach Pakettyp.
    
.PARAMETER Package
    Das zu deinstallierende Softwarepaket (SoftwareIdentity-Objekt).
    
.EXAMPLE
    $office = get-installedsoftware "Microsoft Office"
    uninstall-softwarepackage $office
    
    Deinstalliert Microsoft Office.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
#>
function uninstall-softwarepackage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [Microsoft.PackageManagement.Packaging.SoftwareIdentity]$Package
    )
    
    try {
        Log-Message "Versuche Deinstallation des Pakets: $($Package.Name) (Version: $($Package.Version))"
        
        # Prüfe, ob es sich um ein Programm handelt
        if ($Package.ProviderName -eq "Programs") {
            # Extrahiere Deinstallationsinformationen aus SWID-Tag
            $xml = [xml]$Package.SwidTagText
            $uninstallString = $xml.SoftwareIdentity.Meta.UninstallString
            
            Log-Message "Deinstallationsstring: $uninstallString" "INFO"
            
            # Verarbeite je nach Art des Deinstallationsstrings
            if ($uninstallString -match 'msiexec.* /[iI].*') {
                # MSI-Deinstallation (ersetze /I durch /X für Deinstallation)
                $uninst = (($uninstallString -split ' ')[1] -replace '/[iI]', '/X') + ' /qb'
                Log-Message "Führe MSI-Deinstallation aus: msiexec.exe $uninst"
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            } 
            elseif ($uninstallString -match 'msiexec.* /[xX].*') {
                # MSI-Deinstallation (bereits mit /X)
                $uninst = ($uninstallString -split ' ')[1] + ' /qb'
                Log-Message "Führe MSI-Deinstallation aus: msiexec.exe $uninst"
                Start-Process msiexec.exe -ArgumentList $uninst -Wait -NoNewWindow -ErrorAction Stop
            }
            elseif (Test-Path -LiteralPath $uninstallString -ErrorAction SilentlyContinue) {
                # Ausführbare Deinstallationsdatei
                Log-Message "Führe Deinstallationsprogramm aus: $uninstallString"
                Start-Process -FilePath $uninstallString -Wait -NoNewWindow -ErrorAction Stop
            }
            else {
                # Versuche, den Deinstallationsstring direkt auszuführen
                Log-Message "Versuche direkten Aufruf des Deinstallationsstrings"
                Invoke-Expression $uninstallString
            }
            
            Log-Message "Deinstallation erfolgreich: $($Package.Name)" "SUCCESS"
        } 
        else {
            # Für andere Provider (z.B. PowerShellGet, Chocolatey)
            Log-Message "Deinstalliere über Paketprovider: $($Package.ProviderName)"
            $Package | Uninstall-Package -Force -ErrorAction Stop
            Log-Message "Deinstallation erfolgreich über Provider: $($Package.Name)" "SUCCESS"
        }
    } 
    catch {
        Log-Message "Fehler bei der Deinstallation des Pakets: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Führt den Windows Disk Cleanup Manager mit vordefinierten Einstellungen aus.
    
.DESCRIPTION
    Konfiguriert und startet den Windows Disk Cleanup Manager (cleanmgr.exe) mit 
    vordefinierten Einstellungen zur Bereinigung verschiedener Systemdateien.
    
.EXAMPLE
    cleanup-mgr
    
    Führt den Disk Cleanup Manager mit den vordefinierten Einstellungen aus.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
    Ein Wert von 2 bedeutet, dass diese Kategorie zur Bereinigung ausgewählt wird.
#>
function cleanup-mgr {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte cleanup-mgr"
        
        # Definiere die zu bereinigenden Kategorien
        $regList = @(
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Active Setup Temp Folders"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\BranchCache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\D3D Shader Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Delivery Optimization Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Diagnostic Data Viewer database files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Downloaded Program Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Internet Cache Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Language Pack"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Old ChkDsk Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Recycle Bin"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\RetailDemo Offline Content"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Setup Log Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error memory dump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\System error minidump files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Temporary Files"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Thumbnail Cache"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Update Cleanup"; Key = 2 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\User file versions"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Defender"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Error Reporting Files"; Key = 0 },
            @{ Pfad = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Windows Upgrade Log Files"; Key = 2 }
        )
        
        # Verwende einen eindeutigen Namen für den Cleanup-Lauf
        $keyname = 'StateFlags1337'
        
        # Setze die Registry-Einträge für jede Kategorie
        foreach($key in $regList) {
            # Prüfe, ob Registry-Pfad existiert, und erstelle ihn ggf.
            if (-not (Test-Path -Path $key.Pfad)) {
                New-Item -Path $key.Pfad -Force | Out-Null
            }
            
            # Setze oder aktualisiere den Registry-Wert
            New-ItemProperty -Path $key.Pfad -Name $keyname -Value $key.Key -Type DWORD -Force | Out-Null
        }
        
        # Starte den Disk Cleanup Manager
        Log-Message "Starte cleanmgr.exe mit vordefinierten Einstellungen"
        Start-Process -FilePath "$env:SystemRoot\System32\cleanmgr.exe" -ArgumentList "/D C /verylowdisk /sagerun:1337" -WindowStyle Hidden -Wait
        
        Log-Message "cleanup-mgr erfolgreich abgeschlossen" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in cleanup-mgr: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Löscht spezifische Cache-Ordner und temporäre Dateien.
    
.DESCRIPTION
    Löscht den Chocolatey-Cache und Adobe ARM-Daten, wenn sie vorhanden sind
    und bestimmte Größenbedingungen erfüllen.
    
.EXAMPLE
    delete-folders
    
    Löscht Chocolatey-Cache und Adobe ARM-Daten, falls sie existieren.
#>
function delete-folders {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte delete-folders"
        
        # Definiere zu prüfende Pfade
        $foldersToCheck = @(
            @{
                Path = 'C:\ProgramData\choco-cache\';
                Name = "Chocolatey Cache";
                SizeThreshold = 100; # in MB
                CheckSize = $true;
            },
            @{
                Path = 'C:\ProgramData\Adobe\ARM\';
                Name = "Adobe ARM-Daten";
                CheckSize = $false;
            }
        )
        
        # Prüfe und lösche jeden Ordner gemäß den Bedingungen
        foreach ($folder in $foldersToCheck) {
            if (Test-Path -Path $folder.Path) {
                $shouldDelete = $true
                
                # Prüfe Größe, falls erforderlich
                if ($folder.CheckSize) {
                    $size = (Get-ChildItem -Path $folder.Path -Recurse -ErrorAction SilentlyContinue | 
                            Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
                    
                    if ($size -lt $folder.SizeThreshold) {
                        Log-Message "$($folder.Name) ist kleiner als $($folder.SizeThreshold) MB (aktuell: $([math]::Round($size, 2)) MB), wird übersprungen" "INFO"
                        $shouldDelete = $false
                    }
                    else {
                        Log-Message "$($folder.Name) hat die Größe von $([math]::Round($size, 2)) MB" "INFO"
                    }
                }
                
                # Lösche den Ordner, wenn die Bedingungen erfüllt sind
                if ($shouldDelete) {
                    Remove-Item -Path "$($folder.Path)*" -Recurse -Force -ErrorAction Stop
                    Log-Message "$($folder.Name) erfolgreich gelöscht" "SUCCESS"
                }
            }
            else {
                Log-Message "$($folder.Name) nicht gefunden, Überspringen" "INFO"
            }
        }
        
        Log-Message "delete-folders abgeschlossen" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in delete-folders: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Bereinigt Windows-Event-Logs, die älter als ein bestimmter Zeitraum sind.
    
.DESCRIPTION
    Identifiziert Event-Logs, deren letzter Eintrag älter als vier Monate ist,
    und löscht deren Inhalt.
    
.EXAMPLE
    clear-event-logs
    
    Bereinigt alle Event-Logs, deren letzte Einträge älter als vier Monate sind.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
#>
function clear-event-logs {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte clear-event-logs"
        
        # Definiere den Grenzwert (vier Monate zurück)
        $threeMonthsAgo = (Get-Date).AddMonths(-4)
        Log-Message "Lösche Event-Logs mit Einträgen älter als $($threeMonthsAgo.ToString('yyyy-MM-dd'))" "INFO"
        
        # Hole alle Event-Logs mit Einträgen
        $eventLogs = Get-WinEvent -ListLog * -ErrorAction SilentlyContinue | 
                      Where-Object { $_.RecordCount -gt 0 -and $_.IsEnabled -and $_.IsLogFull -eq $false }
        
        $clearedCount = 0
        $skippedCount = 0
        $errorCount = 0
        
        foreach ($log in $eventLogs) {
            $logName = $log.LogName
            
            try {
                # Prüfe, wann das letzte Ereignis erstellt wurde
                $latestEvent = Get-WinEvent -LogName $logName -MaxEvents 1 -ErrorAction Stop
                
                if ($latestEvent) {
                    $latestEventTime = $latestEvent.TimeCreated
                    
                    if ($latestEventTime -lt $threeMonthsAgo) {
                        # Log löschen, wenn das letzte Ereignis älter als die Grenze ist
                        [System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($logName)
                        $clearedCount++
                        Log-Message "Event-Log '$logName' erfolgreich bereinigt (letzter Eintrag: $($latestEventTime.ToString('yyyy-MM-dd')))" "SUCCESS"
                    }
                    else {
                        $skippedCount++
                        Log-Message "Event-Log '$logName' übersprungen (letzter Eintrag: $($latestEventTime.ToString('yyyy-MM-dd')))" "INFO"
                    }
                }
            } 
            catch {
                $errorCount++
                # Wenn keine Ereignisse gefunden werden oder andere Fehler auftreten
                if ($_.Exception -is [System.Management.Automation.RuntimeException] -or 
                    $_.Exception.Message -match "No events") {
                    Log-Message "Keine Ereignisse im Log '$logName' gefunden oder unzureichende Berechtigungen" "WARNING"
                } 
                else {
                    Log-Message "Fehler beim Bearbeiten des Logs '$logName': $_" "ERROR"
                }
            }
        }
        
        Log-Message "Event-Log-Bereinigung abgeschlossen: $clearedCount gelöscht, $skippedCount übersprungen, $errorCount Fehler" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in clear-event-logs: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Bereinigt den Windows Update Cache.
    
.DESCRIPTION
    Stoppt den Windows Update-Dienst, löscht den Inhalt des SoftwareDistribution-Ordners
    und startet den Dienst wieder.
    
.EXAMPLE
    cleanup-windows-update
    
    Bereinigt den Windows Update Cache.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
#>
function cleanup-windows-update {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte cleanup-windows-update"
        
        # Prüfe Größe des Update-Caches vor der Bereinigung
        $updateCachePath = "C:\Windows\SoftwareDistribution"
        $sizeBefore = 0
        
        if (Test-Path -Path $updateCachePath) {
            $sizeBefore = (Get-ChildItem -Path $updateCachePath -Recurse -ErrorAction SilentlyContinue | 
                          Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
            Log-Message "Windows Update Cache-Größe vor Bereinigung: $([math]::Round($sizeBefore, 2)) MB" "INFO"
        }
        
        # Stoppe den Windows Update-Dienst
        Log-Message "Stoppe Windows Update-Dienst" "INFO"
        Stop-Service -Name wuauserv -Force -ErrorAction Stop
        
        # Lösche den Inhalt des SoftwareDistribution-Ordners
        if (Test-Path -Path $updateCachePath) {
            Remove-Item -LiteralPath "$updateCachePath\*" -Recurse -Force -ErrorAction SilentlyContinue
            Log-Message "Windows Update Cache-Dateien gelöscht" "SUCCESS"
        }
        
        # Starte den Windows Update-Dienst wieder
        Log-Message "Starte Windows Update-Dienst" "INFO"
        Start-Service -Name wuauserv -ErrorAction Stop
        
        # Prüfe Größe nach der Bereinigung
        $sizeAfter = 0
        if (Test-Path -Path $updateCachePath) {
            $sizeAfter = (Get-ChildItem -Path $updateCachePath -Recurse -ErrorAction SilentlyContinue | 
                         Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
            
            $savedSpace = $sizeBefore - $sizeAfter
            Log-Message "Windows Update Cache-Größe nach Bereinigung: $([math]::Round($sizeAfter, 2)) MB (gespart: $([math]::Round($savedSpace, 2)) MB)" "SUCCESS"
        }
        
        Log-Message "Windows Update Cache erfolgreich bereinigt" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in cleanup-windows-update: $_" "ERROR"
        
        # Versuche, den Windows Update-Dienst zu starten, falls er gestoppt wurde
        try {
            Start-Service -Name wuauserv -ErrorAction SilentlyContinue
        } 
        catch {
            Log-Message "Konnte Windows Update-Dienst nicht neu starten: $_" "ERROR"
        }
    }
}

<#
.SYNOPSIS
    Aktualisiert Gruppenrichtlinien.
    
.DESCRIPTION
    Führt eine Aktualisierung der Gruppenrichtlinien auf dem Computer durch.
    
.EXAMPLE
    update-group-policy
    
    Aktualisiert Gruppenrichtlinien mit Force-Option.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
#>
function update-group-policy {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte update-group-policy"
        
        # Führe gpupdate mit Force-Option aus
        $process = Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -Wait -PassThru -NoNewWindow
        
        # Prüfe Exit-Code
        if ($process.ExitCode -eq 0) {
            Log-Message "Gruppenrichtlinien erfolgreich aktualisiert" "SUCCESS"
        } 
        else {
            Log-Message "Gruppenrichtlinien-Update abgeschlossen mit Exit-Code $($process.ExitCode)" "WARNING"
        }
    } 
    catch {
        Log-Message "Fehler in update-group-policy: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Bereinigt alte Benutzerprofile.
    
.DESCRIPTION
    Identifiziert und löscht Benutzerprofile, die seit einer bestimmten Zeit
    nicht mehr verwendet wurden.
    
.EXAMPLE
    cleanup-user-profiles
    
    Bereinigt Benutzerprofile, die seit mehr als 6000 Tagen nicht verwendet wurden.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
    Systemprofile und aktive Profile werden nicht gelöscht.
#>
function cleanup-user-profiles {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte cleanup-user-profiles"
        
        # Berechne das Datum für die Filterung (6000 Tage zurück)
        $cutoffDate = (Get-Date).AddDays(-6000)
        $cutoffFileTime = $cutoffDate.ToFileTime()
        
        Log-Message "Suche nach Benutzerprofilen, die seit $($cutoffDate.ToString('yyyy-MM-dd')) nicht verwendet wurden" "INFO"
        
        # Hole alle Benutzerprofile
        $allProfiles = Get-WmiObject -Class Win32_UserProfile -ErrorAction Stop
        
        # Filtere die zu löschenden Profile
        $profilesToDelete = $allProfiles | Where-Object {
            # Überspringe Systemprofile, spezielle Profile und das aktuell angemeldete Profil
            $_.Special -eq $false -and 
            $_.Loaded -eq $false -and 
            $_.LastUseTime -ne $null -and 
            $_.LastUseTime -lt $cutoffFileTime
        }
        
        if ($profilesToDelete.Count -eq 0) {
            Log-Message "Keine alten Benutzerprofile gefunden, die gelöscht werden können" "INFO"
            return
        }
        
        # Zeige die zu löschenden Profile an
        foreach ($profile in $profilesToDelete) {
            $lastUseDate = [DateTime]::FromFileTime($profile.LastUseTime)
            Log-Message "Wird gelöscht: $($profile.LocalPath) (Zuletzt verwendet: $($lastUseDate.ToString('yyyy-MM-dd')))" "INFO"
            
            # Lösche das Profil
            try {
                Remove-WmiObject -InputObject $profile -ErrorAction Stop
                Log-Message "Benutzerprofil gelöscht: $($profile.LocalPath)" "SUCCESS"
            } 
            catch {
                Log-Message "Fehler beim Löschen des Profils $($profile.LocalPath): $_" "ERROR"
            }
        }
        
        Log-Message "Alte Benutzerprofile erfolgreich bereinigt" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in cleanup-user-profiles: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Bereinigt temporäre Dateien.
    
.DESCRIPTION
    Löscht temporäre Dateien aus dem Windows-Temp-Verzeichnis und dem
    Temp-Verzeichnis des aktuellen Benutzers.
    
.EXAMPLE
    cleanup-temp-files
    
    Bereinigt alle temporären Dateien.
    
.NOTES
    Einige Dateien können möglicherweise nicht gelöscht werden, wenn sie 
    von Prozessen verwendet werden.
#>
function cleanup-temp-files {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte cleanup-temp-files"
        
        # Definiere zu bereinigende Verzeichnisse
        $tempFolders = @(
            @{
                Path = "C:\Windows\Temp";
                Name = "Windows-Temp";
            },
            @{
                Path = $env:TEMP;
                Name = "Benutzer-Temp";
            }
        )
        
        foreach ($folder in $tempFolders) {
            if (Test-Path -Path $folder.Path) {
                # Ermittle Größe vor der Bereinigung
                $sizeBefore = (Get-ChildItem -Path $folder.Path -Recurse -Force -ErrorAction SilentlyContinue | 
                              Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
                
                Log-Message "$($folder.Name)-Verzeichnis Größe vor Bereinigung: $([math]::Round($sizeBefore, 2)) MB" "INFO"
                
                # Versuche, alle Dateien zu löschen
                $errorCount = 0
                Get-ChildItem -Path "$($folder.Path)\*" -Recurse -Force -ErrorAction SilentlyContinue | 
                ForEach-Object {
                    try {
                        Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
                    } 
                    catch {
                        $errorCount++
                    }
                }
                
                # Ermittle Größe nach der Bereinigung
                $sizeAfter = (Get-ChildItem -Path $folder.Path -Recurse -Force -ErrorAction SilentlyContinue | 
                              Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1MB
                
                $savedSpace = $sizeBefore - $sizeAfter
                Log-Message "$($folder.Name)-Verzeichnis Größe nach Bereinigung: $([math]::Round($sizeAfter, 2)) MB (gespart: $([math]::Round($savedSpace, 2)) MB)" "SUCCESS"
                
                if ($errorCount -gt 0) {
                    Log-Message "Konnte $errorCount Dateien in $($folder.Name) nicht löschen (möglicherweise in Verwendung)" "WARNING"
                }
            }
            else {
                Log-Message "$($folder.Name)-Verzeichnis nicht gefunden" "WARNING"
            }
        }
        
        Log-Message "Temporäre Dateien erfolgreich bereinigt" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in cleanup-temp-files: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Bereinigt den DNS-Client-Cache.
    
.DESCRIPTION
    Löscht alle DNS-Einträge aus dem lokalen DNS-Client-Cache.
    
.EXAMPLE
    cleanup-dns-cache
    
    Bereinigt den DNS-Client-Cache.
#>
function cleanup-dns-cache {
    [CmdletBinding()]
    param ()
    
    try {
        Log-Message "Starte cleanup-dns-cache"
        
        # Hole DNS-Cache-Einträge vor der Bereinigung
        $entriesBefore = (Get-DnsClientCache -ErrorAction SilentlyContinue).Count
        Log-Message "DNS-Cache enthält $entriesBefore Einträge vor der Bereinigung" "INFO"
        
        # Bereinige den DNS-Cache
        Clear-DnsClientCache -ErrorAction Stop
        
        # Bestätige die Bereinigung
        $entriesAfter = (Get-DnsClientCache -ErrorAction SilentlyContinue).Count
        Log-Message "DNS-Cache erfolgreich bereinigt ($entriesBefore Einträge entfernt)" "SUCCESS"
    } 
    catch {
        Log-Message "Fehler in cleanup-dns-cache: $_" "ERROR"
    }
}

<#
.SYNOPSIS
    Hauptfunktion zur Systembereinigung.
    
.DESCRIPTION
    Führt alle definierten Bereinigungsfunktionen nacheinander aus.
    Protokolliert den Fortschritt und die Ergebnisse.
    
.EXAMPLE
    cleanup
    
    Führt eine vollständige Systembereinigung durch.
    
.NOTES
    Diese Funktion erfordert Administratorrechte.
#>
function cleanup {
    [CmdletBinding()]
    param ()
    
    try {
        $startTime = Get-Date
        Log-Message "===== STARTE SYSTEMBEREINIGUNG =====" "INFO"
        Log-Message "Startzeit: $($startTime.ToString('yyyy-MM-dd HH:mm:ss'))" "INFO"
        
        # Führe alle Bereinigungsfunktionen nacheinander aus
        Log-Message "Führe cleanup-mgr aus..." "INFO"
        cleanup-mgr
        
        Log-Message "Führe delete-folders aus..." "INFO"
        delete-folders
        
        Log-Message "Führe clear-event-logs aus..." "INFO"
        clear-event-logs
        
        Log-Message "Führe cleanup-windows-update aus..." "INFO"
        cleanup-windows-update
        
        Log-Message "Führe update-group-policy aus..." "INFO"
        update-group-policy
        
        # Benutzerprofile-Bereinigung ist standardmäßig deaktiviert (potenziell gefährlich)
        # Entfernen Sie den Kommentar, um diese Funktion zu aktivieren
        # Log-Message "Führe cleanup-user-profiles aus..." "INFO"
        # cleanup-user-profiles
        
        Log-Message "Führe cleanup-temp-files aus..." "INFO"
        cleanup-temp-files
        
        Log-Message "Führe cleanup-dns-cache aus..." "INFO"
        cleanup-dns-cache
        
        # Berechne Gesamtdauer
        $endTime = Get-Date
        $duration = $endTime - $startTime
        Log-Message "===== SYSTEMBEREINIGUNG ABGESCHLOSSEN =====" "SUCCESS"
        Log-Message "Endzeit: $($endTime.ToString('yyyy-MM-dd HH:mm:ss'))" "INFO"
        Log-Message "Gesamtdauer: $($duration.Hours) Stunden, $($duration.Minutes) Minuten, $($duration.Seconds) Sekunden" "INFO"
    } 
    catch {
        Log-Message "Kritischer Fehler in cleanup: $_" "ERROR"
    }
}

# Prüfe, ob das Skript als Administrator ausgeführt wird
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Host "Dieses Skript erfordert Administratorrechte. Bitte starten Sie PowerShell als Administrator." -ForegroundColor Red
    exit
}

# Ausführung der Systembereinigung
cleanup

Allgemeines zu Powershell

In diesem Beitrag zeige ich dir, wie du in die faszinierende Welt der Powershell eintauchst – ein mächtiges und flexibles Tool, das fortgeschrittenen IT-Profis dabei hilft, komplexe administrative Aufgaben zu automatisieren und Systeme effizient zu verwalten. Dies ist der erste Teil einer Serie, in der wir tief in fortgeschrittene Techniken, Best Practices und die Architektur der Powershell eintauchen.


🔍 Schritt 1: Einführung in Powershell

Powershell ist weit mehr als nur eine Befehlszeile – es handelt sich um eine objektorientierte Skriptsprache und Shell, die speziell für die Systemadministration entwickelt wurde. Ursprünglich für Windows konzipiert, ist Powershell heute dank der Open-Source-Version auch auf Linux und macOS verfügbar. In diesem Beitrag richten wir unseren Fokus auf die fortgeschrittene Nutzung, um anspruchsvolle Automatisierungs- und Verwaltungsaufgaben zu meistern.

Einige zentrale Konzepte, die du kennen solltest, sind:

  1. Objektbasierte Pipeline: Anders als herkömmliche Shells arbeitet Powershell mit Objekten, was die Verarbeitung und Weitergabe von Daten erheblich vereinfacht.
  2. Module und Cmdlets: Powershell bietet eine Vielzahl an integrierten Cmdlets und die Möglichkeit, Module zu laden, die zusätzliche Funktionalitäten bereitstellen.
  3. Remoting und Automatisierung: Über Powershell-Remoting lassen sich mehrere Systeme zentral verwalten und automatisierte Abläufe realisieren.

🚀 Schritt 2: Powershell-Umgebung und erste Befehle

Starten kannst du Powershell, indem du in Windows die Anwendung „Powershell“ oder „Powershell ISE“ öffnest. Unter Linux installierst du die neueste Version, z. B. auf Ubuntu 24.04, mit:

# Installation von Powershell auf Ubuntu 24.04
# Repository hinzufügen
wget -q "https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb" -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install -y powershell

# Powershell starten
pwsh

💡 Schritt 3: Erste Schritte und fortgeschrittene Konzepte

Nachdem du Powershell gestartet hast, kannst du dich mit den grundlegenden Cmdlets vertraut machen. Einige nützliche Befehle zum Einstieg sind:

Get-Command
Get-Help Get-Process -Detailed #Oder -Examples

Für fortgeschrittene IT-Profis lohnt sich ein Blick in folgende Themen:

  1. Erweiterte Skript-Entwicklung: Nutze fortgeschrittene Techniken wie Error Handling, Debugging und das Arbeiten mit komplexen Objekten.
  2. Remoting und Automatisierung: Konfiguriere Powershell Remoting, um mehrere Systeme zentral zu verwalten und automatisierte Aufgaben über verschiedene Server hinweg auszuführen.
  3. Modul-Entwicklung: Erstelle und teile eigene Module, um wiederkehrende Aufgaben und Funktionen zu kapseln und in deinem Team zu standardisieren.
  4. Desired State Configuration (DSC): Setze DSC ein, um den gewünschten Zustand von Systemen deklarativ festzulegen und automatisch durchzusetzen.

📌 Fazit und Ausblick

In diesem Beitrag haben wir einen tiefgehenden Einblick in die Grundlagen und erste fortgeschrittene Konzepte der Powershell gewonnen. Als IT-Profi profitierst du von der mächtigen, objektorientierten Natur der Powershell, die dir eine effiziente und automatisierte Verwaltung deiner Systeme ermöglicht.

Dies ist der Auftakt zu einer Serie von Blogbeiträgen, in denen wir uns mit noch komplexeren Themen, Best Practices und praxisnahen Anwendungsfällen rund um Powershell beschäftigen werden. Hast du Fragen oder möchtest du eigene Erfahrungen teilen? Dann diskutiere gern in den Kommentaren mit!