Finalize homelab audit end state
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Windows Reinstall Helpers
|
||||
|
||||
Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufsetzen-/Dual-Boot-Kontext vom Mai 2026.
|
||||
|
||||
- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel.
|
||||
- `repair-disk0-boot-to-new-windows.ps1` repariert EFI/Bootdateien fuer das neue Windows auf der Intel-SSD und verlangt Adminrechte.
|
||||
- `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung.
|
||||
|
||||
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden.
|
||||
@@ -0,0 +1,118 @@
|
||||
param(
|
||||
[datetime]$Cutoff = "2026-05-07T15:30:00",
|
||||
[string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup",
|
||||
[string]$UserProfilePath = "C:\Users\michi"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Invoke-RobocopyDelta {
|
||||
param(
|
||||
[string]$Source,
|
||||
[string]$Destination,
|
||||
[string]$LogName
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Source)) {
|
||||
Write-Host "SKIP missing source: $Source"
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||
$LogPath = Join-Path $DeltaLogDir $LogName
|
||||
|
||||
Write-Host "DELTA COPY $Source"
|
||||
Write-Host " -> $Destination"
|
||||
|
||||
robocopy $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /MAXAGE:$CutoffString /TEE /LOG:$LogPath
|
||||
$ExitCode = $LASTEXITCODE
|
||||
if ($ExitCode -gt 7) {
|
||||
throw "Robocopy failed with exit code $ExitCode for source: $Source"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $BackupRoot)) {
|
||||
throw "BackupRoot does not exist: $BackupRoot"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $UserProfilePath)) {
|
||||
throw "UserProfilePath does not exist: $UserProfilePath"
|
||||
}
|
||||
|
||||
$DeltaRoot = Join-Path $BackupRoot "_Delta_2026-05-19"
|
||||
$DeltaLogDir = Join-Path $DeltaRoot "00_Logs"
|
||||
New-Item -ItemType Directory -Force -Path $DeltaLogDir | Out-Null
|
||||
|
||||
$CutoffString = $Cutoff.ToString("yyyyMMdd")
|
||||
|
||||
$Manifest = [ordered]@{
|
||||
CreatedAt = (Get-Date).ToString("s")
|
||||
Cutoff = $Cutoff.ToString("s")
|
||||
CutoffNote = "Robocopy /MAXAGE uses date granularity, so files changed on or after the cutoff day are included."
|
||||
RunningSystemDrive = $env:SystemDrive
|
||||
RunningWinDir = $env:windir
|
||||
BackupRoot = $BackupRoot
|
||||
DeltaRoot = $DeltaRoot
|
||||
}
|
||||
|
||||
$Manifest.GetEnumerator() |
|
||||
ForEach-Object { "$($_.Key)=$($_.Value)" } |
|
||||
Out-File (Join-Path $DeltaRoot "delta_manifest.txt") -Encoding UTF8
|
||||
|
||||
$Jobs = @(
|
||||
@{ Source = Join-Path $UserProfilePath "Desktop"; Destination = Join-Path $DeltaRoot "01_Desktop"; Log = "delta_current_desktop.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Documents"; Destination = Join-Path $DeltaRoot "02_Dokumente"; Log = "delta_current_documents.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Pictures"; Destination = Join-Path $DeltaRoot "03_Bilder"; Log = "delta_current_pictures.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Videos"; Destination = Join-Path $DeltaRoot "04_Videos"; Log = "delta_current_videos.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Downloads"; Destination = Join-Path $DeltaRoot "05_Downloads"; Log = "delta_current_downloads.log" },
|
||||
@{ Source = Join-Path $UserProfilePath ".ssh"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\ssh"; Log = "delta_current_ssh.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Subsembly"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Subsembly_Local"; Log = "delta_subsembly_local.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Local"; Log = "delta_buhl_local.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_Local"; Log = "delta_buhl_data_service_local.log" },
|
||||
@{ Source = "C:\ProgramData\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_ProgramData"; Log = "delta_buhl_data_service_programdata.log" },
|
||||
@{ Source = "G:\Gitea_Clone"; Destination = Join-Path $DeltaRoot "06_Projekte\Gitea_Clone"; Log = "delta_g_gitea_clone.log" },
|
||||
@{ Source = "G:\open-webui"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\open-webui"; Log = "delta_g_open_webui.log" },
|
||||
@{ Source = "G:\Treiber"; Destination = Join-Path $DeltaRoot "13_Treiber_Windows\G_Treiber"; Log = "delta_g_treiber.log" },
|
||||
@{ Source = "F:\BMW Leasing"; Destination = Join-Path $DeltaRoot "07_Banking_Finanzen\BMW Leasing"; Log = "delta_f_bmw_leasing.log" },
|
||||
@{ Source = "F:\Marina Handy 2025"; Destination = Join-Path $DeltaRoot "03_Bilder\Marina Handy 2025"; Log = "delta_f_marina_handy_2025.log" }
|
||||
)
|
||||
|
||||
foreach ($Job in $Jobs) {
|
||||
Invoke-RobocopyDelta -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log
|
||||
}
|
||||
|
||||
$ExplicitBankingDir = Join-Path $DeltaRoot "07_Banking_Finanzen\Banking4_Datentresor_explizit"
|
||||
New-Item -ItemType Directory -Force -Path $ExplicitBankingDir | Out-Null
|
||||
|
||||
$BankingFiles = @(
|
||||
(Join-Path $UserProfilePath "Documents\Mein Datentresor.sub"),
|
||||
(Join-Path $UserProfilePath "Documents\.Mein Datentresor.sub.att")
|
||||
)
|
||||
|
||||
foreach ($File in $BankingFiles) {
|
||||
if (Test-Path $File) {
|
||||
Copy-Item -LiteralPath $File -Destination (Join-Path $ExplicitBankingDir (Split-Path $File -Leaf)) -Force
|
||||
}
|
||||
}
|
||||
|
||||
$SummaryRoots = Get-ChildItem $DeltaRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "00_Logs" }
|
||||
$Summary = foreach ($Root in $SummaryRoots) {
|
||||
$Files = @(Get-ChildItem $Root.FullName -Recurse -Force -File -ErrorAction SilentlyContinue)
|
||||
$Measure = $Files | Measure-Object Length -Sum
|
||||
[pscustomobject]@{
|
||||
Path = $Root.FullName
|
||||
Files = $Files.Count
|
||||
SizeGB = [math]::Round(($Measure.Sum / 1GB), 4)
|
||||
Newest = ($Files | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty LastWriteTime)
|
||||
}
|
||||
}
|
||||
|
||||
$Summary | Export-Csv (Join-Path $DeltaLogDir "delta_summary.csv") -NoTypeInformation -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Delta backup finished:"
|
||||
Write-Host " $DeltaRoot"
|
||||
Write-Host ""
|
||||
Write-Host "Summary:"
|
||||
$Summary | Format-Table -AutoSize
|
||||
@@ -0,0 +1,141 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$BackupDir = "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen"
|
||||
$BeforeFile = Join-Path $BackupDir "bcdedit_enum_before_dualboot_cleanup.txt"
|
||||
$AfterFile = Join-Path $BackupDir "bcdedit_enum_after_dualboot_cleanup.txt"
|
||||
|
||||
function Assert-Admin {
|
||||
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$Principal = [Security.Principal.WindowsPrincipal]::new($Identity)
|
||||
if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BcdOsLoaderEntries {
|
||||
$Text = & bcdedit /enum osloader /v
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "bcdedit /enum osloader /v ist fehlgeschlagen."
|
||||
}
|
||||
|
||||
$Entries = @()
|
||||
$Current = $null
|
||||
|
||||
foreach ($Line in $Text) {
|
||||
if ($Line -match "^-{5,}$") {
|
||||
if ($Current -and $Current.Identifier) {
|
||||
$Entries += [pscustomobject]$Current
|
||||
}
|
||||
$Current = @{
|
||||
Identifier = $null
|
||||
Device = $null
|
||||
OsDevice = $null
|
||||
Path = $null
|
||||
Description = $null
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $Current) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($Line -match "^\s*(identifier|Bezeichner)\s+(.+)$") { $Current.Identifier = $Matches[2].Trim(); continue }
|
||||
if ($Line -match "^\s*device\s+(.+)$") { $Current.Device = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*osdevice\s+(.+)$") { $Current.OsDevice = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*path\s+(.+)$") { $Current.Path = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*description\s+(.+)$") { $Current.Description = $Matches[1].Trim(); continue }
|
||||
}
|
||||
|
||||
if ($Current -and $Current.Identifier) {
|
||||
$Entries += [pscustomobject]$Current
|
||||
}
|
||||
|
||||
return $Entries
|
||||
}
|
||||
|
||||
Assert-Admin
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
|
||||
|
||||
& bcdedit /enum all /v | Out-File $BeforeFile -Encoding UTF8
|
||||
|
||||
$Entries = Get-BcdOsLoaderEntries
|
||||
|
||||
Write-Host "Gefundene Windows-Boot-Eintraege:"
|
||||
$Entries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize
|
||||
|
||||
$NewWindows = $Entries | Where-Object {
|
||||
$_.OsDevice -eq "partition=D:" -and
|
||||
$_.Path -match "winload\.efi$" -and
|
||||
(Test-Path "D:\Windows\System32\winload.efi")
|
||||
} | Select-Object -First 1
|
||||
|
||||
$OldWindows = $Entries | Where-Object {
|
||||
$_.OsDevice -eq "partition=C:" -and
|
||||
$_.Path -match "winload\.efi$" -and
|
||||
(Test-Path "C:\Windows\System32\winload.efi")
|
||||
} | Select-Object -First 1
|
||||
|
||||
if (-not $NewWindows) {
|
||||
throw "Abbruch: Neuer Windows-Eintrag auf D:\Windows wurde nicht eindeutig gefunden."
|
||||
}
|
||||
|
||||
if (-not $OldWindows) {
|
||||
throw "Abbruch: Alter Windows-Eintrag auf C:\Windows wurde nicht eindeutig gefunden."
|
||||
}
|
||||
|
||||
$KeepIds = @($NewWindows.Identifier, $OldWindows.Identifier)
|
||||
$DeleteEntries = $Entries | Where-Object { $KeepIds -notcontains $_.Identifier }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Bleibt erhalten:"
|
||||
Write-Host " NEU: $($NewWindows.Identifier) -> $($NewWindows.OsDevice) -> D:\Windows"
|
||||
Write-Host " ALT: $($OldWindows.Identifier) -> $($OldWindows.OsDevice) -> C:\Windows"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Wird aus dem Bootmenue entfernt:"
|
||||
if ($DeleteEntries) {
|
||||
$DeleteEntries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize
|
||||
} else {
|
||||
Write-Host " Keine ueberfluessigen Eintraege gefunden."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Es werden nur Bootmenue-Eintraege geloescht, keine Partitionen und keine Windows-Ordner."
|
||||
$Confirmation = Read-Host "Tippe exakt BOOTCLEAN um fortzufahren"
|
||||
if ($Confirmation -ne "BOOTCLEAN") {
|
||||
throw "Abbruch: Bestaetigung wurde nicht eingegeben."
|
||||
}
|
||||
|
||||
& bcdedit /set $NewWindows.Identifier description "Windows 11 Neu"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte neuen Windows-Eintrag nicht umbenennen." }
|
||||
|
||||
& bcdedit /set $OldWindows.Identifier description "Windows 11 Alt"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte alten Windows-Eintrag nicht umbenennen." }
|
||||
|
||||
& bcdedit /default $NewWindows.Identifier
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte Standard-Boot-Eintrag nicht setzen." }
|
||||
|
||||
& bcdedit /timeout 5
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte Timeout nicht setzen." }
|
||||
|
||||
foreach ($Entry in $DeleteEntries) {
|
||||
Write-Host "Loesche Boot-Eintrag $($Entry.Identifier) ($($Entry.Description))"
|
||||
& bcdedit /delete $Entry.Identifier /f
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Konnte Boot-Eintrag $($Entry.Identifier) nicht loeschen."
|
||||
}
|
||||
}
|
||||
|
||||
& bcdedit /enum all /v | Out-File $AfterFile -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Bootmenue bereinigt."
|
||||
Write-Host "Vorher gesichert: $BeforeFile"
|
||||
Write-Host "Nachher gesichert: $AfterFile"
|
||||
Write-Host ""
|
||||
Write-Host "Bitte neu starten. Es sollten nur noch zwei Eintraege erscheinen:"
|
||||
Write-Host " Windows 11 Neu"
|
||||
Write-Host " Windows 11 Alt"
|
||||
@@ -0,0 +1,120 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$TargetDiskNumber = 0
|
||||
$TargetWindowsDrive = "D"
|
||||
$TargetDiskFriendlyName = "INTEL SSDSC2BW180A3L"
|
||||
$TargetDiskSerialNumber = "CVCV3105053K180EGN"
|
||||
$EfiSizeMB = 260
|
||||
$EfiLabel = "SYSTEM"
|
||||
|
||||
function Assert-Admin {
|
||||
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$Principal = [Security.Principal.WindowsPrincipal]::new($Identity)
|
||||
if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-FreeDriveLetter {
|
||||
$Used = (Get-Volume | Where-Object DriveLetter | Select-Object -ExpandProperty DriveLetter)
|
||||
foreach ($Letter in "S","T","U","V","W","X","Y","Z") {
|
||||
if ($Used -notcontains $Letter) {
|
||||
return $Letter
|
||||
}
|
||||
}
|
||||
throw "Kein freier Laufwerksbuchstabe fuer die EFI-Partition gefunden."
|
||||
}
|
||||
|
||||
Assert-Admin
|
||||
|
||||
$Disk = Get-Disk -Number $TargetDiskNumber
|
||||
$TargetWindowsPath = "$TargetWindowsDrive`:\Windows"
|
||||
$TargetWinload = "$TargetWindowsDrive`:\Windows\System32\winload.efi"
|
||||
|
||||
Write-Host "Aktuell gestartetes Windows:"
|
||||
Write-Host " SystemDrive: $env:SystemDrive"
|
||||
Write-Host " windir: $env:windir"
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Zieldatentraeger:"
|
||||
$Disk | Select-Object Number, FriendlyName, SerialNumber, HealthStatus, OperationalStatus, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}, PartitionStyle | Format-Table -AutoSize
|
||||
|
||||
Write-Host "Partitionen auf Datentraeger ${TargetDiskNumber}:"
|
||||
Get-Partition -DiskNumber $TargetDiskNumber | Sort-Object PartitionNumber | Select-Object DiskNumber, PartitionNumber, DriveLetter, Type, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,3)}}, GptType | Format-Table -AutoSize
|
||||
|
||||
if ($Disk.FriendlyName -ne $TargetDiskFriendlyName) {
|
||||
throw "Abbruch: Datentraeger 0 ist nicht die erwartete Intel-SSD."
|
||||
}
|
||||
|
||||
if ($Disk.SerialNumber -ne $TargetDiskSerialNumber) {
|
||||
throw "Abbruch: Seriennummer von Datentraeger 0 passt nicht."
|
||||
}
|
||||
|
||||
if (-not (Test-Path $TargetWinload)) {
|
||||
throw "Abbruch: Ziel-Windows wurde nicht gefunden: $TargetWinload"
|
||||
}
|
||||
|
||||
if ($env:SystemDrive -eq "$TargetWindowsDrive`:") {
|
||||
throw "Abbruch: Du bist bereits im Ziel-Windows. Dieses Skript ist fuer den Zustand gedacht, in dem neues Windows als D: sichtbar ist."
|
||||
}
|
||||
|
||||
$ExistingEfiOnDisk0 = Get-Partition -DiskNumber $TargetDiskNumber | Where-Object {
|
||||
$_.GptType -eq "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"
|
||||
}
|
||||
|
||||
if ($ExistingEfiOnDisk0) {
|
||||
Write-Host "EFI-Systempartition auf Datentraeger 0 existiert bereits."
|
||||
$EfiPartition = $ExistingEfiOnDisk0 | Select-Object -First 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "Auf Datentraeger 0 existiert keine EFI-Systempartition."
|
||||
Write-Host "Das Skript verkleinert $TargetWindowsDrive`: um $EfiSizeMB MB und erstellt daraus eine neue EFI-Systempartition."
|
||||
Write-Host ""
|
||||
$Confirmation = Read-Host "Tippe exakt EFI um fortzufahren"
|
||||
if ($Confirmation -ne "EFI") {
|
||||
throw "Abbruch: Bestaetigung wurde nicht eingegeben."
|
||||
}
|
||||
|
||||
$TargetPartition = Get-Partition -DiskNumber $TargetDiskNumber | Where-Object DriveLetter -eq $TargetWindowsDrive
|
||||
if (-not $TargetPartition) {
|
||||
throw "Abbruch: Windows-Partition $TargetWindowsDrive`: wurde auf Datentraeger 0 nicht gefunden."
|
||||
}
|
||||
|
||||
$Supported = Get-PartitionSupportedSize -DiskNumber $TargetDiskNumber -PartitionNumber $TargetPartition.PartitionNumber
|
||||
$NewSize = $TargetPartition.Size - ($EfiSizeMB * 1MB)
|
||||
if ($NewSize -lt $Supported.SizeMin) {
|
||||
throw "Abbruch: Partition kann nicht um $EfiSizeMB MB verkleinert werden."
|
||||
}
|
||||
|
||||
Resize-Partition -DiskNumber $TargetDiskNumber -PartitionNumber $TargetPartition.PartitionNumber -Size $NewSize
|
||||
|
||||
$EfiPartition = New-Partition -DiskNumber $TargetDiskNumber -Size ($EfiSizeMB * 1MB) -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"
|
||||
Format-Volume -Partition $EfiPartition -FileSystem FAT32 -NewFileSystemLabel $EfiLabel -Confirm:$false | Out-Null
|
||||
}
|
||||
|
||||
$EfiLetter = Get-FreeDriveLetter
|
||||
Set-Partition -DiskNumber $TargetDiskNumber -PartitionNumber $EfiPartition.PartitionNumber -NewDriveLetter $EfiLetter
|
||||
$EfiDrive = "$EfiLetter`:"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Schreibe Bootdateien fuer $TargetWindowsPath nach $EfiDrive ..."
|
||||
bcdboot "$TargetWindowsPath" /s $EfiDrive /f UEFI
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "bcdboot ist fehlgeschlagen."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Setze Bootmenue-Timeout auf 3 Sekunden."
|
||||
bcdedit /timeout 3
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Aktuelle BCD-Eintraege:"
|
||||
bcdedit /enum
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Fertig. Naechster Schritt:"
|
||||
Write-Host "1. Neustarten."
|
||||
Write-Host "2. Im UEFI/BIOS Windows Boot Manager der Intel-SSD/Datentraeger 0 als erste Bootoption waehlen, falls angeboten."
|
||||
Write-Host "3. Im Bootmenue den neuen Windows-11-Eintrag starten."
|
||||
Write-Host "4. Wenn das neue Windows laeuft, sollte SystemDrive C: sein und der alte Windows-Datentraeger einen anderen Buchstaben haben."
|
||||
Reference in New Issue
Block a user