【问题标题】:File System Watcher stops working when converting Word doc/docx files to PDF将 Word doc/docx 文件转换为 PDF 时,File System Watcher 停止工作
【发布时间】:2016-03-18 13:48:03
【问题描述】:

我有一个用于将 .doc/.docx 文件自动转换为 *.pdf 的 Powershell 脚本。 该脚本对于第一个文件运行良好。但是如果我将另一个文件放在监视文件夹中,则监视程序不会触发事件。

这是完整的脚本。如果我注释掉所有 $doc 变量,脚本会运行多次而没有任何问题。我是否忽略/忽略了什么?

$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
}`

【问题讨论】:

  • 不是一个真正的答案。我对观察者和Register-ObjectEvent 有一些问题,无法解决。所以我使用了不同的方法并创建了工具Watch-Directory.ps1。请看一下,也许你会发现它有用。
  • 当您看到问题时,您是否注意到使用任务管理器时 word 实例没有退出?我看不出这会如何影响任何事情,但在 WaitForPendingFinalizers 添加Remove-Variable docRemove-Variable word 之后。可能值得将您的代码放入 try/catch/finally 并将您的清理代码放入 finally。添加更多日志记录(文件/写入主机/等)以帮助调试。目前只有我能想到的建议......

标签: windows powershell pdf scripting docx


【解决方案1】:

您的脚本有一些问题,可以找到更多调试逻辑。

在某些情况下,(Get-Item System.Management.Automation.PSEventArgs.SourceEventArgs.FullPath) 返回 null。由于未知原因,这似乎对每个转换的文档都发生一次。也许它与“~Temp”文件有关。

随后,if(!($name.startsWith("~") 将抛出异常。

当您使用$inputFilePath = $Event.SourceEventArgs.FullPath 时,您的变量是一个FileInfo,并且您确实想将一个字符串传递给$word.Documents.Open($inputFilePath)

最后,有时BaseName 为空。不知道为什么,但代码可以对此进行测试或使用其他方式剖析 FullPath 以获取名称和路径部分。

话虽如此,一旦你开始工作,我的个人个人经验是调用 Word 上的 COM 对象在 PowerShell 中进行这种转换是不可靠的(Word 挂起,~Temp 文件留下,你必须从任务管理器中杀死 Word,PowerShell 中的 COM 调用永远不会返回)。我的测试表明调用 C# 控制台应用程序进行转换更加可靠。您可以完全用 C# 编写此目录观察程序和转换器并完成相同的任务。

假设您仍想将两者结合起来,即 PowerShell 观察程序和 C# Word 到 PDF 转换器,下面是我想出的解决方案。该脚本运行大约一分钟,因此您可以在 ISE 或控制台中进行测试。从控制台按一个键退出。在退出之前,脚本通过取消注册事件干净地退出,这在 ISE 中进行测试时非常有用。 根据您打算如何运行脚本相应地更改此设置。

PowerShell 观察者

$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 转换器

为参数添加适当的错误检查...

添加对 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;
        }
    }
}

【讨论】:

  • 你说得对。如果“~Temp.doc*”文件触发了事件,BasePath 将为空。这会导致if(!($name.startsWith("~") 出现异常。正如您提到的,我必须先检查$name 是否不为空。回答是的,我知道将 Word 称为 COM 对象的问题。但就目前而言,这对我们来说是唯一合理的解决方案。
猜你喜欢
  • 1970-01-01
  • 2016-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多