Wer als Windows-Admin arbeitet und PowerShell noch nicht täglich nutzt, lässt massiv viel Potenzial liegen. Kein Klicken durch GUIs, keine Fehler durch Copy-Paste in Excel – stattdessen saubere, wiederholbare Automatisierung. Dieser Post fasst die Befehle zusammen, die ich im Alltag am häufigsten nutze.
Voraussetzungen
Ich empfehle PowerShell 7 (cross-platform, aktiv weiterentwickelt) statt Windows PowerShell 5.1. Installation auf Windows:
winget install Microsoft.PowerShell
Für die meisten Beispiele hier brauchst du außerdem:
# Active Directory Modul (auf Windows Server vorinstalliert oder via RSAT)
Install-WindowsFeature RSAT-AD-PowerShell
# Microsoft Graph (M365 / Azure)
Install-Module Microsoft.Graph -Scope CurrentUser
# Exchange Online
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Active Directory
Benutzer auflisten und exportieren
Einer meiner meistgenutzten Befehle: alle aktivierten AD-User mit letztem Login exportieren.
Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate, Department, Title |
Select-Object Name, SamAccountName, Department, Title, LastLogonDate |
Sort-Object LastLogonDate -Descending |
Export-Csv "C:\Reports\users_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8
Inaktive Accounts finden
Accounts, die sich 90 Tage nicht eingeloggt haben – ideal für regelmäßige Audits:
$cutoff = (Get-Date).AddDays(-90)
Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate |
Where-Object { $_.LastLogonDate -lt $cutoff -or $_.LastLogonDate -eq $null } |
Select-Object Name, SamAccountName, LastLogonDate |
Export-Csv "C:\Reports\inactive_users.csv" -NoTypeInformation
Benutzer schnell anlegen
Neuer Mitarbeiter, neuer Account. Template-User als Basis:
$template = Get-ADUser "muster.user" -Properties MemberOf, Department, Company
$password = ConvertTo-SecureString "Start@2026!" -AsPlainText -Force
New-ADUser `
-Name "Max Mustermann" `
-GivenName "Max" `
-Surname "Mustermann" `
-SamAccountName "max.mustermann" `
-UserPrincipalName "max.mustermann@contoso.de" `
-Path "OU=Mitarbeiter,DC=contoso,DC=local" `
-AccountPassword $password `
-Enabled $true `
-Department $template.Department `
-Company $template.Company
# Gruppen vom Template-User kopieren
$template.MemberOf | Add-ADGroupMember -Members "max.mustermann"
Passwort-Ablauf im Blick behalten
Welche User haben ein ablaufendes Passwort in den nächsten 14 Tagen?
$maxAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
$threshold = (Get-Date).AddDays(14)
Get-ADUser -Filter {Enabled -eq $true -and PasswordNeverExpires -eq $false} `
-Properties PasswordLastSet |
ForEach-Object {
$expires = $_.PasswordLastSet + $maxAge
[PSCustomObject]@{
Name = $_.Name
Expires = $expires
DaysLeft = ($expires - (Get-Date)).Days
}
} |
Where-Object { $_.DaysLeft -le 14 -and $_.DaysLeft -ge 0 } |
Sort-Object DaysLeft
Microsoft 365 via Graph API
Verbinden
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Directory.Read.All"
Alle lizenzierten User ausgeben
Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses |
Where-Object { $_.AssignedLicenses.Count -gt 0 } |
Select-Object DisplayName, UserPrincipalName
UsageLocation für alle User setzen
Ohne UsageLocation lassen sich keine M365-Lizenzen zuweisen – ein klassischer Fallstrick bei Migrationen:
Get-MgUser -All | ForEach-Object {
if (-not $_.UsageLocation) {
Update-MgUser -UserId $_.Id -UsageLocation "DE"
Write-Host "Gesetzt: $($_.DisplayName)"
}
}
MFA-Status aller User prüfen
Connect-MgGraph -Scopes "UserAuthenticationMethod.Read.All"
Get-MgUser -All | ForEach-Object {
$methods = Get-MgUserAuthenticationMethod -UserId $_.Id
[PSCustomObject]@{
User = $_.DisplayName
UPN = $_.UserPrincipalName
MFATypes = ($methods.AdditionalProperties.'@odata.type' -join ", ")
}
} | Export-Csv "C:\Reports\mfa_status.csv" -NoTypeInformation
Exchange Online
Connect-ExchangeOnline -UserPrincipalName admin@contoso.de
Postfachgröße aller Mailboxen
Get-Mailbox -ResultSize Unlimited |
Get-MailboxStatistics |
Select-Object DisplayName, TotalItemSize, ItemCount |
Sort-Object { $_.TotalItemSize.Value.ToBytes() } -Descending |
Format-Table -AutoSize
Weiterleitungen im Unternehmen aufspüren
Aktive Weiterleitungen sind ein häufiges Sicherheitsrisiko – und oft vergessen:
Get-Mailbox -ResultSize Unlimited |
Where-Object { $_.ForwardingAddress -or $_.ForwardingSmtpAddress } |
Select-Object DisplayName, ForwardingAddress, ForwardingSmtpAddress, DeliverToMailboxAndForward
Netzwerk & Server
Port-Erreichbarkeit testen
Ersetzt telnet vollständig – mit mehr Infos:
Test-NetConnection -ComputerName "dc01.contoso.local" -Port 445
# Mehrere Hosts / Ports in einer Schleife
$targets = @("dc01", "dc02", "fileserver01")
$ports = @(445, 3389, 80, 443)
foreach ($host in $targets) {
foreach ($port in $ports) {
$result = Test-NetConnection -ComputerName $host -Port $port -WarningAction SilentlyContinue
[PSCustomObject]@{
Host = $host
Port = $port
Status = if ($result.TcpTestSucceeded) { "✓ offen" } else { "✗ zu" }
}
}
} | Format-Table -AutoSize
DNS-Auflösung auf mehreren Servern testen
$dnsServers = @("8.8.8.8", "1.1.1.1", "192.168.1.1")
$hostname = "westmeier.cloud"
foreach ($dns in $dnsServers) {
try {
$result = Resolve-DnsName -Name $hostname -Server $dns -ErrorAction Stop
Write-Host "$dns → $($result[0].IPAddress)" -ForegroundColor Green
} catch {
Write-Host "$dns → Fehler: $_" -ForegroundColor Red
}
}
Freien Festplattenplatz überwachen
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Used -gt 0 } |
Select-Object Name,
@{N="Gesamt (GB)"; E={[math]::Round($_.Used / 1GB + $_.Free / 1GB, 1)}},
@{N="Frei (GB)"; E={[math]::Round($_.Free / 1GB, 1)}},
@{N="Frei (%)"; E={[math]::Round(($_.Free / ($_.Used + $_.Free)) * 100, 1)}} |
Format-Table -AutoSize
Fehlerbehandlung & Logging
Produktions-Skripte brauchen ordentliches Logging. Mein Standard-Template:
$LogFile = "C:\Logs\script_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogFile -Value $line
switch ($Level) {
"ERROR" { Write-Host $line -ForegroundColor Red }
"WARN" { Write-Host $line -ForegroundColor Yellow }
default { Write-Host $line -ForegroundColor Cyan }
}
}
try {
Write-Log "Skript gestartet"
# ... deine Logik ...
Write-Log "Skript erfolgreich abgeschlossen"
} catch {
Write-Log "Kritischer Fehler: $_" -Level "ERROR"
exit 1
}
Nützliche One-Liner
Kleine Helfer, die ich immer wieder brauche:
# Laufende Dienste mit hohem RAM-Verbrauch
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 Name, @{N="RAM (MB)"; E={[math]::Round($_.WorkingSet64/1MB,1)}}
# Alle lokalen Admins auflisten
Get-LocalGroupMember -Group "Administrators"
# Zertifikate, die in 30 Tagen ablaufen
Get-ChildItem Cert:\LocalMachine\My |
Where-Object { $_.NotAfter -lt (Get-Date).AddDays(30) } |
Select-Object Subject, NotAfter
# Letzten Reboot ermitteln
(Get-CimInstance Win32_OperatingSystem).LastBootUpTime
# Aktuell eingeloggte User auf Remote-System
query user /server:SERVERNAME
# Datei-Hash prüfen (z.B. nach Download)
Get-FileHash "C:\Downloads\setup.exe" -Algorithm SHA256
Fazit
PowerShell ist kein “Nice to have” — es ist das Werkzeug, das dich von einem reaktiven zu einem proaktiven Admin macht. Einmal geschriebene Skripte laufen zuverlässig, dokumentieren sich selbst durch Write-Log, und ersparen dir Stunden manueller Arbeit pro Woche.
Mein Tipp für den Einstieg: Nimm eine Aufgabe, die du regelmäßig manuell machst – Benutzerexport, Port-Check, Speicherübersicht – und automatisiere genau diese eine Sache. Der Rest folgt von selbst.