Jump to content

Skript zum Prüfen und Kopieren von PDF Dateien


Der letzte Beitrag zu diesem Thema ist mehr als 180 Tage alt. Bitte erstelle einen neuen Beitrag zu Deiner Anfrage!

Empfohlene Beiträge

... n' bissl von hinten durch die Brust ins Auge, aber Du könntest den Aufruf des Tools in einen Job oder einen Runspace oder einfach in eine zweite, unabhängige Konsolen-Session auslagern und diese überwachen. Wenn das länger dauert als "erlaubt", schießt Du's ab und gehst davon aus, dass das Tool gecrashed ist oder das PDF korrupt ist oder beides. ;-) 

 

ooops ...  steht ja schon da oben ... sorry, einfach ignorieren ...  :grin3:

bearbeitet von BOfH_666
Link zu diesem Kommentar
  • 2 Wochen später...

Hi Leute,

 

ich habe ein bisschen weiter versucht eine Lösung zu finden, Dabei bin ich auf folgende Variante gestoßen:

 

foreach ($FileItem in $FoundPdfList)
{
	$i++
	Write-Progress -Activity 'Prüfe gefundene PDF-Dateien' -Status "$i von $($FoundPdfList.Count)" -CurrentOperation $FileItem.Name
	[string]$FilePath = $FileItem.FullName
       
	$retValue = & $PdfInfoToolPath $FilePath 2>&1
    
	if ($retValue | Select-String -Pattern 'find trailer dictionary') 
	{
		Move-Item -Path $FilePath -Destination $DefectFilesPath
		wait-process -name pdfinfo -timeout 5
		stop-process -name pdfinfo -force
	}
}

 

Mit Outlook getestet, habe ich folgende Meldungen erhalten:

 

1. Outlook nicht beendet

 

Wait-Process : Dieser Befehl hat den Vorgang beendet, da der Prozess "OUTLOOK (11624)" nicht innerhalb des angegebenen Timeouts beendet wurde.
In C:\Users\alexander.thissen\Desktop\tools\test.ps1:1 Zeichen:1
+ Wait-Process -Name OUTLOOK -Timeout 5
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (System.Diagnostics.Process (OUTLOOK):Process) [Wait-Process], TimeoutException
    + FullyQualifiedErrorId : ProcessNotTerminated,Microsoft.PowerShell.Commands.WaitProcessCommand
 

Stop-process hat Outlook dann auch einfach beendet.

 

2. Outlook in der Zeitspanne beendet

Stop-Process : Es kann kein Prozess mit dem Namen "OUTLOOK" gefunden werden. Überprüfen Sie den Prozessnamen, und rufen Sie das Cmdlet erneut auf.
In C:\Users\alexander.thissen\Desktop\tools\test.ps1:2 Zeichen:1
+ Stop-Process -Name OUTLOOK -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (OUTLOOK:String) [Stop-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.StopProcessCommand

Keine Meldung von wait-process, aber stop-process meldet, das der Prozess nicht gefunden wurde,

 

Mein Gedanken gehen dahin, das die Funktion "stop-process", ähnlich wie das verschieben der Dateien, nur ausgeführt wird, wenn "wait-process" die ErrorId "ProcessNotTerminated" ausgibt. Müsste dann, denke ich, eine If-Schleife innerhalb der If-Schleife zum umkopieren sein.

 

Was meint Ihr?

Könnte das so funktionieren, und wenn ja, wie setzt man das um (evtl. auch so, das die Meldungen zu wait und stop nicht dargestellt werden)?

 

Nochmals Danke..... 

Link zu diesem Kommentar

Danke für den Tip.

Ich bin jetzt bei diesem Stand:

 

foreach ($FileItem in $FoundPdfList)
{
	$i++
	Write-Progress -Activity 'Prüfe gefundene PDF-Dateien' -Status "$i von $($FoundPdfList.Count)" -CurrentOperation $FileItem.Name
	[string]$FilePath = $FileItem.FullName
       
	$retValue = & $PdfInfoToolPath $FilePath 2>&1
    $retValueProcess = ???

	if ($retValue | Select-String -Pattern 'find trailer dictionary') 
	{
		Move-Item -Path $FilePath -Destination $DefectFilesPath
		wait-process -name pdfinfo -timeout 5 -ErrorAction SilentlyContinue
        
        	if ($retValueProcess | Select-String -Pattern 'ProcessNotTerminated')
        	{ 
          		stop-process -name pdfinfo -force -ErrorAction SilentlyContinue
        	}
	}
}

 

Was mir noch nicht klar ist, wäre die Übernahme des Fehlercodes von wait-process in die Variable "$retValueProcess".

Hier wird ja auf eine Rückmeldung eines Standartbefehls Bezug genommen.

Im Gegensatz dazu wird bei "$retValue" die Variable aus einem Vorgegeben Pfad und einen ausgelesenen Pfad zusammen gesetzt. Auch wenn ich zugeben muss, das mir da auch die Syntax nicht klar ist...

 

Danke für die Hilfe...

Link zu diesem Kommentar

Ich würde es nicht asynchron machen. Beim obigen Programm hat das meiner Meinung nach nur Nachteile. Erstens muss die Prozess-ID für jeden gestarteten Prozess gesichert werden. Zweitens muss permanent geprüft werden ob die Prozesse beendet und falls ja, mit welchem Status diese beendet wurden.

Glaube bei solch einem Programm kommt es nicht auf Multithreading an. Da eignet sich C# besser als Powershell.

 

Link zu diesem Kommentar

So ... jetzt ich nochmal.

 

Ich hab nochmal gründlich über die Aufgabenstellung und die von Dir geschilderten Probleme nachgedacht und hab versucht, das Ganze in kleinere Teile zu zerlegen. 

 

Da das PDFInfo-Tool bei Dir offenbar ziemlich instabil läuft - ich kann das auf meinem Computer nicht nachstellen - bräuchtest Du wohl als erstes eine Funktion, die das Tool starten kann, deren Ausführung überwachen und, wenn sich das Tool dann eben doch aufgehängt hat, das Tool beendet und Dir die Möglichkeit bietet, diesen Status zu erkennen und trotzdem weiterzumachen.

Danach musst Du die Ausgabe des Tools auswerten. Wenn die Ausgabe einen entsprechenden Fehler enthält, wird die gerade verarbeitete Datei ignoriert. Wenn die Ausgabe die erwartete Information enthält, wird die Datei kopiert.

Also hier meine Idee zur Funktion, die das Tool in einem Hintergrund-Job startet und dann eine einstellbare Zeit wartet, dass das Tool sich von selbst beendet und eine Ausgabe liefert:

function Start-ProcessWithTimeout {
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $FilePath,

        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [System.String[]]
        $ArgumentList,

        [Parameter(Position = 2)]
        [ValidateNotNull()]
        [System.Int32]
        $Timeout = 10

    )
    $SBFilePath = $FilePath
    $SBArgumentList = $ArgumentList

    $ScriptBlock = {
        param(
            $SBFilePath,
            $SBArgumentList
        )
        $TmpStdOut = New-TemporaryFile
        $TmpStdErr = New-TemporaryFile
        Start-Process -FilePath $SBFilePath -ArgumentList $SBArgumentList -NoNewWindow -RedirectStandardError $TmpStdErr -RedirectStandardOutput $TmpStdOut -Wait
        $StdErr = Get-Content -Path $TmpStdErr.FullName
        $StdOut = Get-Content -Path $TmpStdOut.FullName
        Remove-Item -Path $TmpStdErr -Force
        Remove-Item -Path $TmpStdOut -Force
        $StdErr
        $StdOut
    }
    $JobProps = @{
        Name        = 'BackGroundJob'
        ScriptBlock = $ScriptBlock
    }

    [VOID](Start-Job @JobProps -ArgumentList $SBFilePath, $SBArgumentList )

    $StartTime = Get-Date
    do {
        $JobState = Get-Job -Name BackGroundJob
        Write-Debug "JobState: $($JobState.State) - HasMOreData: $($JobState.HasMoreData)"
        if ($JobState.State -eq 'Completed') {
            $HasMoreData = $JobState.HasMoreData
            if ($JobState.HasMoreData) {
                $Result = Receive-Job -Name BackGroundJob
                Remove-Job -Name BackGroundJob
                break
            }
        }
        else {
            Start-Sleep -Milliseconds 50
        }
    } while ((Get-Date) -lt $StartTime.AddSeconds($Timeout))
    if (Get-Job -Name BackGroundJob -ErrorAction SilentlyContinue) {
        Stop-Job -Name BackGroundJob
        Remove-Job -Name BackGroundJob -Force 
    }

    [PSCustomObject]@{
        State       = $JobState.State
        HasMoreData = $HasMoreData
        Runtime     = New-TimeSpan -Start $JobState.PSBeginTime -End $JobState.PSEndTime
        Result      = $Result
    }

}

Leider gibt es noch ein Problem, wenn die Datei oder der Pfad ein Leerzeichen enthält. Das hab ich noch nicht lösen können. Wenn Du solche Datei hast, könntest Du die ja vielleicht vorher entsprechend umbenennen ... z.B. Leerzeichen durch Unterstriche ersetzen oder so. Oder Du ersetzt die Leerzeichen temporär durch ein einigermaßen exotisches Zeichen. Dann kannst Du sie später wieder relativ einfach zurück-umbenennen;-) 

In der Voreinstellung wartet die Funktion 10 Sekunden darauf, dass das Tool sich wieder beendet. Das war für meine Tests mehr als genug. Wenn das bei Dir anders ist, kannst Du den Timeout beim Aufrufen der Funktion entsprechend anpassen.

 

Jetzt könntest Du diese Funktion ungefähr so benutzen: 

$ToolPath = '<kompletter Pfad zum PDFInfo-Tool>\bin64\pdfinfo.exe'
Get-ChildItem -Path <Pfad zu den zu verarbeitenden PDF-Dateien> -Filter *.pdf |
    ForEach-Object{
        $PDFFilePath = $PSItem.FullName  #  hier wird der komplette Pfad zur PDF-Datei innerhalb der switch-Anweisung nutzbar gemacht
        $Result = Start-ProcessWithTimeout -FilePath $ToolPath -ArgumentList $PSItem.FullName  # -Timeout 20  # anpassen falls nötig
        If($Result.State -eq 'Completed' -and $Result.HasMoreData){
            
            switch ($Result.Result) {
                {$Result.Result -match 'Usage:\s+pdfinfo'}          { "PDF-Tool SyntaxError - $PDFFilePath"; break }
                {$Result.Result -match 'PDF\s+file\s+is\s+damaged'} { "PDF file is damaged  - $PDFFilePath"; break }
                {$Result.Result -match 'PDF\s+version:'}            { "PDF file is valid    - $PDFFilePath"; break }
            }
        }
        Else{
            "PDF-Tool stuck ...   - $PDFFilePath"
        }
    }

Dieses Beispiel gibt nur Strings auf der Console aus. Die eigentlichen Aktionen musst Du dann entsprechend Deiner Anforderungen einbauen. Natürlich kann man den switch block noch beliebig erweitern, z.B. je nach Author die PDFs in verschiedene Ordner verschieben oder, oder , oder ... ;-) 

 

Ich hoffe, es hilft.  (... und es funktioniert wie erhofft ... wie gesagt, bei mir stürzt das Tool leider nicht ab)

 

bearbeitet von BOfH_666
Link zu diesem Kommentar

Vielen Dank für deine Mühen...

 

Den unteren Teil habe ich verstanden (denke ich) und mal so angepasst, wie ich es mir (als Laie) denke.

Problem ist, das auch eine zu öffnende Datei als defekt erkannt werden kann. Daher habe ich den String von "PDF file is damaged" in (Could´t) "find Trailer Dictionary" abgeändert. 

Damit sollten nur noch wirklich defekte Dateien verschoben werden.

 


$ToolPath = '<kompletter Pfad zum PDFInfo-Tool>\bin64\pdfinfo.exe'
$DefectFilesPath = 'Zielordner'

Get-ChildItem -Path <Pfad zu den zu verarbeitenden PDF-Dateien> -Filter *.pdf |
    ForEach-Object{
        $PDFFilePath = $PSItem.FullName  #  hier wird der komplette Pfad zur PDF-Datei innerhalb der switch-Anweisung nutzbar gemacht
        $Result = Start-ProcessWithTimeout -FilePath $ToolPath -ArgumentList $PSItem.FullName  # -Timeout 20  # anpassen falls nötig
        If($Result.State -eq 'Completed' -and $Result.HasMoreData){
            
            switch ($Result.Result) {
                {$Result.Result -match 'Usage:\s+pdfinfo'}            { "PDF-Tool SyntaxError - $PDFFilePath"; break }
                {$Result.Result -match 'find\s+trailer\s+dictionary'} { Move-Item -Path $PDFFilePath -Destination $DefectFilesPath; break }
                {$Result.Result -match 'PDF\s+version:'}              { "PDF file is valid    - $PDFFilePath"; break }
            }
        }
        Else{
            "PDF-Tool stuck ...   - $PDFFilePath"
        }
    }

Unsicher bin ich mit dem $PDFFilePath im switch-block. Da es sich um eine Datei handelt würde ich gefühlsmäßig eher zu $PSItem.Fullname tendieren. Aber da kann ich natürlich auch "heavy on the woodway" sein...

 

Ich habe es auch so verstanden, das die Funktion die du erstellt hast innerhalb der ForEach-Schleife gestartet wird (Start-ProcesswithTimeout).

Was mir noch unklar ist, wie gehe ich mit der Funktion um? Wo und/oder Wie wird sie hinterlegt? Muss die irgendwo eingefügt werden?

 

Danke

Fosco

Link zu diesem Kommentar
vor 17 Minuten schrieb Fosco:

Problem ist, das auch eine zu öffnende Datei als defekt erkannt werden kann. Daher habe ich den String von "PDF file is damaged" in (Could´t) "find Trailer Dictionary" abgeändert. 

Damit sollten nur noch wirklich defekte Dateien verschoben werden.

Ja ... das solltest Du für Dich anpassen, wie es am besten passt. Ich hatte nur einen sehr kleinen Pool an PDF-Dateien zum Testen und hatte mir nur 2 defekte PDFs gebastelt.

vor 17 Minuten schrieb Fosco:

Unsicher bin ich mit dem $PDFFilePath im switch-block. Da es sich um eine Datei handelt würde ich gefühlsmäßig eher zu $PSItem.Fullname tendieren. Aber da kann ich natürlich auch "heavy on the woodway" sein...

Ich kann's im Moment nicht verifizieren, aber innerhalb des switch - Blocks verweist $PSItem auf das zu testende "switch-Element" ...  deshalb die extra Variable.

vor 17 Minuten schrieb Fosco:

Was mir noch unklar ist, wie gehe ich mit der Funktion um? Wo und/oder Wie wird sie hinterlegt? Muss die irgendwo eingefügt werden?

Das kannst Du machen, wie Du möchtest. Die Funktion muss eben spätestens wenn Du sie aufrufen möchtest definiert sein. Du könntest sie in einer extra Datei speichern, die Du am Anfang Deines Scriptes mittels Dot-Sourcing einbindest. Oder Du packst die Funktion direkt mit in die Script-Datei. Oder Du packst sie mit ins Profil des Users oder PCs, wo das Script ausgeführt werden soll. Oder, oder, oder ...  ;-) 

Link zu diesem Kommentar
Der letzte Beitrag zu diesem Thema ist mehr als 180 Tage alt. Bitte erstelle einen neuen Beitrag zu Deiner Anfrage!

Schreibe einen Kommentar

Du kannst jetzt antworten und Dich später registrieren. Falls Du bereits ein Mitglied bist, logge Dich jetzt ein.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor-Fenster leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

×
×
  • Neu erstellen...