File System Watcher stops working when converting Word doc/docx files to PDF

别说谁变了你拦得住时间么 提交于 2019-12-08 11:09:20

问题


I have a Powershell script for automatic converting .doc/.docx files to *.pdf. The script is running well for the first file. But if I put another file in the watched folder, the watcher doesn't trigger an event.

Here is the complete script. If I comment out the all $doc variables, the script is running multiple times without any problems. Did I ignore/overlook something?

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$Env:DropboxRoot"
$watcher.Filter = "*.doc*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true

Add-type -AssemblyName Microsoft.Office.Interop.Word


$action  = {

$name = (get-item $Event.SourceEventArgs.FullPath).BaseName

### DON'T PROCESS WORD BACKUP FILES (START WITH A TILDE ~)
if(!($name.startsWith("~"))){

    write-host Triggered event from $Event.SourceEventArgs.FullPath
    $inputFilePath = $Event.SourceEventArgs.FullPath

    $parentPath = (get-item $inputFilePath).Directory
    $filename = (get-item $inputFilePath).BaseName
    $pdfDir = "$parentPath\PDF"

    if(!(Test-Path -Path $pdfDir)){
        New-Item -ItemType directory -Path $pdfDir
    }

    ###Execute PDF generate script
    write-host Create word object
    $word = New-Object -ComObject "Word.Application"


    ######define the parameters######
    write-host Define parameters
    $wdExportFormat =[Microsoft.Office.Interop.Word.WdExportFormat]::wdExportFormatPDF

    $OpenAfterExport = $false

    $wdExportOptimizeFor = [Microsoft.Office.Interop.Word.WdExportOptimizeFor]::wdExportOptimizeForOnScreen
    $wdExportItem = [Microsoft.Office.Interop.Word.WdExportItem]::wdExportDocumentContent
    $IncludeDocProps = $true
    $KeepIRM = $false #Don't export Inormation Rights Management informations
    $wdExportCreateBookmarks = [Microsoft.Office.Interop.Word.WdExportCreateBookmarks]::wdExportCreateWordBookmarks #Keep bookmarks
    $DocStructureTags = $true #Add additional data for screenreaders
    $BitmapMissingFonts = $true 
    $UseISO19005_1 = $true #Export as PDF/A

    $outputFilePath = $pdfDir + "\" + $filename + ".pdf" 


    $doc = $word.Documents.Open($inputFilePath)
     $doc.ExportAsFixedFormat($OutputFilePath,$wdExportFormat,$OpenAfterExport,`
                     $wdExportOptimizeFor,$wdExportRange,$wdStartPage,$wdEndPage,$wdExportItem,$IncludeDocProps,`
                    $KeepIRM,$wdExportCreateBookmarks,$DocStructureTags,$BitmapMissingFonts,$UseISO19005_1)

    $doc.Close()
    $word.Quit()

    [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($doc)
    [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($word)
    [GC]::Collect()
    [GC]::WaitForPendingFinalizers()    

 }
}

$created = Register-ObjectEvent $watcher -EventName "Created" -Action    $action
$renamed = Register-ObjectEvent $watcher -EventName "Renamed" -Action $action


while($true) {
    sleep 5
}`

回答1:


Your script has a few issues, that more debugging logic could find.

In some cases, (Get-Item System.Management.Automation.PSEventArgs.SourceEventArgs.FullPath) returns null. For unknown reasons, this seems to happen once for every document that gets converted. Perhaps it has to do with the "~Temp" files.

Subsequently, if(!($name.startsWith("~") will throw an exception.

When you use $inputFilePath = $Event.SourceEventArgs.FullPath, your variable is a FileInfo, and really you want to pass a string to $word.Documents.Open($inputFilePath).

Lastly, sometimes BaseName is null. Not sure why but the code could test for that or use other means to dissect the FullPath to get names and path parts.

All that said, once you get this working, my personal experience is that calling the COM object on Word to do this conversion in PowerShell is unreliable (Word hangs, ~Temp files get left behind, you have to kill Word from task manager, the COM calls in PowerShell never return). My testing shows that calling a C# console app to do the conversion is much more reliable. You could write this directory watcher and converter completely in C# and accomplish the same task.

Assuming you still want to combine the two, a PowerShell watcher, and a C# Word to PDF converter, below is a solution I came up with. The script runs for about a minute so you can test in the ISE or Console. From the Console press a key to exit. Before exiting, the script exits cleanly by unregistering the events which is quite helpful while testing in the ISE. Change this accordingly for how you intend to run the script.

PowerShell watcher

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "d:\test\docconvert\src"
$watcher.Filter = "*.doc*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true

# copy this somehwere appropriate
# perhaps in same directory as your script
# put on a read-only share, etc.
$wordToPdf = 'd:\test\docconvert\WordToPdf\WordToPdf\bin\Debug\WordToPdf.exe'

$action  = {
    try 
    {
        Write-Host "Enter action @ $(Get-Date)"

        $fullPathObject = (Get-Item $Event.SourceEventArgs.FullPath)

        if (!($fullPathObject))
        {
            Write-Host "(Get-Item $Event.SourceEventArgs.FullPath) returned null."
            return
        }

        $fullPath = ($fullPathObject).ToString()
        Write-Host "Triggered event from $fullPath"

        $fileName = Split-Path $FullPath -Leaf

        if ($fileName -and ($fileName.StartsWith("~")))
        {
            Write-Host "Skipping temp file"
            return
        }

        # put pdf in same dir as the file
        # can be changed, but a lot easier to test this way
        $pdfDir = Split-Path $FullPath -Parent
        $baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
        $outputFilePath = Join-Path $pdfDir $($baseName + ".pdf")
        Write-Host "outputFilePath is: '$outputFilePath'"

        # call c# WordToPdf to do conversion because
        # it is way more reliable than similar calls
        # from PowerShell
        & $wordToPdf $fullPath $outputFilePath

        if ($LASTEXITCODE -ne 0)
        {
            Write-Host "Conversion result: FAIL"
        }
        else
        {
            Write-Host "Conversion result: OK"
        }
    }
    catch
    {
        Write-Host "Exception from ACTION:`n$($_ | Select *)"
    }
    finally
    {
        Write-Host "Exit action @ $(Get-Date)"
    }
}

$created = Register-ObjectEvent $watcher -EventName "Created" -Action $action
$renamed = Register-ObjectEvent $watcher -EventName "Renamed" -Action $action

$count = 12
while($count--) {
    Write-Output "run/sleep ($count)..."
    sleep 5

    # will exit from console, not ISE
    if ([console]::KeyAvailable) {
        $key = [console]::ReadKey()
        break
    }
}

$created | % {Unregister-Event $_.Name}
$renamed | % {Unregister-Event $_.Name}

C# WordToPdf converter

add appropriate error checking for the arguments...

add Reference to COM Microsoft.Office.Interop.Word

using System;
using Microsoft.Office.Interop.Word;

namespace WordToPdf
{
    class Program
    {
        static int Main(string[] args)
        {
            Console.WriteLine($"Converting: {args[0]} to {args[1]}");
            var conversion = new DocumentConversion();
            bool result = conversion.WordToPdf(args[0], args[1]);

            if (result)
            {
                return 0;
            }
            else {
                return 1;
            }
        }
    }

    public class DocumentConversion
    {
        private Microsoft.Office.Interop.Word.Application Word;
        private object Unknown = Type.Missing;
        private object True = true;
        private object False = false;

        public bool WordToPdf(object Source, object Target)
        {
            bool ret = true;

            if (Word == null) Word = new Microsoft.Office.Interop.Word.Application();

            try
            {
                Word.Visible = false;
                Word.Documents.Open(ref Source, ref Unknown,
                     ref True, ref Unknown, ref Unknown,
                     ref Unknown, ref Unknown, ref Unknown,
                     ref Unknown, ref Unknown, ref Unknown,
                     ref Unknown, ref Unknown, ref Unknown,
                     ref Unknown, ref Unknown);
                Word.Application.Visible = false;
                Word.WindowState = WdWindowState.wdWindowStateMinimize;

#if false
                object saveFormat = Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatPDF;

                Word.ActiveDocument.SaveAs(ref Target, ref saveFormat,
                    ref Unknown, ref Unknown, ref Unknown,
                    ref Unknown, ref Unknown, ref Unknown,
                    ref Unknown, ref Unknown, ref Unknown,
                    ref Unknown, ref Unknown, ref Unknown,
                    ref Unknown, ref Unknown);
#else
                Word.ActiveDocument.ExportAsFixedFormat(
                    (string)Target, WdExportFormat.wdExportFormatPDF,
                    false, WdExportOptimizeFor.wdExportOptimizeForOnScreen,
                    WdExportRange.wdExportAllDocument, 0, 0,
                    WdExportItem.wdExportDocumentContent, true, false,
                    WdExportCreateBookmarks.wdExportCreateWordBookmarks,
                    true, true, true);
#endif
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                ret = false;
            }
            finally
            {
                if (Word != null)
                {
                    // close the application
                    Word.Quit(ref Unknown, ref Unknown, ref Unknown);
                }
            }

            return ret;
        }
    }
}


来源:https://stackoverflow.com/questions/36066524/file-system-watcher-stops-working-when-converting-word-doc-docx-files-to-pdf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!