Benutzer mit den meisten Antworten
Doppeltes Unterverzeichnis entfernen

Frage
-
Hallo liebes Forum,
ich mache gerade ein Praktikum und habe da die Aufgabe bekommen ein Skript zu schreiben um Verzeichnisse nach alten Dateien zu durchforsten und wenn sie ein bestimmtes Alter überschritten haben zu verschieben. Soweit so gut. Ich habe mich durch diverse Webseiten und Forenbeiträge inspirieren lassen und bin zu diesem Ergebnis gekommen:
############################################# # Skript zum Verschieben alter Verzeichnisse # mit Log-File aber ohne xml ############################################# # Variablen #Quellordner $sourcePath = "d:\temp" #Zielordner $targetPath = "d:\temp\!Archiv" #Logfiles $LogsPath = "d:\temp\!Archiv\!Logfiles" #Mindestalter der Dateien im Verzeichnis $Monate = 20 #Sortierung $Sort = "LastWriteTime" $Folders = (Get-ChildItem $sourcePath -Recurse | Where {$_.PSIsContainer -eq $True} | Where {$_.GetFiles().Count -ne 0} | Where-Object { -not ($_.FullName -like "$targetPath*")}).FullName foreach ($Folder in $Folders) { $File = @(Get-ChildItem $Folder -File | Where {$_.LastWriteTime -lt (get-date).AddMonths(-$Monate)} | Sort-Object $Sort)[-1] if ($File) { $Path = $Folder -ireplace [regex]::Escape($sourcePath),$null $oldPath = $sourcePath+$Path $newPath = $targetPath+$Path echo "Es wird von $oldPath nach $newPath verschoben" if (!(Test-path $newPath)){md $newPath} Move-Item -Path $oldPath -Destination $newPath -Verbose -Force *>&1 | Out-File -FilePath $LogsPath\movelog-$((get-date).ToString("dd-MM-yyyy-HH_mm_ss")).txt -Append } else { $Path = $Folder -ireplace [regex]::Escape($sourcePath),$null $oldPath = $sourcePath+$Path echo "Keine alten Dateien in $oldPath gefunden" | Out-File -FilePath $LogsPath\leavelog-$((get-date).ToString("dd-MM-yyyy-HH_mm_ss")).txt -Append } }
Nicht schön, aber selten...
Nun mein eigentliches Problem:
Verschoben sollten die Datein in \!Archiv\KundeXX\ReleaseXX werden. Wo die Dateien aber tatsächlich landen ist \!Archiv\KundeXX\ReleaseXX\ReleaseXX
Kunde und Release sind Platzhalter und heißen leider nicht so im aktuellen Verzeichnis, also habe ich keinen Anhaltspunkt für einen Filter.
Hat irgendeiner einen Ansatz oder einen Code-Schnipsel der mich an mein Ziel bringt?
Danke schonmal im Voraus
HJE
Antworten
-
Nicht schön, aber selten...
... ;-) so selten ist das gar nicht, wie ich glaube. :-D
Ich habe mal versucht, die eigentliche Aufgabe als Code etwas zu "streamlinen" ... :-D
$sourcePath = "d:\temp" $targetPath = "d:\temp\!Archiv" $Monate = 20 $Stichtag = (get-date).AddMonths( - $Monate) Get-ChildItem -Path $sourcePath -Recurse -File | Where-Object {$_.LastWriteTime -lt $Stichtag} | ForEach-Object { $Release = Split-Path -Path (Split-Path -Path $_.FullName) -Leaf $Kunde = Split-Path -Path (Split-Path -Path (Split-Path -Path $_.FullName)) -Leaf $Destination = Join-Path -Path (Join-Path -Path $targetPath -ChildPath $Kunde ) -ChildPath $Release $Destination Move-Item -Path $_.FullName -Destination $Destination }
Du fehlen jetzt natürlich noch Fehlerbehandlung und Logging, aber Du sollst ja auch was lernen und dafür ist es immer besser, wenn man es selber macht. :-) ;-)
Da die Powershell-cmdlets ziemlich "sprechend" sind, ist es übrigens nicht nötig so viel im Code zu kommentieren. ;-)
Best regards,
(79,108,97,102|%{[char]$_})-join''
- Bearbeitet BOfH-666 Dienstag, 29. Mai 2018 12:33
- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
-
Du liegst richtig, dass ! dem -not entspricht. Allerdings kann man das schnell mal überlesen. Es gibt deshalb auch "Best Practices", welche du unter anderem hier finden kannst. Es sind nur Best Practices, sind aber unglaublich nützlich wenn andere deine Skripts noch verstehen müssen.
Zu deinem zweiten Anliegen: Das Sort-Object ist nur für das Sortieren von Objekten nach einer Eigenschaft verantwortlich. Dies hat nichts mit Ausschliessen von Objekten zu tun, denn du musst diese filtern nach einer Eigenschaft.
Es ist aber auch richtig, dass neuere Dateien als das Stichdatum im Ordner erhalten bleiben. Durch den Filter auf das Schreibdatum von vor 20 Monaten gehst du nur an die Objekte bzw. in deinem Fall Dateien, welche auch wirklich zuletzt vor 20 Monaten geändert wurden. Und nur diese werden dann verschoben. Wenn du für die anderen Dateien etwas anderes planst, musst du dies andersweitig beschreiben.- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
-
Hab mal drüber nachgedacht, wie ich das lösen würde - sieht dann sinngemäß so aus:
$sourcePath = "d:\temp" $targetPath = "d:\temp\!Archiv" $Monate = 24 $Stichtag = (get-date).AddMonths( - $Monate) $Kunden = Get-ChildItem $SourcePath -Directory -Exclude $( Split-Path $TargetPath -Leaf ) ForEach ( $Kunde in $Kunden ) { $TargetKunde = New-Item "$targetPath\$($Split-Path $Kunde -Leaf)" -ItemType Directory $Releases = $Kunde.GetDirectories() ForEach ( $Release in $Releases ) { If ( $Release.LastWriteTime -lt $Stichtag ) { $TargetRelease = New-Item "$TargetKunde\$(Split-Path $Release -Leaf)" -ItemType Directory $Release.GetFiles() | Move-Item -Destination $TargetRelease $Release.Delete( $True ) } } }
Wenn es unterhalb von $Release noch Unterordner geben sollte, mußt Du ein wenig umbauen, $Release.GetFiles() ist nicht rekursiv.
- Bearbeitet Martin Binder Mittwoch, 30. Mai 2018 11:48 ...
- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
Alle Antworten
-
Nicht schön, aber selten...
... ;-) so selten ist das gar nicht, wie ich glaube. :-D
Ich habe mal versucht, die eigentliche Aufgabe als Code etwas zu "streamlinen" ... :-D
$sourcePath = "d:\temp" $targetPath = "d:\temp\!Archiv" $Monate = 20 $Stichtag = (get-date).AddMonths( - $Monate) Get-ChildItem -Path $sourcePath -Recurse -File | Where-Object {$_.LastWriteTime -lt $Stichtag} | ForEach-Object { $Release = Split-Path -Path (Split-Path -Path $_.FullName) -Leaf $Kunde = Split-Path -Path (Split-Path -Path (Split-Path -Path $_.FullName)) -Leaf $Destination = Join-Path -Path (Join-Path -Path $targetPath -ChildPath $Kunde ) -ChildPath $Release $Destination Move-Item -Path $_.FullName -Destination $Destination }
Du fehlen jetzt natürlich noch Fehlerbehandlung und Logging, aber Du sollst ja auch was lernen und dafür ist es immer besser, wenn man es selber macht. :-) ;-)
Da die Powershell-cmdlets ziemlich "sprechend" sind, ist es übrigens nicht nötig so viel im Code zu kommentieren. ;-)
Best regards,
(79,108,97,102|%{[char]$_})-join''
- Bearbeitet BOfH-666 Dienstag, 29. Mai 2018 12:33
- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
-
Danke für die schnelle Antwort. Die Code-Zeile mit if (!(Test-path $Destination)){md $Destination} nach $Destination habe ich schonmal eingefügt und die Fehlermeldung ist weg. Jetzt muss ich nur noch die leeren Verzeichnisse unter \Kunde\Release löschen und ein einfach lesbares Log erstellen.
-
Danke für die schnelle Antwort. Die Code-Zeile mit if (!(Test-path $Destination)){md $Destination} nach $Destination habe ich schonmal eingefügt und die Fehlermeldung ist weg.
Wenn Du's schön machen möchtest, nimmst Du:
if (-not(Test-Path -Path $Destination)){New-Item -Path $Destination -ItemType Directory}
Aliasse wie md sind eher was für die interaktive Konsole. In Skripts ist das eher sehr schlechter Stil.
... und ein einfach lesbares Log erstellen.
Best regards,
(79,108,97,102|%{[char]$_})-join''
- Bearbeitet BOfH-666 Dienstag, 29. Mai 2018 13:19
-
New-Item -Path anstelle des Alias md verstehe ich, aber ! entspricht ja -not, richtig? Oder ist das auch schlechter Stil?
Und an welcher Stelle muss ich die Sortierung nach neuester Datei einfügen, weil so wie das gerade steht verschiebt er mir nur die Dateien in den jeweiligen Verzeichnissen die der Altersvorgabe entsprechen. Wenn ich jetzt aber neuere Dateien in den Release-Ordnern habe bleiben die zurück und wandern nicht mit ins Archiv, was mir das Release zerstört. Dafür hatte ich ja den Sort-Befehl in meinem Array um die entsprechenden Ordner auszuschliessen.
-
... und ein einfach lesbares Log erstellen.
Dafür gibt es im Internet quasi fertige / ntzbare Lösungen, die Du einfach in eigene Skripte oder Module integrieren kannst. Da muss man ja nicht jedes mal das Rad neu erfinden. ;-)
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
Du liegst richtig, dass ! dem -not entspricht. Allerdings kann man das schnell mal überlesen. Es gibt deshalb auch "Best Practices", welche du unter anderem hier finden kannst. Es sind nur Best Practices, sind aber unglaublich nützlich wenn andere deine Skripts noch verstehen müssen.
Zu deinem zweiten Anliegen: Das Sort-Object ist nur für das Sortieren von Objekten nach einer Eigenschaft verantwortlich. Dies hat nichts mit Ausschliessen von Objekten zu tun, denn du musst diese filtern nach einer Eigenschaft.
Es ist aber auch richtig, dass neuere Dateien als das Stichdatum im Ordner erhalten bleiben. Durch den Filter auf das Schreibdatum von vor 20 Monaten gehst du nur an die Objekte bzw. in deinem Fall Dateien, welche auch wirklich zuletzt vor 20 Monaten geändert wurden. Und nur diese werden dann verschoben. Wenn du für die anderen Dateien etwas anderes planst, musst du dies andersweitig beschreiben.- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
-
Dabei hab ich kürzlich gelesen, dass Google immer mehr "Künstliche Intelligenz" freischaltet, um uns besser zu unterstützen. ;-)
Für einen Powersheller ist natürliche immer das Microsoft Script Center eine gute Anlaufstelle und natürlich die Powershell Gallery. Die kannst Du sogar aus der Konsole antriggern:
Find-Module *logging* # .. oder wenn Du viel Zeit hast: Find-Module *log* # ... oder so geht's auch Find-Module -Tag 'logging'
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
Danke erstmal für die Links.
Hier mal ein Zwischenschritt, allerdings noch ohne Log und mit leeren Ausgangs-Verzeichnissen.
$sourcePath = "d:\temp" $targetPath = "d:\temp\!Archiv" $Monate = 24 $Stichtag = (get-date).AddMonths( - $Monate) $Folders = (Get-ChildItem $sourcePath -Recurse | Where {$_.PSIsContainer -eq $True} | Where {$_.GetFiles().Count -ne 0} | Where-Object { -not ($_.FullName -like "$targetPath*")}).FullName foreach ($Folder in $Folders) { $File = @(Get-ChildItem $Folder -File | Sort-Object $Sort)[-1] if ($File){ $FilePath = @($File.DirectoryName | Where {$File.LastWriteTime -lt $Stichtag}) } if ($FilePath){ Get-ChildItem -Path $sourcePath -Recurse -File | Where-Object { -not ($_.FullName -like "$targetPath*")} | Where-Object { ($_.FullName -like "$FilePath*")} | ForEach-Object { $Release = Split-Path -Path (Split-Path -Path $_.FullName) -Leaf $Kunde = Split-Path -Path (Split-Path -Path (Split-Path -Path $_.FullName)) -Leaf $Destination = Join-Path -Path (Join-Path -Path $targetPath -ChildPath $Kunde ) -ChildPath $Release $Destination if (-not (Test-Path -Path $Destination) ) {New-Item -Path $Destination -ItemType Directory} Move-Item -Path $_.FullName -Destination $Destination #-WhatIf } } }
Streamlining und andere Optimierungstipps sind immer gerne gesehen und bitte eine Erklärung warum der Ausgangsordner leer zurück bleibt und nicht mit verschoben wird.
Und dann noch ein Skript um leere Unterordner aufzuspüren und zu löschen. Meine Suche hat leider nichts für mich verwertbares ergeben, wahrscheinlich habe ich die falschen Suchbegriffe eingegeben.
-
Ich hätte Dir noch ein paar Anregungen als Optimierungsmaßnahmen :-) Wenn ich Denkfehler habe, wird mich der BOfH_666 sicher gerne korrigieren.
$targetPath = "d:\temp\!Archiv"
Nicht ganz ideal, wenn das Ziel innerhalb der zu untersuchenden Struktur liegt - ich würde das woanders unterbringen. Aber ok.
$Folders = (Get-ChildItem $sourcePath -Recurse | Where {$_.PSIsContainer -eq $True} | Where {$_.GetFiles().Count -ne 0} | Where-Object { -not ($_.FullName -like "$targetPath*")}).FullName
Filtern immer so weit links wie möglich. "Get-ChildItem -Directory -Exclude $( Split-Path $TargetPath -Leaf)" erspart Dir schon mal 2 dieser Where-Filter. Und warum steckst Du in $Folders nicht die Ordner-Objekte, sondern nur Strings? ( .FullName ganz hinten...)
foreach ($Folder in $Folders) {
$File = @(Get-ChildItem $Folder -File | Sort-Object $Sort)[-1]
if ($File){
$FilePath = @($File.DirectoryName | Where {$File.LastWriteTime -lt $Stichtag})
}Damit suchst Du ja nur, ob im jeweiligen Ordner eine Datei älter als Stichtag enthalten ist, oder? Das Sort-Object kannst Du weglassen - $Sort ist leer, damit holst Du halt eine Datei - und daß eine Datei drin ist, weißt Du schon ($_.GetFiles().Count -ne 0). Und unnötig ist es eigentlich auch, denn: Bei Ordnern ist LastWriteTime die LastWriteTime der neuesten Datei darin. Damit wäre das zu ersetzen durch
If ( $Folder.LastWriteTime -lt $Stichtag ) { ... }
> if ($FilePath){Get-ChildItem -Path $sourcePath -Recurse -File |
Warum fängst Du hier die Suche wieder oben an? Du ackerst Dich doch grad durch Unterordner durch, -Path $Filepath wäre geeigneter.
Where-Object { -not ($_.FullName -like "$targetPath*")} |
Kann entfallen, die sind in $Folders schon aussortiert.
Where-Object { ($_.FullName -like "$FilePath*")} |
Kann entfallen, wenn Du 2 Zeilen weiter oben nicht $sourcePath verwendest, sondern $FilePath.
"$Get-ChildItem | Foreach-Object { #Do something }" ist immer langsamer als "$Items = Get-ChildItem; Foreach ($Item in $Items) { #Do something }". Pipeline ist ok für manuell ausgeführte Commands, in Skripts solltest Du sie meist vermeiden.
ForEach-Object {
$Release = Split-Path -Path (Split-Path -Path $_.FullName) -Leaf
$Kunde = Split-Path -Path (Split-Path -Path (Split-Path -Path $_.FullName)) -LeafDa würde ich nicht Split-Path verwenden, sondern direkt Split (oder einen Match). Ist eine Zeile mehr, aber übersichtlicher:
$Parts = $_.Fullname -Split '\\' (doppelt, weil das escaped werden muß)
$Release = $Parts[-2]
$Kunde = $Parts[-3]Geht mit RegEx bestimmt noch eleganter, aber das krieg ich auswendig nicht hin, RegEx ist nicht so meins :-))
$Destination = Join-Path -Path (Join-Path -Path $targetPath -ChildPath $Kunde ) -ChildPath $Release
...der Lesbarkeit halber nicht Join-Path verwenden, sondern den String direkt zusammenbauen:
$Destination = '{0}\{1}\{2}' -f $TargetPath, $Kunde, $Release
$Destination
if (-not (Test-Path -Path $Destination) ) {New-Item -Path $Destination -ItemType Directory}
Move-Item -Path $_.FullName -Destination $Destination #-WhatIf
}
}
}
[/code]
warum der Ausgangsordner leer zurück bleibt und nicht mit verschoben wird.Weil Du das nigends machst. Du verschiebst nur Dateien.
Und dann noch ein Skript um leere Unterordner aufzuspüren und zu löschen.
Das hast doch in der $Folders-Ermittlung oben quasi schon andersrum drin.
-
Hab mal drüber nachgedacht, wie ich das lösen würde - sieht dann sinngemäß so aus:
$sourcePath = "d:\temp" $targetPath = "d:\temp\!Archiv" $Monate = 24 $Stichtag = (get-date).AddMonths( - $Monate) $Kunden = Get-ChildItem $SourcePath -Directory -Exclude $( Split-Path $TargetPath -Leaf ) ForEach ( $Kunde in $Kunden ) { $TargetKunde = New-Item "$targetPath\$($Split-Path $Kunde -Leaf)" -ItemType Directory $Releases = $Kunde.GetDirectories() ForEach ( $Release in $Releases ) { If ( $Release.LastWriteTime -lt $Stichtag ) { $TargetRelease = New-Item "$TargetKunde\$(Split-Path $Release -Leaf)" -ItemType Directory $Release.GetFiles() | Move-Item -Destination $TargetRelease $Release.Delete( $True ) } } }
Wenn es unterhalb von $Release noch Unterordner geben sollte, mußt Du ein wenig umbauen, $Release.GetFiles() ist nicht rekursiv.
- Bearbeitet Martin Binder Mittwoch, 30. Mai 2018 11:48 ...
- Als Antwort markiert Denniver ReiningMVP, Moderator Mittwoch, 13. Juni 2018 10:54
-
Puh .... da war der Martin schneller ... Gott sei Dank. ;-) :-P
Warum Du nicht einfach mein Beispiel an Deine Bedürfnisse angepasst hast und stattdessen Dich so aufwändig durch die Ordnerstruktur ackerst, hab ich auch nicht verstanden. Wenn Du Source und Target in unterschiedliche Basisordner packst, sollte mein Code ohne Anpassung laufen, oder?
Die Split-Path's und Join-Path's waren von mir aber Absicht, weil ich für einen Anfänger am Anfang möglichst bei einfachem puren Powershell bleiben wollte. Wenn's ohne Regex geht, mach ich's meistens auch ohne.
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
\Verzeichnis\!Archiv
\Verzeichnis\Kunde1\Release1
\Verzeichnis\Kunde1\Release2
\Verzeichnis\Kunde2\Release1 ...ist vorgegeben.
$Folders = (Get-ChildItem $Path -Recurse -Directory).FullName foreach ($Folder in $Folders) { $Folder $File = @(Get-ChildItem $Folder -File | Sort-Object $Sort)[-1] if ($File) { "{0} {1}" -f $File.LastWriteTime, $File.Name }
Habe ich mir von function Get-NewestFileforEachFolder entlehnt. Und die Funktion untersucht ob alle Dateien in dem Ordner älter als der Stichtag sind.
Ich habe mehrere Versionen ausprobiert und eine wollte mir sämtliche Dateien ab d:\ verschieben die den Anforderungen entsprechen.
Ich habe nur halbwegs eine Ahnung was die einzelnen Teile meines Skripts bewirken. Ich bastel einfach etwas herum und hoffe, dass ich mir mit meinen Experimenten nicht meinen PC abschiesse...
Also nochmal zur Erklärung was ich mit meinem Skript eigentlich erreichen will.
\Verzeichnis\!Archiv - hierhin sollen alle Ordner mit abgelaufenem Haltbarkeitsdatum verschoben werden
\Verzeichnis\KundeX\ReleaseX - diese Ordner sollen untersucht werden. Wenn alle Dateien in einem Release-Ordner das Haltbarleitsdatum überschritten haben soll der Ordner unter Beibehaltung der Verzeichnisstruktur verschoben werden. Also \verzeichnis\kunde\release nach \verzeichnis\!Archiv\kunde\release
Der ganze Vorgang soll protokolliert werden und im nächsten Schritt soll ich dann die Parameter aus einer xml-Datei ziehen.
-
Ich habe es versucht und bin kläglich gescheitert, deshalb mein rumgeackere. Morgen ist Feiertag, da habe ich dann hoffentlich genug Muse mir eine bessere Lösung einfallen zu lassen. Ich glaube ich habe mich da gerade verrannt. Eine Nacht darüber schlafen und dann mit neuem Schwung an die Sache. Außerdem lerne ich auf diese Art leichter, so blöd das klingt. Bei solchen Sachen geh ich leider immer nach dem Motto "Warum einfach, wenn es auch umständlich geht?". Da ist es dann gut wenn ich Menschen wie euch finde die mich wieder auf den rechten Weg zurückbringen und mir zusätzlich noch sagen, wo ich falsch abgebogen bin.
Hatte ich mich eigentlich schon bei euch bedankt?
-
Ich habe es versucht und bin kläglich gescheitert, deshalb mein rumgeackere. ......
OK, dann vielleicht die Empfehlung, vielleicht noch mal einen kleinen Schritt zurück zu gehen und die Dir die Grundlagen von Powershell draufzuschaffen - von Gund auf. Das erspart Dir bestimmt eine Menge Frustration und Zeitverschwendung.
Ein guter Start ist immer der kostenlose Video-Kurs von der Microsoft Virtual Academy. Getting Started With Powershell. Der basiert zwar auf Powershell 3 aber die Grundlagen sind ja immernoch die gleichen. ... sehr empfehlenswert für Anfänger.
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
Habe ich mir von function Get-NewestFileforEachFolder entlehnt. Und die Funktion untersucht ob*alle* Dateien in dem Ordner älter als der Stichtag sind.
Ist wie gesagt unnötig. LastWriteTime eines Ordners ist der Timestamp der neuesten Datei in diesem Ordner. Also sind alle anderen Dateien eh älter. Wer auch immer diese Funktion geschrieben hat, der wußte das wohl auch nicht.
\Verzeichnis\KundeX\ReleaseX - diese Ordner sollen untersucht werden. Wenn alle Dateien in einem Release-Ordner das Haltbarleitsdatum überschritten haben soll der Ordner unter Beibehaltung der Verzeichnisstruktur verschoben werden. Also \verzeichnis\kunde\release nach \verzeichnis\!Archiv\kunde\release
Dann würde ich sagen, Du nimmst den Ansatz aus meinem letzten Post ("So würde ich das machen") - der scheint mir doch wesentlich übersichtlicher als Dein Code :-))
Der ganze Vorgang soll protokolliert werden und im nächsten Schritt soll ich dann die Parameter aus einer xml-Datei ziehen.
Protokollieren ist einfach - oft hilft "-verbose" und ein gelegentliches Write-Debug oder auch nur Write-Host. Und XML kann Powershell nativ, das ist auch einfach.
-
ist vorgegeben.
Wenn Du beim initialen Get-ChildItem per -Exclude den !Archiv-Ordner ausschließt, kannst Du Dir den späteren Filterkram sparen.
Und die Funktion untersucht ob alle Dateien in dem Ordner älter als der Stichtag sind.
Bist Du sicher? Hast Du es getestet? ;-)
Ich habe nur halbwegs eine Ahnung was die einzelnen Teile meines Skripts bewirken. Ich bastel einfach etwas herum und hoffe, dass ich mir mit meinen Experimenten nicht meinen PC abschiesse...
Deshalb die Empfehlung, Dir erstmal die Grundlagen anzueignen.
Der ganze Vorgang soll protokolliert werden und im nächsten Schritt soll ich dann die Parameter aus einer xml-Datei ziehen.
Ich würde es schrittweise erweitern. Fang erstmal klein an. Wenn eine Funktion zuverlässig läuft, nimmst Du Dir die nächste vor.
Wenn Du zur Entwicklung die Powershell-Ise benutzt, kannst Du Code markieren und mit F8 nur diesen markierten Code selektiv ausführen.
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
Habe ich mir von function Get-NewestFileforEachFolder entlehnt. Und die Funktion untersucht ob*alle* Dateien in dem Ordner älter als der Stichtag sind.
Ist wie gesagt unnötig. LastWriteTime eines Ordners ist der Timestamp der neuesten Datei in diesem Ordner. Also sind alle anderen Dateien eh älter. Wer auch immer diese Funktion geschrieben hat, der wußte das wohl auch nicht.
Dem muss ich leider wiedersprechen. Habe mir zum Testen ein paar Ordner erstellt und z.B. \Kunde4\Release1 hat das Änderungsdatum 29.05.2018, fünf der enthaltenen Dateien 08.05.2016 und eine 04.03.2017. Wenn ich einen Ordner mit dem Explorer kopiere oder verschiebe bekommt er die LastWriteTime dieses Vorgangs, ändert aber nichts an der LastWriteTime der enthaltenen Dateien. -
Exclude habe ich jetzt übernommen und Get-ChildItem untersucht ob alle Dateien (auch versteckte) älter sind wenn ich den Parameter -force noch einfüge, Danke für den Hinweis. Bin in meinem jugendlichen Leichtsinn davon ausgegangen, dass ich keine versteckten Dateien und Verzeichnisse in einem der Ordner antreffen werde.
Grundlagen mache ich parallel, werde ich jetzt aber wohl erstmal priorisieren und mir auch diverse Bücher und Tutorials durchlesen und anschauen.
-
> Wenn ich einen Ordner mit dem Explorer kopiere oder verschiebe bekommt er die LastWriteTime dieses Vorgangs, ändert aber nichts an der LastWriteTime der enthaltenen Dateien.
Das ist korrekt - ich bin davon ausgegangen, daß Deine Quellstruktur direkt entsteht und nicht durch Kopieren. Ok, dann nutze die Funktion :-)
-
Ok, dann nutze die Funktion :-)
.... lol ... da fällt mir glatt die aktuelle Netto-Werbung ein ... suupiBest regards,
(79,108,97,102|%{[char]$_})-join''
-
Mein derzeitiger Stand:
$xmlFile = "C:\temp\test.xml" [xml]$check = Get-Content $xmlFile $sourcePath = $check.params.path.sPath $targetPath = $check.params.path.tPath $logPath = $check.params.path.lPath $Monate = $check.params.alter $Stichtag = (Get-Date).AddMonths( - $Monate) $Sort = "LastWriteTime" $tomove = $null $moveto = $null $tomove = @() $moveto = @() $getKunden = Get-ChildItem -Path $sourcePath -Directory | Where-Object { -not ($_.FullName -like "$targetPath*")} foreach ($getKunde in $getKunden){ $Kunden = Join-Path -Path $sourcePath -ChildPath $getKunde $Kunden $Releases = Get-ChildItem -Path $Kunden -Directory foreach ($Release in $Releases){ $Destination = $targetPath+"\"+$getKunde $KundenReleases = Join-Path -Path $Kunden -ChildPath $Release for ($i=0; $i -lt $KundenReleases.count; $i++){ $allFiles = @(Get-ChildItem -Path $KundenReleases -File -Recurse) $useFiles = @(Get-ChildItem -Path $KundenReleases -File -Recurse | Where {$_.LastWriteTime -lt (get-date).AddMonths(-$Monate)} ) if ($allFiles.Count -eq $useFiles.Count){ if (-not(Test-Path -Path $Destination)){New-Item -Path $Destination -ItemType Directory } $x = Copy-Item -Path $KundenReleases -Destination $Destination -Recurse -PassThru -ErrorAction silentlyContinue if ($x) { $x } else { "Copy failure"} $x | out-file -FilePath $logPath\copylog-$((get-date).ToString("dd-MM-yyyy")).txt -Append -width 100 } } } } Get-ChildItem "$logPath\copylog-$((get-date).ToString("dd-MM-yyyy")).txt" -recurse | Foreach-Object { $c = ($_ | Get-Content) | where {$_ -like "*Verzeichnis*"} $c = $c.trim() -replace 'Verzeichnis: ','' | sort -Unique $c = $c.replace("$targetPath","$sourcePath") $c = $c | where {$_ -ne "getKunden"} } "" "" echo "Haltbarkeitsdatum: $(($Stichtag).ToString("dd.MM.yyyy"))" $c | out-file -FilePath $logPath\Folders-$((get-date).ToString("dd-MM-yyyy_HH-mm")).txt -Append -width 100
Jetzt muss ich nur noch eine Fehlerabfrage für Copy-Item einfügen und danach die kopierten Ordner löschen.
-
Möchtest Du nur unsere Meinung zu Deinem Code hören oder funktioneirt irgendwas nicht? Bei nem kurzen "Überflug" seh ich erstmal nur ein paar Kleingkeiten ... angenommen Dein Code hier oben hätte Zeilennummern bei 1 angefangen ...
Zeilen 9 - 13 ... die Variablen werden teils doppelt deklariert aber trotzdem sowieso nie benutzt.
Zeile 21 ... Ich finde Join-Path für solche Fälle eleganer und weniger fehlerträchtig. Wenn in der Variable mal versehentlich ein Pfad mit abschließndem Backslash landet, hast Du Dir so einen Fehler eingebaut - Join-Path bügelt das automatisch glatt. ;-)
Zeile 22 ... eine einfach Forach-Schleife bringt das gleiche Ergebnis und ist einfacher zu lesen.
Zeilen 25,39,40,42,47 ... Laut Powershell Style Guide sollte man speziell in Scripten möglichst keine Aliasse benutzen. Lass Dir doch von der ISE oder von VSCode oder was auch immer Du benutzt helfen und benutze die Tab-Vervollständigung ... s' ließt sich einfach leichter.Dein Logging-Ansatz ist (gefühlt) ein bissl von hinten durch die Brust ins Auge, aber wenn's für Dich erst mal so funktioniert - warum nicht. ;-)
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
Nächstes Mal sollte ich wohl dazuschreiben was ich mit meinem Post erreichen will...
Anregungen was ich noch optimieren oder besser anderst nachen sollte wollte ich hören.
$moveto und $tomove waren Hilfsvariablen die ich vergessen habe zu löschen und $Stichtag sollte anstelle von (get-date).AddMonths(-$Monate) stehen.
Was für Aliasse? Wenn Du $allFiles und $useFiles meinst, da ist mir bis jetzt noch keine bessere Lösung eingefallen. Stört mich aber auch noch.
-
Mit Get-Alias kannst Du Dir eine Liste der bereits vordefinierten Aliasse anzeigen lassen. Es sind quasi Kurzformen der Powershell-cmdlets, die das Verhalten von altbekannten Befehlen aus der Windows-Welt oder von Linux nachbilden. Wie z.B. ls oder dir für Get-ChildItem oder Where für Where-Object oder eben echo für Write-Output. Die sind der Konsole schnell getippt, besonders wenn man ursprünglich von Linux kommt und eher an ls und man gewöhnt ist. Aber in Scripten gelten sie eher als schlechter Stil ... einfach mal den Style-Guide "durchblättern" ... ;-)
Best regards,
(79,108,97,102|%{[char]$_})-join''
-
> Zeile 22 ... eine einfach Forach-Schleife bringt das gleiche Ergebnis und ist einfacher zu lesen.
Wenn das die hier ist
for ($i=0; $i -lt $KundenReleases.count; $i++){
Dann ist das irgendwie obsolet. Drüber wird $KundenReleases zusammengebaut mit Join-Path, .Count ist also immer 1. Und $i wird auch nirgends verwendet... :-))So ähnlich sieht es auch bei dem Pseudo-Loggging mit $c aus - wenn da mal mehr als eine Datei gefunden würde, dann landet in $c die letzte davon.
$allFiles = @(Get-ChildItem -Path $KundenReleases -File -Recurse) $useFiles = @(Get-ChildItem -Path $KundenReleases -File -Recurse | Where {$_.LastWriteTime -lt (get-date).AddMonths(-$Monate)} )
Warum gehst Du bei $useFiles noch mal durchs Dateisystem, wenn Du doch in $allFiles schon alles hast?
$useFiles = $allFiles | Where....Stichwort "Code-Sparsamkeit" - Du mußt das Ergebnis von Cmdlets nicht als Array definieren, das macht PowerShell automatisch:
$allFiles = Get-ChildItem -Path $KundenReleases -File -Recurse
Liest sich besser, finde ich. Und solange Du nicht Set-StrictMode verwendest, mußt Du Variablen auch nicht deklarieren.Und wo mir das grad einfällt: Du willst verschieben, wenn ALLES alt ist. Wäre da nicht die Probe andersrum effizienter?
[bool]$MoveFlag = { ( $useFiles | Where { $_.LastWriteTime -gt ( get-date ).AddMonths( -$Monate ) } ).Count -eq 0 } # oder so :-) - da steht dann $TRUE drin, wenn es keine neueren Dateien gibt, sonst $FALSEUnd ganz zum Schluß: Copy-Item - ich würde Dateioperationen, die auch mal größer werden können, niemals mit Cmdlets machen, sondern immer (wenn Du es kannst) direkt mit .NET oder (viel einfacher) mit Robocopy. Geht signifikant schneller.
-
Danke für die Hilfen.
Das mit den Arrays habe ich gemacht, weil PowerShell mir die Ergebnisse alle in eine Zeile geschrieben hatte bei einem Versuch, deshalb bin ich da lieber auf Nummer sicher gegangen. Aber mir hat sich die PowerSehll ISE auch fünf mal hintereinander aufgehängt, von daher...
Werde die Spar-Version mal testen. Mein derzeitiges Skripz läuft soweit ohne Fehler, allerdings suche ich noch nach einer Möglichkeit zu testen ob auch genug Platz auf meinem Ziellaufwerk ist um die Verzeichnisse zu verschieben. Allerdings ist mein bisheriger Versuch etwas unelegant:
$sum = (Get-ChildItem -Path $KundenReleases -Force -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum |% {[math]::truncate($sum/1GB)} $share = Get-WmiObject Win32_LogicalDisk -filter "name='d:'" | %{[math]::truncate($_.freespace/1GB)} if ($share-$sum -lt 10){ Write-Output "Zu wenig Speicherplatz auf Archiv. Vorgang Abgebrochen." } else {...}
Write-Output ist nur ein Platzhalter, das wird in eine Report-Datei geschrieben.
Die True/False-Abfrage wollte ich eigentlich machen, mir wollte aber einfach nichtmehr einfallen wie das geht.
Robocopy wäre auch eine Möglichkeit, allerdings hatte ich damit schlechte Erfahrungen gemacht als ich mit dem -move Schalter das Unterverzeichnis verschoben und das Hauptverzeichnis und alle anderen Unterverzeichnisse gelöscht hatte. Mit .NET habe ich mich noch nicht befasst, werde ich aber wohl demnächst auch mal in Angriff nehmen müsssen. Muss ich mir aber alles selber beibringen, Schule ist da keine Hilfe. Dort hatten wir je drei Wochen PHP und Java gehabt und in der Abschlussprüfung müssen wir dann etwas unter Visual Studio in C# programmieren.
Noch eine Frage zur ISE. In der Firma zeigt die mir an welche Klammernpaare zusammengehören, daheim an meinem PC aber nicht. Welche Einstellung muss ich vornehmen damit das funktioniert?