mischl71
Goto Top

PowerShell über Aufgabenplanung Löschung Dateien älter als X Tage auf Netzwerkfreigaben

Hallo Community,

ich verzweifle gerade an einer Aufgabenstellung und benötige euer Schwarmwissen und Ideen. Ich habe eine PowerShell-Skript geschrieben, das vielleicht nicht schön ist aber dennoch funktioniert, wenn ich es in der PowerShell ISE (Admin) oder auch in PowerShell als anderer Benutzer ausführe.

Kurz zum Hintergrund der Aufgabenstellung:
1. Es sollen alle Dateien aus zwei Ordnern gelöscht werden, die älter als 30 Tage sind. (Server1)
- auf diese Freigabe haben aber nur Administratoren und bestimmt Service-Accounts zugriff.
2. Zusätzlich sollten alle Dateien aus einem TEMP-Verzeichnis gelöscht werden die älter als 1 Tag sind. (Server2)
- auf diese Freigabe haben aber nur Administratoren und bestimmt Service-Accounts zugriff.
3. Weiter wird ein Protokoll in eine Textdatei geschrieben, in dem steht wann das Skript ausgeführt wurde, welche Dateien gelöscht wurden und ob es einen Fehler gab. Zusätzlich wird das Logs täglich neu geschrieben und für 60 Tage vorgehalten.

# Variablen definieren
$netzwerkpfad1 = "\\Server1\Freigabe1\Eingang\1\"  
$netzwerkpfad2 = "\\Server1\Freigabe1\Ausgang\1\"  
$temppfad = "\\Server2\Temp"  
$logDateiname = "Log_Script"  
$logVerzeichnis = "\\Server2\Log"  
$maxAnzahlLogs = 60

# Schritt 1: Dateien im Netzwerkpfaden älter als 30 Tage löschen
$gelöschteNetzwerkdateien1 = Get-ChildItem -Path $netzwerkpfad1 | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) }
$gelöschteNetzwerkdateien1 | Remove-Item -Force

Start-Sleep -Seconds 5 # Pause für 5 Sekunden

$gelöschteNetzwerkdateien2 = Get-ChildItem -Path $netzwerkpfad2 | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) }
$gelöschteNetzwerkdateien2 | Remove-Item -Force

Start-Sleep -Seconds 5 # Pause für 5 Sekunden

# Schritt 2: Dateien im Temp Pfad älter als 1 Tage löschen
$gelöschteTempdateien = Get-ChildItem -Path $temppfad | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-1) }
$gelöschteTempdateien | Remove-Item -Force

Start-Sleep -Seconds 5 # Pause für 5 Sekunden

# Schritt 3: Aktuelle fortlaufende Nummer basierend auf dem Tag erhalten
$fortlaufendeNummer = Get-Date -Format "MMdd"  

# Schritt 4: Speichern von Ausführungsstatus in Logdatei
$dateiname = "${logDateiname}_$fortlaufendeNummer.txt"  
$dateipfad = Join-Path -Path $logVerzeichnis -ChildPath $dateiname

# Schritt 5: Prüfen und löschen der ältesten Datei, wenn mehr als die maximale Anzahl vorhanden ist
$dateien = Get-ChildItem -Path $logVerzeichnis -Filter "${logDateiname}_*.txt" | Sort-Object CreationTime  
if ($dateien.Count -ge $maxAnzahlLogs) {
    $zuLöschendeDateien = $dateien[0..($dateien.Count - $maxAnzahlLogs)]
    $zuLöschendeDateien | ForEach-Object {
        Remove-Item -Path $_.FullName -Force
    }
}

# Schritt 6: Schreibe Datum, Zeit und Ausführungsstatus in Logdatei
Add-Content -Path $dateipfad -Value (Get-Date -Format "dd.MM.yyyy HH:mm:ss") -NoNewline  
Add-Content -Path $dateipfad -Value "`t`t$logDateiname`tSkript zum Löschen wird ausgeführt..."  

# Schritt 7: Schreibe gelöschte Dateinamen in Logdatei
if ($gelöschteNetzwerkdateien1.Count -gt 0) {
    Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`tGelöschte Netzwerkdateien Schritt 1:"  
    $gelöschteNetzwerkdateien1 | ForEach-Object {
        Start-Sleep -Seconds 5 # Pause für 5 Sekunden
        Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

if ($gelöschteNetzwerkdateien2.Count -gt 0) {
    Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`tGelöschte Netzwerkdateien Schritt 2:"  
    $gelöschteNetzwerkdateien2 | ForEach-Object {
        Start-Sleep -Seconds 5 # Pause für 5 Sekunden
        Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

if ($gelöschteTempdateien.Count -gt 0) {
    Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`tGelöschte Temp Dateien:"  
    $gelöschteTempdateien | ForEach-Object {
        Start-Sleep -Seconds 5 # Pause für 5 Sekunden
        Add-Content -Path $dateipfad -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

# Schritt 8: Schreibe Datum, Zeit und Ausführungsstatus in Logdatei
$ausführungsstatus = if ($?) { "erfolgreich" } else { "mit Fehler" }  
    Add-Content -Path $dateipfad -Value (Get-Date -Format "dd.MM.yyyy HH:mm:ss") -NoNewline  
    Add-Content -Path $dateipfad -Value "`t`t$logDateiname`tSkript wurde $ausführungsstatus ausgeführt." -Force  

Nun aber nach dem ganzen Hintergrund zu meinem Problem. Ich möchte das Skript nicht selbst ausführen sondern einen Task in der Aufgabenplanung anlegen, der das Skript beispielsweise alle 24 Stunden gegen 7 Uhr ausführt.
Der Task war auch schnell angelegt, ist schließlich nicht das erste Mal, aber es passiert leider überhaupt nichts. Der Task wurde für eine Service-Account erstellt wie folgend.

<?xml version="1.0" encoding="UTF-16"?>  
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">  
  <RegistrationInfo>
    <Date>2023-09-19T11:08:17.1892535</Date>
    <Author>DOMÄNE\ADMIN1</Author>
    <URI>\Beispiele\deleteFiles</URI>
  </RegistrationInfo>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2023-09-19T07:00:00</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">  
      <UserId>A-2-2-22-22222222-2222222222-2222222222-2222222</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P1D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">  
    <Exec>
      <Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
      <Arguments>-ExecutionPolicy Bypass -File "E:\deleteFiles-5.ps1"</Arguments>  
    </Exec>
  </Actions>
</Task>

Wenn ich den Task auf meinem Windows Server 2022 mit meinem Admin-Account mit "Run only when user is logged on" ausführe, funktioniert alles wie am Anfang beschrieben.

Hat jemand eine Idee, was ich falsch gemacht habe oder wo mein Denkfehler ist? Muss ich die Benutzerinformationen für den Service-Account auch nochmal im Skript eintragen oder reicht es aus, wenn ich den Task mit diesem Service-Account ausführen lasse? Sollten die Benutzerinformationen ins das Skript müssen, frage ich mich wie ich das am besten machen.

Vielen Dank bereits im Voraus!

Grüße
Mischl

Content-Key: 1515451775

Url: https://administrator.de/contentid/1515451775

Printed on: December 6, 2023 at 21:12 o'clock

Member: mayho33
mayho33 Sep 21, 2023 updated at 17:18:33 (UTC)
Goto Top
Hi,

Wenn du die Suchfunktion genutzt hättest, hättest du eventuell das gefunden:
Aufgabenplanung Backup

Da habe ich ziemlich genau beschrieben wie man einen Scheduled Task anlegt.

Ach! Ganz wichtig! Keine Umlaute im Skript!

Grüße
Member: NullReferenceException
Solution NullReferenceException Sep 21, 2023 at 17:18:45 (UTC)
Goto Top
Moin,

ohne einen konkreten Tipp würde ich z.B. in die erste Zeile ein Start-Transcript mit passendem LiteralPath einbauen (Stop-Transcript am Ende des Scripts). Dann kannst Du schon mal - unabhängig von den erzeugten EventLogs zum SchTask - sehen, ob Deine Kommandozeile korrekt gestartet wird (scheint sie ja zu tun, da der SchTask unter bestimmten Umständen anläuft).
Im selben Atemzug könntest Du noch mal gucken, ob Du die Delays von 5s tats. brauchst - ist mir seit Jahren nicht mehr untergekommen, so‘n „warten auf‘s Dateisystem“…
Und noch 'n Kommentar zur ISE: der Konsolen-Host ist ein Anderer, als der Host der powershell.exe oder pwsh.exe und verhält sich (zugegeben nur in „edge cases“) anders. Ich hatte bei den alten WMI-CmdLets und bei ShouldProcess und -Debug abweichendes Verhalten bei dem, was in die Pipeline geht, festgestellt (oder hab‘ mies diagnostiziert ­čśë)

Mit dem Start-Transcript und ordentlich Write-Host oder Wite-Debug kommst Du der Ursache auf Jeden schnell auf die Spur.

Viel Erfolg!
Member: mayho33
mayho33 Sep 21, 2023 updated at 17:47:21 (UTC)
Goto Top
Ich denke ich habe den Fehler gefunden:

In deinem Task rufst du die Powershell so auf:
<Exec>
      <Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
      <Arguments>-ExecutionPolicy Bypass -File "E:\deleteFiles-5.ps1"</Arguments>    
    </Exec>
  • Auf Server ist es aber nicht ungewöhlich, dass %SystemDrive% etwas anderes als C:\ ist.
  • besser die die Comspec callen und den Rest als Argument mitgeben mit %SYSTEMROOT%:
<Exec>
      <Command>cmd.exe</Command>
      <Arguments>/c %SYSTEMROOT%\System32\WindowsPowershell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "E:\deleteFiles-5.ps1"</Arguments>  
    </Exec>

Das andere Problem wird sein, dass du als Pfad zum Scrpt ein E:\ angibst. Könnte sein, dass das auf deinem Server nicht mehr stimmt. Verwende besser den FQDN zum Script,
Member: SeaStorm
Solution SeaStorm Sep 21, 2023 at 21:19:09 (UTC)
Goto Top
Logge mal allen Output mit
Start-Transcript -path C:\temp\output.txt -append
....DeinScript....
Stop-Transcript 

Dann solltest du sehen warum er da auf die Bretter geht
Member: Mischl71
Mischl71 Sep 25, 2023 at 06:11:34 (UTC)
Goto Top
Hallo & guten Morgen Zusammen,

vielen Dank für eure (@NullReferenceException, @SeaStorm) Unterstützung! Mit dem Transcript bin ich dem Problem tatsächlich auf die Schliche gekommen.
Ursache war, dass ich über den Task trotz des hinterlegtem Benutzers kein Zugriff auf die Netzwerk-Rescourcen hatte. Die Lösung war dann leichter als erwartet. Benutzerinformationen im Skript hinterlegt und mit New-PSDrive die zwei Netzlaufwerke verbunden, Löschung und Logging durchgeführt, anschließend wieder mittels Remove-PSDrive Netzkaufwerke getrennt.

Zitat von @NullReferenceException:

... Dann kannst Du schon mal - unabhängig von den erzeugten EventLogs zum SchTask - sehen, ob Deine Kommandozeile korrekt gestartet wird (scheint sie ja zu tun, da der SchTask unter bestimmten Umständen anläuft).


Hier war tatsächlich nur zu sehen, dass alles in Ordnung ist. Task wurde getriggert über Scheduler, created task process, task started, action started, action completed, task completed.

Zitat von @NullReferenceException:

Im selben Atemzug könntest Du noch mal gucken, ob Du die Delays von 5s tats. brauchst - ist mir seit Jahren nicht mehr untergekommen, so‘n „warten auf‘s Dateisystem“…

Die Delays hatte ich nur als zusätzliche Sicherheit drin und sie werden aktuell nur beim Log schreiben benötigt, da ich sonst die Meldung kommt :

Add-Content : The process cannot access the file '...' because it is being used by another process.   

Das wird aber sehr wahrscheinlich am Design liegen, dass das Skript sich selbst blockiert wenn mehrere Zeilen geschrieben werden.

So sieht das Skript jetzt aktuell aus und macht das, was es soll bzw. ich mir vorgestellt habe.

# Variablen definieren
$serviceAccount = "domain\serviceAccount"  
$securePSPPath = "C:\geheim\secure.psp"  
$netshare1 = "\\Server1\Freigabe1"  
$netshare2 = "\\Server2\Freigabe2"  
$folder1 = "Eingang\1"  
$folder2 = "Ausgang\1"  
$tempFolder = "Temp"  
$logFilename = "Log_Script"  
$logFolder = "Log"  
$maxCountLogs = 60

# Schritt 1: Passwort entschluesseln
$secureData = Get-Content -Path $securePSPPath | ConvertTo-SecureString
    $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @($serviceAccount, $secureData)

#Schritt 2: Verbindung zu NAS herstellen
New-PSDrive -Name path1 -PSProvider FileSystem -root "$netshare1" -Credential $cred  
Write-Host "$netshare1 wird verbunden"  
New-PSDrive -Name path2 -PSProvider FileSystem -root "$netshare2" -Credential $cred  
Write-Host "$netshare2 wird verbunden"  

# Schritt 3: Dateien im Netzwerkpfaden aelter als 30 Tage loeschen
$deleteNetFiles1 = Get-ChildItem -Path path1:\$folder1 | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) }
$deleteNetFiles1 | Remove-Item -Force

$deleteNetFiles2 = Get-ChildItem -Path path1:\$folder2 | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) }
$deleteNetFiles2 | Remove-Item -Force

# Schritt 4: Dateien im Temp Pfad aelter als 1 Tage loeschen
$deleteTempFiles = Get-ChildItem -Path path2:\$tempFolder | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-1) }
$deleteTempFiles | Remove-Item -Force

# Schritt 5: Aktuelle fortlaufende Nummer basierend auf dem Tag erhalten
$fileDate = Get-Date -Format "MMdd"  

# Schritt 6: Speichern von Ausfuehrungsstatus in Logdatei
$filename = "${logFilename}_$fileDate.txt"  
$filepath = Join-Path -Path $netshare2\$logFolder -ChildPath $filename

# Schritt 7: Pruefen und loeschen der aeltesten Datei, wenn mehr als die maximale Anzahl vorhanden ist
$files = Get-ChildItem -Path path2:\$logFolder -Filter "${logFilename}_*.txt" | Sort-Object CreationTime  
if ($files.Count -ge $maxCountLogs) {
    $deleteLogFiles = $files[0..($files.Count - $maxCountLogs)]
    $deleteLogFiles | ForEach-Object {
        Remove-Item -Path $_.FullName -Force
    }
}

# Schritt 8: Schreibe Datum, Zeit und Ausfuehrungsstatus in Logdatei
Add-Content -Path $filepath -Value (Get-Date -Format "dd.MM.yyyy HH:mm:ss") -NoNewline  
Add-Content -Path $filepath -Value "`t`t$logFilename`tSkript zum Loeschen wird ausgefuehrt..."  

# Schritt 9: Schreibe geloeschte Dateinamen in Logdatei
if ($deleteNetFiles1.Count -gt 0) {
    Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`tGeloeschte Netzwerkdateien Schritt 1:"  
    $deleteNetFiles1 | ForEach-Object {
        Start-Sleep -Seconds 3 # Pause fuer 3 Sekunden
        Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

if ($deleteNetFiles2.Count -gt 0) {
    Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`tGeloeschte Netzwerkdateien Schritt 2:"  
    $deleteNetFiles2 | ForEach-Object {
        Start-Sleep -Seconds 3 # Pause fuer 3 Sekunden
        Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

if ($deleteTempFiles.Count -gt 0) {
    Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`tGeloeschte Temp Dateien:"  
    $deleteTempFiles | ForEach-Object {
        Start-Sleep -Seconds 3 # Pause fuer 3 Sekunden
        Add-Content -Path $filepath -Value "`t`t`t`t`t`t`t`t`t$($_.FullName)"  
    }
}

# Schritt 10: Schreibe Datum, Zeit und Ausfuehrungsstatus in Logdatei
$status = if ($?) { "erfolgreich" } else { "mit Fehler" }  
    Add-Content -Path $filepath -Value (Get-Date -Format "dd.MM.yyyy HH:mm:ss") -NoNewline  
    Add-Content -Path $filepath -Value "`t`t$logFilename`tSkript wurde $status ausgefuehrt." -Force  

# Schritt 11: Verbindung zu NAS trennen
Remove-PSDrive -Name path1 -Force
Write-Host "$netshare1 wurde getrennt"  
Remove-PSDrive -Name path2 -Force
Write-Host "$netshare2 wurde getrennt"  

Auch wenn das initiale Problem gelöst ist, nehme ich sehr gerne Anregungen zur weitere Optimierung entgegen.

Vielen Dank nochmals.

Grüße
Mischl