Jump to content
Fosco

Skript zum Prüfen und Kopieren von PDF Dateien

Recommended Posts

GUI automatisieren mit Powershell ist schwierig. Du könntest einen Watcher bauen, der nach einer definierten Zeit den pdfinfo-Prozess abschießt, falls er zu lange aktiv ist.

Share this post


Link to post
Share on other sites

... 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:

Edited by BOfH_666

Share this post


Link to post
Share on other sites

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..... 

Share this post


Link to post
Share on other sites

-Erroraction und Try/Catch lösen das. Und Wait-Process hatte ich gar nicht auf dem Schirm - das ist in Verbindung mit Start-Process sicher ein guter Ansatz.

Share this post


Link to post
Share on other sites

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...

Share this post


Link to post
Share on other sites

So geht das nicht. Wenn Du PDFInfo asynchron startest, kommst Du nicht "direkt" an einen Returncode ran. Und so wie Dein Beispielcode oben aussieht, ist das wieder nicht asynchron und hängt dann halt, wenn $PDFInfoToolPath nicht zurückkommt... Multithreading ist nicht einfach am Anfang :-)

Share this post


Link to post
Share on other sites

Wieso verwendest du nicht einfach Start-process? Da genügt die foreach Schleife und start-process plus etwas logging aus. Außerdem kann mittels -wait eine "sequentielle" Abarbeitung durchgeführt werden. Somit läuft wahrscheinlich auch Dein PDF Programm stabiler.

Link zu Microsoft - Start-Process

Share this post


Link to post
Share on other sites

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.

 

Share this post


Link to post
Share on other sites

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)

 

Edited by BOfH_666

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
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 ...  ;-) 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


Werbepartner:



×
×
  • Create New...