File System Tunnel und mein Leid mit "CreatedTime" und Rename

Wer Skripte einsetzt und mit parallelen Jobs und Triggern arbeitet, muss doppelte oder parallele Ausführungen verhindern. Sicher gibt es innerhalb von Windows Semaphoren, Events und Messages aber einfach und auch über Systeme hinweg einfach nutzbar sind Dateien, die man anlegt und sperrt.

  • Ein Prozess prüft also, ob es eine LOCK-Datei gibt und wenn nein, legt er sie an und sperrt diese
  • Gibt es die Datei schon, versucht er sie zu sperren und wenn das gelingt, dann kann er ebenfalls weiter machen.
  • Nur wenn eine bestehende Datei schon gelockt ist, muss er davon ausgehen, dass der Prozess schon da ist.

Das ist ja noch einfach und nicht Teil des Problems. Im gleichen Zuge schreibe ich gerne Protokolldateien. Um eine vorherige Version nicht gleich zu überschreiben, lege ich die Datei gerne mit einem anderen Namen an und lösche erst am Enge die alte Datei und benenne die neue Datei um. Das ist übrigens auch ein ganz passabler Weg um über WAN Dateien zu kopieren und sicherzustellen, dass die Datei bei einem Verbindungsabbruch nicht partiell genutzt wird.

In einem Fall hat mich bei diesen Datei aber sowohl das „CreationTime“ als auch das „LastWritten“-Datum interessiert. Ist es doch bei solchen Logdateien ein passabler Indikator, wann ein Prozess gestartet wurde und zuletzt etwas geschrieben hat. Damit lässt sich neben dem Alter (=Aktualität der Ergebnisse) auch die Laufzeit eines Skripts ermitteln. Und dabei ist mir aufgefallen, dass Windows eine kleine aber gemeine Besonderheit hat, die ich zuerst in der PowerShell aber auch in einer CMD-Shell nachvollziehen konnte:

Hinweis:
Der Befehl "Timeout" ist erst seit Windows 7/2008 Bestandteil der CMD

REM Datei1 anlegen und einige Sekunden warten
echo "test" > datei.1
Timeout 2
REM Datei2 anlegen und einige Sekunden warten
echo "test" > datei.2
Timeout 2
REM Datei2 überarbeiten und einige Sekunden warten
echo "test" > datei.2
REM Datei1 löschen
del datei1
REM Datei2 umbenennen
rename datei.2 datei.1

Zuerst haben wir zwei Dateien, die beide ein unterschiedliches „CreationTime“ haben und die zweite Datei hat auch ein aktualisiertes „LastWritten“. Nach dem Rename sollte die neue Datei1 die Zeitstempel der vorherigen Datei2 habe. Die .hat die verbliebene Datei weiterhin das „LastWritten“ der zweiten Datei aber das CreationTime ist die vormals gelöschte Datei.

Das Datum kann man recht einfach in PowerShell ermitteln:

Get-childitem "datei*" | ft name,*time

Das Verhalten ist sowohl in der CMD-Shell, PowerShell und sogar Windows Explorer zu sehen und auch eine Datei, die in der CMD-Shell gelöscht und unter PowerShell mit gleichem Namen angelegt wird, erhält dieser das CreationTime-Flag. Also muss es "tiefer" sein und da ich erst mal glaube, dass Microsoft so einen "Fehler" sicher schon lange behoben hätte, habe ich erst mal gesucht und wurde fündig:

  • KB 172190 Windows NT Contains File System Tunneling Capabilities

Hier ist genau beschrieben, dass es sich dabei nicht um einen Fehler sondern um ein Feature handelt. Wird eine Datei innerhalb von 15 Sekunden in einem Verzeichnis gelöscht und wieder neu angelegt oder über ein "Rename" gesetzt, dann bekommt diese das "CreationTime" der kurz zuvor gelöschten Datei. Windows speichert sich für jedes Verzeichnis eine Liste der gelöschten Dateien und ihres CreationTime.

Als kritisch würde ich das nicht bezeichnen aber meine Skripte muss ich natürlich anpassen, damit die „Laufzeitberechnung“ weiterhin korrekt ist. Aber zumindest aus der PowerShell lässt sich sehr einfach das Dateidatum der temporären Datei auslesen und nach dem Rename wieder auf die Datei zurück schreiben. Hier ein Beispiel:

$file1 = "c:\temp\file1.txt"
$file2 = "c:\temp\file2.txt"
$CreationTime = (get-childitem $file1).Creationtime
remove-item $file1
rename-item $file2 file1
[System.io.File]::SetCreationTime(($file1),$CreationTime)

Unschön ist es aber doch, da ich so ein "Verhalten" natürlich erst mal nicht erwartet habe.

Effekt beim Überschreiben

Steve Jobs sagte am Ende einer Präsentation manchmal ein "Einen habe ich noch". Das Thema mit dem CreationTime hat noch einen Einfluss auf andere Prozesse. Wer eine Datei durch "Überschreiben" neu anlegt, behält auch das gleiche "CreationTime".

REM Anlegen einer neuen Datei und 2 Sekunden Pause
echo "test" > c:\temp\test.txt
Timeout 2
REM ändern der Datei und 2 Sekunden Pause zum Aendern des "LastWriteTime"
echo "test" >> c:\temp\test.txt
Timeout 2
REM Überschreiben der Datei
echo "test" > c:\temp\test.txt

Die CreationTime der Datei bleibt die alte Datei. Nun könnte man sagen, dass die Datei ja nie gelöscht und neu angelegt sondern wirklich überschrieben wird. Das ändert aber nichts daran, dass selbst ein Löschen der Datei ja nichts hilft, da der Windows Cache bei der gleichnamigen Datei wieder die CreationTime auf den alten Wert setzt. Auch Out-File von PowerShell ist betroffen.

"test" | out-file .\test.txt
get-childitem .\test.exe | ft *time
Start-Sleep 2

"test" | out-file .\test.txt -append
get-childitem .\test.exe | ft *time
Start-Sleep 2

"test" | out-file .\test.txt
get-childitem .\test.exe | ft *time
Start-Sleep 2

Quintessenz kann nur lauten: Traue nicht dem CreationTime, wenn die Datei kann schon lange ersetzt worden sein.

Weitere Links