powershell 在ForEach-Object -Parallel中使用COM对象

qvsjd97n  于 12个月前  发布在  Shell
关注(0)|答案(1)|浏览(97)

我写了一个脚本,把1000多个Excel文件转换成PDF,以前都是用LibreOffice的命令行界面来实现,现在,我试图使用Microsoft Excel,我希望用ForEach-Object -Parallel并行化该过程。我的问题是,尽管我使用$using:,但 *COM对象 * 无法在并行循环中访问。这是可能的吗(在所有)在一个并行循环中使用COM对象?下面是我的代码:

# Get working directory
$wd = pwd

# Output directory for converted PDFs
$output_dir = "$wd\data\sample_curriculums_fall2023\pdf"

# Excel fixed format (important to specify the conversion target (i.e. PDF))
$ExcelFixedFormat = “Microsoft.Office.Interop.Excel.xlFixedFormatType” -as [type]

# Get Excel files
$excel_files = Get-ChildItem -Path "$wd\data\sample_curriculums_fall2023\excel\" -Filter *xlsm

# Create COM Object for Excel and make it invisible (i.e. headless)
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelObject.Visible = $false

$excel_files | ForEach-Object -ThrottleLimit 20 -Parallel {

        $file = $_
        $obj = $using:ExcelObject
        $fm = $using:ExcelFixedFormat

        # Make name for PDF output
        $output = Join-Path -Path $using:output_dir -ChildPath ($file.BaseName + ".pdf")
    
        # Open Excel file to convert
        $workbook = $obj.Workbooks.Open($file.FullName, 3) # PROBLEM!!!!
        $workbook.Saved = $true
        $workbook.ExportAsFixedFormat($fm::xlTypePDF, $output)
        $obj.Workbooks.Close()
    
}

$ExcelObject.Quit()

字符串
在并行循环中的这一行抛出错误:

$workbook = $obj.Workbooks.Open($file.FullName, 3)


我得到以下错误:

You cannot call a method on a null-valued expression.


这表明$obj变量不包含COM对象,并且是null。请注意,该脚本可以完美地使用常规的foreach函数。
提前感谢您的帮助。

pkwftd7m

pkwftd7m1#

  • Microsoft Office COM Automation服务器不是线程安全的,因此不能在多个线程之间共享给定示例。
  • 有关背景信息和显式管理线程的方法,请参阅Threading support in Office,这在PowerShell中可能不可行或不切实际。
  • New-Object -ComObject Excel.Application为 * 每个要转换的文件 * 创建一个单独的示例是不值得的,因为每个这样的调用都会创建一个新的Excel * 子进程 *,这在性能和内存使用方面都是昂贵的。

但是,您可以尝试 * 批处理 * 您的输入文件,以便您仅创建有限数量的Excel进程,每个进程处理 * 批处理 * 输入文件:

# How many Excel instances to run in parallel.
# Tweak this number based on your system's CPU count and memory.
$throttleLimit = 4

# Collect all input files.
$files = 1..100
  # Get-ChildItem "$wd\data\sample_curriculums_fall2023\excel\" -Filter *xlsm

# Determine how many files to pass to each Excel instance.
$chunkSize = [Math]::Ceiling($files.Count / $throttleLimit)

# Batch the input files and process each batch with ForEach-Object -Parallel
$files |
  ForEach-Object `
    -Begin { $i = 0; $chunk = [System.Collections.Generic.List[object]]::new($chunkSize) } `
    -Process { 
      $chunk.Add($_)
      if (++$i -eq $chunkSize) {
        , $chunk.ToArray()
        $i = 0; $chunk.Clear()
      }
    } `
    -End {
      if ($i) {
        , $chunk.ToArray
      }
    } |
  ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
    $xl = New-Object -ComObject Excel.Application
    foreach ($file in $_) {
      $output = Join-Path $using:output_dir ($file.BaseName + '.pdf')
      $workbook = $xl.Workbooks.Open($file.FullName, 3)
      $workbook.Saved = $true
      $workbook.ExportAsFixedFormat(0, $output) # 0 = [Microsoft.Office.Interop.Excel.xlFixedFormatType]::xlTypePDF
      $xl.Workbooks.Close()
    }
    $xl.Quit()
  }

字符串
注意,使用了一个辅助ForEach-Object调用来批处理(分块)要处理的文件数组。
潜在地将此功能构建到PowerShell本身是GitHub issue #8270的主题。

相关问题