Zeitstempel von Ordnern wiederherstellen

Verschiebt man Dateien von einem Datenträger zu einem anderen, so bleibt zwar der Zeitstempel der Dateien erhalten. Die Ordner jedoch erhalten einem Zeitstempel mit dem Datum der Verschiebung. Das ist ärgerlich, wenn man z.B. Dateien anhand eines Zeitkriteriums wiederfinden möchte.

Das folgende Powershell-Skript löst das Problem. Es setzt z.B. die Zeitstempel der Ordner auf den Zeitstempel der neuesten enthaltenen Datei bzw. des neuesten enthaltenen Ordners. Es bietet darüber hinaus weitere Optionen wie die eines Testlaufs mit "-WhatIf".

<#
.SYNOPSIS
Sets folder timestamps based on the newest or oldest timestamp of contained files and folders.

.DESCRIPTION
For each target folder (all subfolders under -RootPath; optionally including the root folder),
the selected source timestamp (LastWriteTime/CreationTime/LastAccessTime) is determined from
the contained items (files AND folders, recursively) and applied as the target timestamp
to the folder. You can choose either the newest (Max) or the oldest (Min) timestamp.

.PARAMETER RootPath
Root path under which target folders are searched. With -IncludeRoot, the root folder
itself can also be modified. If omitted, the full help is displayed.

.PARAMETER SourceTimestamp
Specifies which timestamp of the contained items should be used as the source.
Valid values: LastWriteTime (default), CreationTime, LastAccessTime.

.PARAMETER TargetTimestamp
Specifies which timestamp should be set on the target folder.
Valid values: LastWriteTime (default), CreationTime, LastAccessTime.

.PARAMETER Pick
Specifies whether the newest ('Max', default) or the oldest ('Min') candidate is selected.

.PARAMETER IncludeRoot
Also apply the change to the root folder itself.

.PARAMETER TopLevelOnly
Use only direct subfolders as target folders (the scan inside each target folder
remains recursive).

.EXAMPLE
.\Set-FolderDateFromFilesAndFolders.ps1 -RootPath 'D:\Daten\Projekte'

Sets the LastWriteTime of all subfolders to the newest timestamp of their contained items.

.EXAMPLE
.\Set-FolderDateFromFilesAndFolders.ps1 -RootPath 'D:\Daten\Projekte' -Pick Min -TargetTimestamp CreationTime

Sets the CreationTime of the folders to the oldest timestamp of their contained items.

.EXAMPLE
.\Set-FolderDateFromFilesAndFolders.ps1 -RootPath 'D:\Daten' -TopLevelOnly -IncludeRoot -WhatIf -Verbose

Shows what would change (WhatIf), logs details (Verbose), and includes the root folder.

.NOTES
Author: Dirk Billand
Version: 1.0
Copyright: 2026
License: MIT
Compatible with Windows PowerShell 5.1 and PowerShell 7+. Supports -WhatIf/-Confirm.
#>

[CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false)]
param(
  [Parameter(HelpMessage = 'Wurzelpfad mit den zu aktualisierenden Ordnern')]
  [string]$RootPath,

  # Welcher Zeitstempel der enthaltenen Elemente (Dateien/Ordner) soll als Quelle dienen?
  [ValidateSet('LastWriteTime','CreationTime','LastAccessTime')]
  [string]$SourceTimestamp = 'LastWriteTime',

  # Welchen Zeitstempel setzen wir am Zielordner?
  [ValidateSet('LastWriteTime','CreationTime','LastAccessTime')]
  [string]$TargetTimestamp = 'LastWriteTime',

  # Neuester (Max) oder ältester (Min) Kandidat?
  [ValidateSet('Max','Min')]
  [string]$Pick = 'Max',

  # Root-Ordner selbst auch anpassen?
  [switch]$IncludeRoot,

  # Nur direkte Unterordner als Zielordner (aber innerhalb jedes Zielordners wird rekursiv gescannt)
  [switch]$TopLevelOnly
)

# Bei Aufruf ohne RootPath: Vollhilfe anzeigen und beenden
if (-not $PSBoundParameters.ContainsKey('RootPath') -or [string]::IsNullOrWhiteSpace($RootPath)) {
  try {
    Get-Help -Name $MyInvocation.MyCommand.Path -Full | Out-Host
  } catch {
    Write-Host ''
    Write-Host 'Usage:' -ForegroundColor Cyan
    Write-Host '  .\Set-FolderDateFromFilesAndFolders.ps1 -RootPath <Pfad> [-SourceTimestamp LastWriteTime|CreationTime|LastAccessTime] `'
    Write-Host '                                             [-TargetTimestamp LastWriteTime|CreationTime|LastAccessTime] [-Pick Max|Min] `'
    Write-Host '                                             [-IncludeRoot] [-TopLevelOnly]' -ForegroundColor Gray
    Write-Host ''
  }
  return
}

function Set-FolderTimestamp {
  param(
    [System.IO.DirectoryInfo]$Folder,
    [datetime]$Stamp
  )
  switch ($TargetTimestamp) {
    'LastWriteTime'  { (Get-Item -LiteralPath $Folder.FullName).LastWriteTime  = $Stamp }
    'CreationTime'   { (Get-Item -LiteralPath $Folder.FullName).CreationTime   = $Stamp }
    'LastAccessTime' { (Get-Item -LiteralPath $Folder.FullName).LastAccessTime = $Stamp }
  }
}

function Get-PathDepth {
  param([string]$Path)
  # Tiefe robust bestimmen (unterstützt \ und /)
  $p = [System.IO.Path]::GetFullPath($Path).TrimEnd('\','/')
  return ($p -split '[\\/]').Count
}

# Zielordner einsammeln
if ($TopLevelOnly) {
  $folders = Get-ChildItem -LiteralPath $RootPath -Directory -ErrorAction SilentlyContinue
} else {
  $folders = Get-ChildItem -LiteralPath $RootPath -Directory -Recurse -ErrorAction SilentlyContinue
}

# Root optional hinzufügen
if ($IncludeRoot) {
  $rootDir = Get-Item -LiteralPath $RootPath
  if ($folders -notcontains $rootDir) { $folders = @($rootDir) + $folders }
}

# *** WICHTIG: Bottom-up sortieren (tiefste Ordner zuerst) ***
$folders = $folders | Sort-Object -Property { Get-PathDepth $_.FullName }, FullName -Descending

foreach ($folder in $folders) {

  # Kandidaten = Dateien und Ordner innerhalb (rekursiv)
  $candidates = Get-ChildItem -LiteralPath $folder.FullName -Recurse -ErrorAction SilentlyContinue

  if (-not $candidates -or $candidates.Count -eq 0) {
    Write-Verbose ('Keine Dateien/Ordner in ''{0}'' gefunden – übersprungen.' -f $folder.FullName)
    continue
  }

  # Max/Min je nach $SourceTimestamp wählen
  $selected = if ($Pick -eq 'Max') {
    $candidates | Sort-Object -Property $SourceTimestamp -Descending | Select-Object -First 1
  } else {
    $candidates | Sort-Object -Property $SourceTimestamp | Select-Object -First 1
  }

  $newStamp = $selected.$SourceTimestamp

  if ($PSCmdlet.ShouldProcess($folder.FullName, ('Set {0} = {1} (aus Element ''{2}'')' -f $TargetTimestamp, $newStamp, $selected.FullName))) {
    Set-FolderTimestamp -Folder $folder -Stamp $newStamp

    # Zeitstempel formatiert, ganz ohne doppelte Anführungszeichen
    $ts = $newStamp.ToString('yyyy-MM-dd HH:mm:ss', [System.Globalization.CultureInfo]::InvariantCulture)
    Write-Host ([string]::Format('[{0}] {1}', $ts, $folder.FullName))
  }
}