xlogI125’s blog

パソコン作業を効率化したい

Acrobat IAC練習 PDFのしおり名としおり移動先ページ番号をCSV形式でクリップボードにコピー 2

メモ

PDFファイルの しおり のレベルが9以下の部分を対象にして、PowerShellでしおり名としおり移動先ページ番号をCSV形式でクリップボードにコピーする。

使い捨てスクリプト

  • エラー処理やCOMオブジェクトの解放を気にしていないので練習以外で実行しない
  • Acrobatのプロセスを停止させるので事前にAcrobatでの作業を終了してください
  • しおりのアクションに「JavaScriptを実行」や「別の文書内のページに移動」は無いものとします
# [テスト環境]
#  PowerShell 5.1, Acrobat Standard DC (2022年11月頃), Windows 11

# [ファイル名]
#  "%USERPROFILE%\Desktop\GetPdfBookmark.ps1"
# [エンコード]
#  UTF-8 (BOM付き)

# [ショートカット(.lnk)]
#  リンク先(T):
#   PowerShell.exe -NoLogo -NoExit -NoProfile -ExecutionPolicy RemoteSigned -File "%USERPROFILE%\Desktop\GetPdfBookmark.ps1"
#  作業フォルダー(S):
#   ""

using namespace Microsoft.VisualBasic
using namespace System.Collections.Generic
using namespace System.Runtime.InteropServices

Set-StrictMode -Version Latest

# CreateObjectとCallByNameを使えるようにする
Add-Type -AssemblyName Microsoft.VisualBasic

# Write-Verboseの結果を表示する
$VerbosePreference = "Continue"

if ($args.Length -eq 0) {
  Write-Verbose "ショートカット(.lnk)にPDFファイルをドラッグ&ドロップしてください"
  throw '$args.Length -eq 0'
}

# パスに角括弧[]を含む場合の作業フォルダー対応
Set-Location -LiteralPath (Split-Path -LiteralPath $args[0])

Write-Verbose "Acrobatでの作業を終了してください"
Write-Verbose "しおり のレベルが9を超える部分は無視します"

timeout /t 3


# 再帰関数
function Get-BookmarkName {
  param(
    [object]$jso, 
    [object]$bkm, 
    [List[PSCustomObject]]$docBookmarks, 
    [int]$lv, 
    [string]$lvStr
  )

  if ($lv -gt 9) {
    Write-Verbose "しおり のレベルが9を超える箇所が存在します"
    return
  }

  try {
    $bkms = [Interaction]::CallByName($bkm, "children", [CallType]::Get)
  }
  catch {
    return
  }

  for ($i = 0; $i -lt $bkms.Length; $i++) {
    Get-BookmarkName `
      $jso `
      $bkms[$i] `
      $docBookmarks `
      ($lv + 1) `
      ($lvStr + "{0:00}-" -f ($i + 1))

    # しおりを実行
    [Interaction]::CallByName($bkms[$i], "execute", [CallType]::Method)

    # ファイル名(Doc.documentFileName)を取得
    $fName = [Interaction]::CallByName($jso, "documentFileName", [CallType]::Get)

    # 移動後のページ番号(Doc.pageNum)を取得
    $page = [int][Interaction]::CallByName($jso, "pageNum", [CallType]::Get)

    # しおりの名前(Bookmark.name)を取得
    $bName = [Interaction]::CallByName($bkms[$i], "name", [CallType]::Get)

    # 先頭ページに移動
    [Interaction]::CallByName($jso, "pageNum", [CallType]::Let, 0)

    # Format演算子
    $lvStrTmp = $lvStr + "{0:00}" -f ($i + 1)

    # ソートしやすいように文字数を合わせる
    for ($j = $lv; $j -lt 9; $j++) {
      $lvStrTmp += "-00"
    }

    $docBookmarks.Add([PSCustomObject]@{
      FileName = $fName;
      LevelStr = $lvStrTmp;
      Level = $lv;
      ItemNum = $i + 1;
      Page = $page + 1;
      Name1 = $null; Name2 = $null; Name3 = $null;
      Name4 = $null; Name5 = $null; Name6 = $null;
      Name7 = $null; Name8 = $null; Name9 = $null
    })
    # インデックスに-1を使用して最後の要素を取得
    $docBookmarks[-1].("Name" + $lv) = $bName
  }

  for ($i = 0; $i -lt $bkms.Length; $i++) {
    [Marshal]::FinalReleaseComObject($bkms[$i]) > $null
  }
}


# しおりの名前や移動先ページ番号などを保存
$docBookmarks = [List[PSCustomObject]]::new()

# AVDocオブジェクトを作成
$avDoc = [Interaction]::CreateObject("AcroExch.AVDoc")

for ($i = 0; $i -lt $args.Length; $i++) {
  # PDFファイルのパス
  $pdfFilePath = $args[$i]

  # PDFファイルを開く(AVDoc)
  $ret = $avDoc.Open($pdfFilePath, "")
  Write-Verbose "`$avDoc.Open(`"$pdfFilePath`", `"`") ..... $ret"

  # PDDocを取得
  $pdDoc = $avDoc.GetPDDoc()

  # JavaScript object を取得
  $jso = $pdDoc.GetJSObject()

  # bookmarkRootを取得
  $bkmR = [Interaction]::CallByName($jso, "bookmarkRoot", [CallType]::Get)

  Get-BookmarkName `
    $jso `
    $bkmR `
    $docBookmarks `
    1 `
    ""

  [Marshal]::FinalReleaseComObject($bkmR) > $null
  [Marshal]::FinalReleaseComObject($jso) > $null

  # PDFファイルを閉じる
  $pdDoc.Close() > $null
  [Marshal]::FinalReleaseComObject($pdDoc) > $null

  # PDFファイルを閉じる(AVDoc)
  $ret = $avDoc.Close($true)
  Write-Verbose "`$avDoc.Close(`$true) ..... $ret"
}

[Marshal]::FinalReleaseComObject($avDoc) > $null

if ($docBookmarks.Count -ne 0) {
  # PSCustomObjectのリストをソート
  $docBookmarks = $docBookmarks | Sort-Object `
    -Property `
      @{Expression = "FileName"; Descending = $false}, 
      @{Expression = "LevelStr"; Descending = $false}

  # PSCustomObjectの配列をCSVに変換
  $csv = $docBookmarks | ConvertTo-Csv -NoTypeInformation

  # CSVをクリップボードにコピー
  $csv | Set-Clipboard

  # グリッド ビュー に表示
  $docBookmarks | Out-GridView
} else {
  Write-Verbose "しおり がありません"
}

# ガベージ コレクション を直ちに強制実行
[GC]::Collect()
[GC]::WaitForPendingFinalizers()

# Acrobatのプロセスを取得
$ps = @(Get-Process | Where-Object Name -match '^Acrobat$|^AcroCEF$')

# Acrobatのプロセスを停止
if ($ps.Length -ne 0) {
  $ps | Stop-Process
}

Start-Sleep -Seconds 1.5

# Acrobat関係のプロセスを表示
Write-Verbose @"
`nGet-Process | Where-Object Name -match 'Acro'
$(Get-Process | Where-Object Name -match 'Acro' | Out-String -Width 80)
"@

過去記事

dy100ms.hatenadiary.jp