PowerShell Touch
Ich stand vor demselben Problem und habe mir ein PowerShell CmdLet zusammengestellt, dass als würdiger touch
-Ersatz in der Kommandozeile genutzt werden kann.
Im Unternehmen räumen wir alte Files knallhart auf: alles, was älter als 10 Jahre ist, wird vom Fileserver gelöscht. Schon alleine, als weitere Sicherungslinie, um die DSGVO umzusetzen, falls andere Prüfverfahren Dokumente übersehen haben. Nach 10 Jahren sind alle relevanten Verjährungsfristen soweit abgelaufen. Das klappt ganz gut, da die Mitarbeiter angehalten sind, relevante Dokumente im DMS abzulegen. Also kann sich, zumindest per Definition, nichts wichtiges auf den Fileservern befinden.
Um alte Dateien dennoch vor dem Shredder zu bewahren, muss das letzte Schreibdatum auf einen Wert jünger als 10 Jahre zurückgesetzt werden. Leider hat Windows kein vernünftiges touch
-Tool um die verschiedenen Zeitstempel von Dateien einfach anzupassen, wie beispielsweise Linux. Das lässt sich zwar alles nachrüsten, aber spätestens bei PowerShell Skripten gibt es in der Anwendung einen Stilbruch.
Um die Zeitstempel anzupassen müssen Sie statt eines CmdLet das Tool Ihrer Wahl aufrufen. Das funktioniert zwar auch und bringt Sie auch zum Ziel, ist aber irgendwie unschön.
Alternativ können Sie natürlich das Datum über das FileInfo-Objekt selber setzen, aber auch das erfindet jedes mal das Rad neu, weil doch mehr als nur ein bequemer Befehl notwendig ist.
Der Weg zur Lösung
PowerShell kann das selbstverständlich mit Bordmitteln erledigen, aber es gibt kein CmdLet, dass die gesamte Arbeit abnimmt. Ich habe im Web zwar einige CmdLets gefunden, die den Job übernehmen können, aber die haben mir alle nicht so ganz gefallen.
Vor allem habe ich kein CmdLet gefunden, dass Pipelining unterstützt. Das wiederum war mir wichtig, weil ich damit zur Auswahl der zu verändernden Dateien Get-ChildItem
bemühen kann und das Rad nicht nochmals erfinden muss.
Umsetzung
Das PowerShell CmdLet kann jetzt also nicht nur mit Dateien umgehen, die ihm als Parameter übergeben werden, sondern auch mit der Pipe:
PS C:\> Update-FileInfo *.txt
PS C:\> Get-ChildItem *.txt | Update-FileInfo
Bei den Parametern habe ich den Datentyp von $Path
offen gelassen, um diesen Parameter sowohl direkt als String, wie auch über die Pipeline, verwenden zu können. Get-ChildItem
liefert eine Reihe von Objekttypen, abhängig vom Provider, zurück. Eine entsprechende Unterscheidung findet dann später im CmdLet statt.
Im Skript habe ich mich für eine Quick&Dirty Lösung entschieden: es kann nur mit Objekten vom Typ String
und FileInfo
umgehen, bei allen anderen Objekten wird eine Fehlermeldung ausgegeben. Ich wüßte aber im Moment auch nicht, ob es wirklich noch weitere sinnvolle Objekttypen an dieser Stelle gibt.
# (c) Michael Grabowski
# Version 1.0.0 vom 01.11.2019
Function Update-FileTime {
Param (
[Parameter(Mandatory = $true, ValueFromPipeLine = $true)]
$Path,
[Parameter(Mandatory = $false)]
[string] $DesiredTime = (Get-Date),
[Parameter(Mandatory = $false)]
[switch] $SetCreationTime = $false,
[Parameter(Mandatory = $false)]
[switch] $SetLastAccessTime = $false,
[Parameter(Mandatory = $false)]
[switch] $SetLastWriteTime = $false,
[Parameter(Mandatory = $false)]
[switch] $AlterDirectory = $false
)
Begin {
If (!$SetCreationTime -and !$SetLastAccessTime -and !$SetLastWriteTime) {
$SetLastAccessTime = $true
}
Try {
[datetime] $_desiredTime = Get-Date -Date $DesiredTime
}
Catch {
throw ("Update-FileTime : {0}" -f $PSItem.Exception.Message)
}
}
Process {
Switch ($Path.GetType().Name) {
"String" { $fileList = Get-ChildItem -Path $Path }
"FileInfo" { $fileList = $Path }
"DirectoryInfo" { If ($AlterDirectory) { $fileList = $Path } Else { $fileList = @() } }
default { Write-Warning ("Type {0} is not supported." -f $Path.GetType().Name); $fileList = @() }
}
ForEach($fileItem in $fileList) {
If ($SetCreationTime) { $fileItem.CreationTime = $_desiredTime }
If ($SetLastAccessTime) { $fileItem.LastAccessTime = $_desiredTime }
If ($SetLastWriteTime) { $fileItem.LastWriteTime = $_desiredTime }
return $fileItem
}
}
End {
}
}
Demo
Die Anwendung des CmdLets ist, alleine schon mangels vieler Optionen, simpel. Ich habe das bewusst so gehalten, da die Dateiauswahl mit Hilfe von Get-ChildItem
viel flexibler gestaltet werden kann und damit ja auch schon ein sehr gutes Werkzeug dafür zur Verfügung steht.